@@ -223,6 +223,9 @@ pub struct L7EndpointFields<'a> {
223223 /// `true` when the endpoint has a non-empty rules list.
224224 pub has_rules : bool ,
225225
226+ /// Whether WebSocket credential rewriting was enabled.
227+ pub websocket_credential_rewrite : bool ,
228+
226229 /// `true` when the endpoint has a non-empty `deny_rules` list.
227230 pub has_deny_rules : bool ,
228231
@@ -244,6 +247,9 @@ pub fn validate_l7_endpoint_semantics(ep: &L7EndpointFields<'_>) -> Vec<String>
244247 let mut errors = Vec :: new ( ) ;
245248 let protocol = ep. protocol ;
246249 let l7_protocol = L7Protocol :: parse ( protocol) ;
250+ if matches ! ( l7_protocol, Some ( L7Protocol :: Rest ) ) && ep. websocket_credential_rewrite {
251+ errors. push ( "websocket_credential_rewrite is not supported for protocol rest; use protocol: websocket with explicit message rules" . to_string ( ) ) ;
252+ }
247253 let explicit_tcp = is_explicit_tcp_protocol ( protocol) ;
248254 let jsonrpc_family = l7_protocol. is_some_and ( L7Protocol :: is_jsonrpc_family) ;
249255 let is_mcp = matches ! ( l7_protocol, Some ( L7Protocol :: Mcp ) ) ;
@@ -335,6 +341,7 @@ mod tests {
335341
336342 fn valid_rest_endpoint ( ) -> L7EndpointFields < ' static > {
337343 L7EndpointFields {
344+ websocket_credential_rewrite : false ,
338345 protocol : "rest" ,
339346 access : "read-only" ,
340347 has_rules : false ,
@@ -344,6 +351,27 @@ mod tests {
344351 }
345352 }
346353
354+ #[ test]
355+ fn rest_rejects_websocket_credential_rewrite_with_migration_hint ( ) {
356+ let mut endpoint = valid_rest_endpoint ( ) ;
357+ endpoint. websocket_credential_rewrite = true ;
358+ for protocol in [ "rest" , "REST" , "Rest" ] {
359+ endpoint. protocol = protocol;
360+ let errors = validate_l7_endpoint_semantics ( & endpoint) ;
361+ assert ! (
362+ errors. iter( ) . any( |e| e. contains( "use protocol: websocket" ) ) ,
363+ "{protocol}: {errors:?}"
364+ ) ;
365+ }
366+ for protocol in [ "websocket" , "WEBSOCKET" , "WebSocket" ] {
367+ endpoint. protocol = protocol;
368+ assert ! (
369+ validate_l7_endpoint_semantics( & endpoint) . is_empty( ) ,
370+ "{protocol}"
371+ ) ;
372+ }
373+ }
374+
347375 #[ test]
348376 fn valid_endpoint_produces_no_errors ( ) {
349377 let errors = validate_l7_endpoint_semantics ( & valid_rest_endpoint ( ) ) ;
@@ -383,6 +411,7 @@ mod tests {
383411 #[ test]
384412 fn rejects_unknown_protocol ( ) {
385413 let ep = L7EndpointFields {
414+ websocket_credential_rewrite : false ,
386415 protocol : "ftp" ,
387416 access : "" ,
388417 has_rules : false ,
@@ -397,6 +426,7 @@ mod tests {
397426 #[ test]
398427 fn rejects_rules_and_access_together ( ) {
399428 let ep = L7EndpointFields {
429+ websocket_credential_rewrite : false ,
400430 protocol : "rest" ,
401431 access : "full" ,
402432 has_rules : true ,
@@ -411,6 +441,7 @@ mod tests {
411441 #[ test]
412442 fn rejects_jsonrpc_with_access_presets ( ) {
413443 let ep = L7EndpointFields {
444+ websocket_credential_rewrite : false ,
414445 protocol : "json-rpc" ,
415446 access : "full" ,
416447 has_rules : false ,
@@ -429,6 +460,7 @@ mod tests {
429460 #[ test]
430461 fn rejects_mcp_with_access_presets ( ) {
431462 let ep = L7EndpointFields {
463+ websocket_credential_rewrite : false ,
432464 protocol : "mcp" ,
433465 access : "full" ,
434466 has_rules : false ,
@@ -447,6 +479,7 @@ mod tests {
447479 #[ test]
448480 fn rejects_jsonrpc_without_rules ( ) {
449481 let ep = L7EndpointFields {
482+ websocket_credential_rewrite : false ,
450483 protocol : "json-rpc" ,
451484 access : "" ,
452485 has_rules : false ,
@@ -461,6 +494,7 @@ mod tests {
461494 #[ test]
462495 fn rejects_protocol_without_rules_or_access ( ) {
463496 let ep = L7EndpointFields {
497+ websocket_credential_rewrite : false ,
464498 protocol : "rest" ,
465499 access : "" ,
466500 has_rules : false ,
@@ -479,6 +513,7 @@ mod tests {
479513 #[ test]
480514 fn rejects_mcp_without_rules_when_allow_all_false ( ) {
481515 let ep = L7EndpointFields {
516+ websocket_credential_rewrite : false ,
482517 protocol : "mcp" ,
483518 access : "" ,
484519 has_rules : false ,
@@ -493,6 +528,7 @@ mod tests {
493528 #[ test]
494529 fn accepts_mcp_with_allow_all_true ( ) {
495530 let ep = L7EndpointFields {
531+ websocket_credential_rewrite : false ,
496532 protocol : "mcp" ,
497533 access : "" ,
498534 has_rules : false ,
@@ -507,6 +543,7 @@ mod tests {
507543 #[ test]
508544 fn rejects_rules_that_deny_all ( ) {
509545 let ep = L7EndpointFields {
546+ websocket_credential_rewrite : false ,
510547 protocol : "rest" ,
511548 access : "" ,
512549 has_rules : true ,
@@ -521,6 +558,7 @@ mod tests {
521558 #[ test]
522559 fn mcp_allow_all_with_empty_allow_not_rejected ( ) {
523560 let ep = L7EndpointFields {
561+ websocket_credential_rewrite : false ,
524562 protocol : "mcp" ,
525563 access : "" ,
526564 has_rules : true ,
@@ -538,6 +576,7 @@ mod tests {
538576 #[ test]
539577 fn rejects_deny_rules_without_protocol ( ) {
540578 let ep = L7EndpointFields {
579+ websocket_credential_rewrite : false ,
541580 protocol : "" ,
542581 access : "" ,
543582 has_rules : false ,
@@ -556,6 +595,7 @@ mod tests {
556595 #[ test]
557596 fn rejects_deny_rules_without_allow_base ( ) {
558597 let ep = L7EndpointFields {
598+ websocket_credential_rewrite : false ,
559599 protocol : "rest" ,
560600 access : "" ,
561601 has_rules : false ,
@@ -574,6 +614,7 @@ mod tests {
574614 #[ test]
575615 fn no_protocol_no_errors ( ) {
576616 let ep = L7EndpointFields {
617+ websocket_credential_rewrite : false ,
577618 protocol : "" ,
578619 access : "" ,
579620 has_rules : false ,
@@ -588,6 +629,7 @@ mod tests {
588629 #[ test]
589630 fn explicit_tcp_is_valid_without_l7_fields ( ) {
590631 let ep = L7EndpointFields {
632+ websocket_credential_rewrite : false ,
591633 protocol : "tcp" ,
592634 access : "" ,
593635 has_rules : false ,
@@ -604,6 +646,7 @@ mod tests {
604646 #[ test]
605647 fn explicit_tcp_rejects_l7_fields ( ) {
606648 let ep = L7EndpointFields {
649+ websocket_credential_rewrite : false ,
607650 protocol : "tcp" ,
608651 access : "full" ,
609652 has_rules : false ,
@@ -648,6 +691,7 @@ mod tests {
648691 #[ test]
649692 fn jsonrpc_with_access_emits_single_diagnostic ( ) {
650693 let ep = L7EndpointFields {
694+ websocket_credential_rewrite : false ,
651695 protocol : "json-rpc" ,
652696 access : "full" ,
653697 has_rules : false ,
@@ -669,6 +713,7 @@ mod tests {
669713 #[ test]
670714 fn jsonrpc_without_rules_or_access_emits_single_diagnostic ( ) {
671715 let ep = L7EndpointFields {
716+ websocket_credential_rewrite : false ,
672717 protocol : "json-rpc" ,
673718 access : "" ,
674719 has_rules : false ,
0 commit comments