1use std::sync::Arc;
44
45use bytes::Bytes;
46use futures::future::BoxFuture;
47use futures::stream::StreamExt;
48
49use crate::codec::CodecFormat;
50use crate::dispatcher::RequestStream;
51use crate::error::ConnectError;
52use crate::handler::BoxStream;
53use crate::payload::Payload;
54use crate::response::{EncodedResponse, RequestContext, Response};
55
56pub use async_trait::async_trait;
67
68#[async_trait::async_trait]
116pub trait Interceptor: Send + Sync + 'static {
117 async fn intercept_unary(
128 &self,
129 req: UnaryRequest,
130 next: Next<'_>,
131 ) -> Result<UnaryResponse, ConnectError> {
132 next.run(req).await
133 }
134
135 async fn intercept_streaming(
182 &self,
183 req: StreamRequest,
184 inbound: PayloadStream,
185 next: NextStream<'_>,
186 ) -> Result<StreamResponse, ConnectError> {
187 next.run(req, inbound).await
188 }
189}
190
191pub fn unary_interceptor<F>(f: F) -> impl Interceptor
207where
208 F: for<'a> Fn(UnaryRequest, Next<'a>) -> BoxFuture<'a, Result<UnaryResponse, ConnectError>>
209 + Send
210 + Sync
211 + 'static,
212{
213 struct FnInterceptor<F>(F);
214
215 #[async_trait::async_trait]
216 impl<F> Interceptor for FnInterceptor<F>
217 where
218 F: for<'a> Fn(UnaryRequest, Next<'a>) -> BoxFuture<'a, Result<UnaryResponse, ConnectError>>
219 + Send
220 + Sync
221 + 'static,
222 {
223 async fn intercept_unary(
224 &self,
225 req: UnaryRequest,
226 next: Next<'_>,
227 ) -> Result<UnaryResponse, ConnectError> {
228 (self.0)(req, next).await
229 }
230 }
231
232 FnInterceptor(f)
233}
234
235pub struct Next<'a> {
241 rest: &'a [Arc<dyn Interceptor>],
242 terminal: &'a (dyn UnaryTerminal + 'a),
243}
244
245impl<'a> Next<'a> {
246 pub(crate) fn new(
248 rest: &'a [Arc<dyn Interceptor>],
249 terminal: &'a (dyn UnaryTerminal + 'a),
250 ) -> Self {
251 Self { rest, terminal }
252 }
253
254 pub async fn run(self, req: UnaryRequest) -> Result<UnaryResponse, ConnectError> {
261 match self.rest.split_first() {
262 Some((head, tail)) => {
263 head.intercept_unary(
264 req,
265 Next {
266 rest: tail,
267 terminal: self.terminal,
268 },
269 )
270 .await
271 }
272 None => self.terminal.call(req).await,
273 }
274 }
275}
276
277impl std::fmt::Debug for Next<'_> {
278 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
279 f.debug_struct("Next")
280 .field("remaining", &self.rest.len())
281 .finish_non_exhaustive()
282 }
283}
284
285#[async_trait::async_trait]
291pub(crate) trait UnaryTerminal: Send + Sync {
292 async fn call(&self, req: UnaryRequest) -> Result<UnaryResponse, ConnectError>;
293}
294
295#[derive(Debug)]
313#[non_exhaustive]
314pub struct UnaryRequest {
315 pub ctx: RequestContext,
318 pub payload: Payload,
321}
322
323impl UnaryRequest {
324 pub fn new(ctx: RequestContext, body: Bytes, format: CodecFormat) -> Self {
327 let payload = Payload::new(body, format).with_decode_options(ctx.decode_options().clone());
328 Self { ctx, payload }
329 }
330}
331
332pub type UnaryResponse = Response<Payload>;
339
340impl UnaryResponse {
341 pub fn from_encoded(resp: EncodedResponse, format: CodecFormat) -> Self {
343 Response {
344 body: Payload::new(resp.body.into_contiguous(), format),
349 headers: resp.headers,
350 trailers: resp.trailers,
351 compress: resp.compress,
352 }
353 }
354
355 pub fn into_encoded(self) -> Result<EncodedResponse, ConnectError> {
362 Ok(Response {
363 body: self.body.encoded()?.into(),
364 headers: self.headers,
365 trailers: self.trailers,
366 compress: self.compress,
367 })
368 }
369}
370
371pub type PayloadStream = BoxStream<Result<Payload, ConnectError>>;
384
385#[derive(Debug)]
395#[non_exhaustive]
396pub struct StreamRequest {
397 pub ctx: RequestContext,
400}
401
402impl StreamRequest {
403 pub fn new(ctx: RequestContext) -> Self {
406 Self { ctx }
407 }
408}
409
410pub type StreamResponse = Response<PayloadStream>;
423
424impl StreamResponse {
425 pub fn from_encoded(
428 resp: Response<BoxStream<Result<Bytes, ConnectError>>>,
429 format: CodecFormat,
430 ) -> Self {
431 resp.map_body(move |stream| -> PayloadStream {
432 Box::pin(stream.map(move |item| item.map(|bytes| Payload::new(bytes, format))))
433 })
434 }
435
436 pub fn into_encoded(self) -> Response<BoxStream<Result<Bytes, ConnectError>>> {
445 self.map_body(|stream| -> BoxStream<Result<Bytes, ConnectError>> {
446 Box::pin(stream.map(|item| item.and_then(|payload| payload.encoded())))
447 })
448 }
449}
450
451pub fn streaming_interceptor<F>(f: F) -> impl Interceptor
466where
467 F: for<'a> Fn(
468 StreamRequest,
469 PayloadStream,
470 NextStream<'a>,
471 ) -> BoxFuture<'a, Result<StreamResponse, ConnectError>>
472 + Send
473 + Sync
474 + 'static,
475{
476 struct FnInterceptor<F>(F);
477
478 #[async_trait::async_trait]
479 impl<F> Interceptor for FnInterceptor<F>
480 where
481 F: for<'a> Fn(
482 StreamRequest,
483 PayloadStream,
484 NextStream<'a>,
485 ) -> BoxFuture<'a, Result<StreamResponse, ConnectError>>
486 + Send
487 + Sync
488 + 'static,
489 {
490 async fn intercept_streaming(
491 &self,
492 req: StreamRequest,
493 inbound: PayloadStream,
494 next: NextStream<'_>,
495 ) -> Result<StreamResponse, ConnectError> {
496 (self.0)(req, inbound, next).await
497 }
498 }
499
500 FnInterceptor(f)
501}
502
503pub struct NextStream<'a> {
510 rest: &'a [Arc<dyn Interceptor>],
511 terminal: &'a (dyn StreamTerminal + 'a),
512}
513
514impl<'a> NextStream<'a> {
515 pub(crate) fn new(
517 rest: &'a [Arc<dyn Interceptor>],
518 terminal: &'a (dyn StreamTerminal + 'a),
519 ) -> Self {
520 Self { rest, terminal }
521 }
522
523 pub async fn run(
530 self,
531 req: StreamRequest,
532 inbound: PayloadStream,
533 ) -> Result<StreamResponse, ConnectError> {
534 match self.rest.split_first() {
535 Some((head, tail)) => {
536 head.intercept_streaming(
537 req,
538 inbound,
539 NextStream {
540 rest: tail,
541 terminal: self.terminal,
542 },
543 )
544 .await
545 }
546 None => self.terminal.call(req, inbound).await,
547 }
548 }
549}
550
551impl std::fmt::Debug for NextStream<'_> {
552 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
553 f.debug_struct("NextStream")
554 .field("remaining", &self.rest.len())
555 .finish_non_exhaustive()
556 }
557}
558
559#[async_trait::async_trait]
565pub(crate) trait StreamTerminal: Send + Sync {
566 async fn call(
567 &self,
568 req: StreamRequest,
569 inbound: PayloadStream,
570 ) -> Result<StreamResponse, ConnectError>;
571}
572
573pub async fn run_chain_streaming<F, Fut>(
596 interceptors: &[Arc<dyn Interceptor>],
597 req: StreamRequest,
598 inbound: PayloadStream,
599 terminal: F,
600) -> Result<StreamResponse, ConnectError>
601where
602 F: Fn(StreamRequest, PayloadStream) -> Fut + Send + Sync,
603 Fut: std::future::Future<Output = Result<StreamResponse, ConnectError>> + Send,
604{
605 struct FnTerminal<F>(F);
606
607 #[async_trait::async_trait]
608 impl<F, Fut> StreamTerminal for FnTerminal<F>
609 where
610 F: Fn(StreamRequest, PayloadStream) -> Fut + Send + Sync,
611 Fut: std::future::Future<Output = Result<StreamResponse, ConnectError>> + Send,
612 {
613 async fn call(
614 &self,
615 req: StreamRequest,
616 inbound: PayloadStream,
617 ) -> Result<StreamResponse, ConnectError> {
618 (self.0)(req, inbound).await
619 }
620 }
621
622 let terminal = FnTerminal(terminal);
623 NextStream::new(interceptors, &terminal)
624 .run(req, inbound)
625 .await
626}
627
628fn payload_stream_to_request_stream(stream: PayloadStream) -> RequestStream {
635 Box::pin(stream.map(|item| item.and_then(|payload| payload.encoded())))
636}
637
638fn request_stream_to_payload_stream(stream: RequestStream, format: CodecFormat) -> PayloadStream {
641 Box::pin(stream.map(move |item| item.map(|bytes| Payload::new(bytes, format))))
642}
643
644pub(crate) async fn call_server_streaming_intercepted<D: crate::Dispatcher>(
651 dispatcher: &D,
652 interceptors: &[Arc<dyn Interceptor>],
653 path: &str,
654 ctx: RequestContext,
655 body: Bytes,
656 format: CodecFormat,
657) -> Result<Response<BoxStream<Result<Bytes, ConnectError>>>, ConnectError> {
658 if interceptors.is_empty() {
659 return dispatcher
660 .call_server_streaming(path, ctx, body, format)
661 .await;
662 }
663 let terminal = ServerStreamingTerminal {
664 dispatcher,
665 path,
666 format,
667 };
668 let req = StreamRequest::new(ctx);
669 let inbound: PayloadStream = Box::pin(futures::stream::once(async move {
670 Ok(Payload::new(body, format))
671 }));
672 let resp = NextStream::new(interceptors, &terminal)
673 .run(req, inbound)
674 .await?;
675 Ok(resp.into_encoded())
676}
677
678pub(crate) async fn call_client_streaming_intercepted<D: crate::Dispatcher>(
684 dispatcher: &D,
685 interceptors: &[Arc<dyn Interceptor>],
686 path: &str,
687 ctx: RequestContext,
688 requests: RequestStream,
689 format: CodecFormat,
690) -> Result<EncodedResponse, ConnectError> {
691 if interceptors.is_empty() {
692 return dispatcher
693 .call_client_streaming(path, ctx, requests, format)
694 .await;
695 }
696 let terminal = ClientStreamingTerminal {
697 dispatcher,
698 path,
699 format,
700 };
701 let req = StreamRequest::new(ctx);
702 let inbound = request_stream_to_payload_stream(requests, format);
703 let resp = NextStream::new(interceptors, &terminal)
704 .run(req, inbound)
705 .await?;
706 let Response {
711 body: mut stream,
712 headers,
713 trailers,
714 compress,
715 } = resp;
716 let body = match stream.next().await {
717 Some(Ok(payload)) => payload.encoded()?,
718 Some(Err(e)) => return Err(e),
719 None => {
720 return Err(ConnectError::internal(
721 "client-streaming interceptor consumed the response without replacing it",
722 ));
723 }
724 };
725 Ok(Response {
726 body: body.into(),
727 headers,
728 trailers,
729 compress,
730 })
731}
732
733pub(crate) async fn call_bidi_streaming_intercepted<D: crate::Dispatcher>(
735 dispatcher: &D,
736 interceptors: &[Arc<dyn Interceptor>],
737 path: &str,
738 ctx: RequestContext,
739 requests: RequestStream,
740 format: CodecFormat,
741) -> Result<Response<BoxStream<Result<Bytes, ConnectError>>>, ConnectError> {
742 if interceptors.is_empty() {
743 return dispatcher
744 .call_bidi_streaming(path, ctx, requests, format)
745 .await;
746 }
747 let terminal = BidiStreamingTerminal {
748 dispatcher,
749 path,
750 format,
751 };
752 let req = StreamRequest::new(ctx);
753 let inbound = request_stream_to_payload_stream(requests, format);
754 let resp = NextStream::new(interceptors, &terminal)
755 .run(req, inbound)
756 .await?;
757 Ok(resp.into_encoded())
758}
759
760struct ServerStreamingTerminal<'a, D> {
763 dispatcher: &'a D,
764 path: &'a str,
765 format: CodecFormat,
766}
767
768#[async_trait::async_trait]
769impl<D: crate::Dispatcher> StreamTerminal for ServerStreamingTerminal<'_, D> {
770 async fn call(
771 &self,
772 req: StreamRequest,
773 mut inbound: PayloadStream,
774 ) -> Result<StreamResponse, ConnectError> {
775 let body = match inbound.next().await {
779 Some(Ok(payload)) => payload.encoded()?,
780 Some(Err(e)) => return Err(e),
781 None => {
782 return Err(ConnectError::internal(
783 "server-streaming interceptor consumed the request without replacing it",
784 ));
785 }
786 };
787 let resp = self
788 .dispatcher
789 .call_server_streaming(self.path, req.ctx, body, self.format)
790 .await?;
791 Ok(StreamResponse::from_encoded(resp, self.format))
792 }
793}
794
795struct ClientStreamingTerminal<'a, D> {
798 dispatcher: &'a D,
799 path: &'a str,
800 format: CodecFormat,
801}
802
803#[async_trait::async_trait]
804impl<D: crate::Dispatcher> StreamTerminal for ClientStreamingTerminal<'_, D> {
805 async fn call(
806 &self,
807 req: StreamRequest,
808 inbound: PayloadStream,
809 ) -> Result<StreamResponse, ConnectError> {
810 let requests = payload_stream_to_request_stream(inbound);
811 let resp = self
812 .dispatcher
813 .call_client_streaming(self.path, req.ctx, requests, self.format)
814 .await?;
815 let format = self.format;
816 Ok(resp.map_body(move |body| -> PayloadStream {
820 Box::pin(futures::stream::once(async move {
821 Ok(Payload::new(body.into_contiguous(), format))
822 }))
823 }))
824 }
825}
826
827struct BidiStreamingTerminal<'a, D> {
830 dispatcher: &'a D,
831 path: &'a str,
832 format: CodecFormat,
833}
834
835#[async_trait::async_trait]
836impl<D: crate::Dispatcher> StreamTerminal for BidiStreamingTerminal<'_, D> {
837 async fn call(
838 &self,
839 req: StreamRequest,
840 inbound: PayloadStream,
841 ) -> Result<StreamResponse, ConnectError> {
842 let requests = payload_stream_to_request_stream(inbound);
843 let resp = self
844 .dispatcher
845 .call_bidi_streaming(self.path, req.ctx, requests, self.format)
846 .await?;
847 Ok(StreamResponse::from_encoded(resp, self.format))
848 }
849}
850
851pub async fn run_chain<F, Fut>(
872 interceptors: &[Arc<dyn Interceptor>],
873 req: UnaryRequest,
874 terminal: F,
875) -> Result<UnaryResponse, ConnectError>
876where
877 F: Fn(UnaryRequest) -> Fut + Send + Sync,
878 Fut: std::future::Future<Output = Result<UnaryResponse, ConnectError>> + Send,
879{
880 struct FnTerminal<F>(F);
881
882 #[async_trait::async_trait]
883 impl<F, Fut> UnaryTerminal for FnTerminal<F>
884 where
885 F: Fn(UnaryRequest) -> Fut + Send + Sync,
886 Fut: std::future::Future<Output = Result<UnaryResponse, ConnectError>> + Send,
887 {
888 async fn call(&self, req: UnaryRequest) -> Result<UnaryResponse, ConnectError> {
889 (self.0)(req).await
890 }
891 }
892
893 let terminal = FnTerminal(terminal);
894 Next::new(interceptors, &terminal).run(req).await
895}
896
897pub(crate) async fn call_unary_intercepted<D: crate::Dispatcher>(
906 dispatcher: &D,
907 interceptors: &[Arc<dyn Interceptor>],
908 path: &str,
909 ctx: RequestContext,
910 body: Bytes,
911 format: CodecFormat,
912) -> Result<EncodedResponse, ConnectError> {
913 if interceptors.is_empty() {
914 let payload = Payload::new(body, format).with_decode_options(ctx.decode_options().clone());
915 return dispatcher.call_unary(path, ctx, payload, format).await;
916 }
917 let terminal = DispatchTerminal {
918 dispatcher,
919 path,
920 format,
921 };
922 let req = UnaryRequest::new(ctx, body, format);
923 let resp = Next::new(interceptors, &terminal).run(req).await?;
924 resp.into_encoded()
925}
926
927struct DispatchTerminal<'a, D> {
929 dispatcher: &'a D,
930 path: &'a str,
931 format: CodecFormat,
932}
933
934#[async_trait::async_trait]
935impl<D: crate::Dispatcher> UnaryTerminal for DispatchTerminal<'_, D> {
936 async fn call(&self, req: UnaryRequest) -> Result<UnaryResponse, ConnectError> {
937 let UnaryRequest { ctx, payload } = req;
938 let resp = self
941 .dispatcher
942 .call_unary(self.path, ctx, payload, self.format)
943 .await?;
944 Ok(UnaryResponse::from_encoded(resp, self.format))
945 }
946}
947
948#[cfg(test)]
949mod tests {
950 use super::*;
951 use crate::codec::encode_proto;
952 use buffa_types::google::protobuf::StringValue;
953 use std::sync::Mutex;
954
955 struct RecordingTerminal {
957 ran: Mutex<bool>,
958 respond_with: &'static str,
959 }
960
961 #[async_trait::async_trait]
962 impl UnaryTerminal for RecordingTerminal {
963 async fn call(&self, req: UnaryRequest) -> Result<UnaryResponse, ConnectError> {
964 *self.ran.lock().unwrap() = true;
965 let in_len = req.payload.encoded()?.len().to_string();
968 let body = encode_proto(&StringValue {
969 value: self.respond_with.into(),
970 ..Default::default()
971 })?;
972 let mut resp = EncodedResponse::new(body.into());
973 resp.headers.insert("x-in-len", in_len.parse().unwrap());
974 Ok(UnaryResponse::from_encoded(resp, CodecFormat::Proto))
975 }
976 }
977
978 fn req() -> UnaryRequest {
979 let body = encode_proto(&StringValue {
980 value: "hi".into(),
981 ..Default::default()
982 })
983 .unwrap();
984 UnaryRequest::new(RequestContext::default(), body, CodecFormat::Proto)
985 }
986
987 struct Tagger(&'static str);
991
992 #[derive(Clone, Default)]
993 struct Trace(Arc<Mutex<Vec<&'static str>>>);
994
995 #[async_trait::async_trait]
996 impl Interceptor for Tagger {
997 async fn intercept_unary(
998 &self,
999 mut req: UnaryRequest,
1000 next: Next<'_>,
1001 ) -> Result<UnaryResponse, ConnectError> {
1002 req.ctx
1003 .extensions
1004 .get_or_insert_default::<Trace>()
1005 .0
1006 .lock()
1007 .unwrap()
1008 .push(self.0);
1009 let resp = next.run(req).await?;
1010 Ok(resp.with_header("x-trace", format!("{}-out", self.0)))
1011 }
1012 }
1013
1014 #[tokio::test]
1015 async fn ordering_first_registered_is_outermost() {
1016 let trace = Trace::default();
1017 let chain: Vec<Arc<dyn Interceptor>> = vec![
1018 Arc::new(Tagger("a")),
1019 Arc::new(Tagger("b")),
1020 Arc::new(Tagger("c")),
1021 ];
1022 let terminal = RecordingTerminal {
1023 ran: Mutex::new(false),
1024 respond_with: "ok",
1025 };
1026 let mut request = req();
1027 request.ctx.extensions.insert(trace.clone());
1028 let resp = Next::new(&chain, &terminal).run(request).await.unwrap();
1029 assert!(*terminal.ran.lock().unwrap(), "terminal should have run");
1030 assert_eq!(*trace.0.lock().unwrap(), vec!["a", "b", "c"]);
1032 let outs: Vec<_> = resp
1035 .headers
1036 .get_all("x-trace")
1037 .iter()
1038 .map(|v| v.to_str().unwrap().to_owned())
1039 .collect();
1040 assert_eq!(outs, vec!["c-out", "b-out", "a-out"]);
1041 }
1042
1043 #[tokio::test]
1044 async fn short_circuit_skips_terminal() {
1045 struct Reject;
1046 #[async_trait::async_trait]
1047 impl Interceptor for Reject {
1048 async fn intercept_unary(
1049 &self,
1050 _req: UnaryRequest,
1051 _next: Next<'_>,
1052 ) -> Result<UnaryResponse, ConnectError> {
1053 let mut headers = http::HeaderMap::new();
1057 headers.insert("x-deny-policy", "p1".parse().unwrap());
1058 Err(ConnectError::permission_denied("nope").with_headers(headers))
1059 }
1060 }
1061 let chain: Vec<Arc<dyn Interceptor>> = vec![Arc::new(Reject), Arc::new(Tagger("never"))];
1062 let terminal = RecordingTerminal {
1063 ran: Mutex::new(false),
1064 respond_with: "ok",
1065 };
1066 let err = Next::new(&chain, &terminal).run(req()).await.unwrap_err();
1067 assert_eq!(err.code, crate::ErrorCode::PermissionDenied);
1068 assert!(!*terminal.ran.lock().unwrap(), "terminal must not run");
1069 assert_eq!(
1075 err.response_headers().get("x-deny-policy").unwrap(),
1076 "p1",
1077 "diagnostic headers on a short-circuit error must survive the chain"
1078 );
1079 }
1080
1081 #[tokio::test]
1085 async fn call_unary_intercepted_propagates_error_headers() {
1086 struct Reject;
1087 #[async_trait::async_trait]
1088 impl Interceptor for Reject {
1089 async fn intercept_unary(
1090 &self,
1091 _req: UnaryRequest,
1092 _next: Next<'_>,
1093 ) -> Result<UnaryResponse, ConnectError> {
1094 let mut headers = http::HeaderMap::new();
1095 headers.insert("x-deny-policy", "p1".parse().unwrap());
1096 Err(ConnectError::permission_denied("nope").with_headers(headers))
1097 }
1098 }
1099 struct PanickyDispatcher;
1100 impl crate::Dispatcher for PanickyDispatcher {
1101 fn lookup(&self, _: &str) -> Option<crate::dispatcher::MethodDescriptor> {
1102 None
1103 }
1104 fn call_unary(
1105 &self,
1106 _: &str,
1107 _: RequestContext,
1108 _: Payload,
1109 _: CodecFormat,
1110 ) -> crate::dispatcher::UnaryResult {
1111 unreachable!("dispatcher must not be reached when an interceptor short-circuits")
1112 }
1113 fn call_server_streaming(
1114 &self,
1115 _: &str,
1116 _: RequestContext,
1117 _: Bytes,
1118 _: CodecFormat,
1119 ) -> crate::dispatcher::StreamingResult {
1120 unreachable!()
1121 }
1122 fn call_client_streaming(
1123 &self,
1124 _: &str,
1125 _: RequestContext,
1126 _: crate::dispatcher::RequestStream,
1127 _: CodecFormat,
1128 ) -> crate::dispatcher::UnaryResult {
1129 unreachable!()
1130 }
1131 fn call_bidi_streaming(
1132 &self,
1133 _: &str,
1134 _: RequestContext,
1135 _: crate::dispatcher::RequestStream,
1136 _: CodecFormat,
1137 ) -> crate::dispatcher::StreamingResult {
1138 unreachable!()
1139 }
1140 }
1141 let chain: Vec<Arc<dyn Interceptor>> = vec![Arc::new(Reject)];
1142 let err = call_unary_intercepted(
1143 &PanickyDispatcher,
1144 &chain,
1145 "p",
1146 RequestContext::default(),
1147 Bytes::new(),
1148 CodecFormat::Proto,
1149 )
1150 .await
1151 .unwrap_err();
1152 assert_eq!(err.code, crate::ErrorCode::PermissionDenied);
1153 assert_eq!(err.response_headers().get("x-deny-policy").unwrap(), "p1");
1154 }
1155
1156 #[tokio::test]
1157 async fn mutation_replaces_request_body() {
1158 struct Replace;
1159 #[async_trait::async_trait]
1160 impl Interceptor for Replace {
1161 async fn intercept_unary(
1162 &self,
1163 mut req: UnaryRequest,
1164 next: Next<'_>,
1165 ) -> Result<UnaryResponse, ConnectError> {
1166 req.payload.set_message(StringValue {
1167 value: "rewritten by interceptor".into(),
1168 ..Default::default()
1169 });
1170 next.run(req).await
1171 }
1172 }
1173 let chain: Vec<Arc<dyn Interceptor>> = vec![Arc::new(Replace)];
1174 let terminal = RecordingTerminal {
1175 ran: Mutex::new(false),
1176 respond_with: "ok",
1177 };
1178 let resp = Next::new(&chain, &terminal).run(req()).await.unwrap();
1179 let in_len: usize = resp
1182 .headers
1183 .get("x-in-len")
1184 .unwrap()
1185 .to_str()
1186 .unwrap()
1187 .parse()
1188 .unwrap();
1189 let original_len = req().payload.encoded().unwrap().len();
1190 assert_ne!(in_len, original_len, "terminal should see the replacement");
1191 }
1192
1193 #[tokio::test]
1194 async fn closure_interceptor_works() {
1195 let i = unary_interceptor(|req, next| {
1196 Box::pin(async move {
1197 let resp = next.run(req).await?;
1198 Ok(resp.with_header("x-fn", "1"))
1199 })
1200 });
1201 let chain: Vec<Arc<dyn Interceptor>> = vec![Arc::new(i)];
1202 let resp = run_chain(&chain, req(), |_| async {
1204 Ok(UnaryResponse::from_encoded(
1205 EncodedResponse::new(Bytes::new().into()),
1206 CodecFormat::Proto,
1207 ))
1208 })
1209 .await
1210 .unwrap();
1211 assert_eq!(resp.headers.get("x-fn").unwrap(), "1");
1212 }
1213
1214 #[tokio::test]
1218 async fn passthrough_chain_preserves_response_metadata() {
1219 struct Passthrough;
1220 #[async_trait::async_trait]
1221 impl Interceptor for Passthrough {}
1222 let chain: Vec<Arc<dyn Interceptor>> = vec![Arc::new(Passthrough)];
1223 let resp = run_chain(&chain, req(), |_| async {
1224 let mut r = EncodedResponse::new(Bytes::from_static(b"x").into());
1225 r.headers.insert("x-h", "1".parse().unwrap());
1226 r.trailers.insert("x-t", "2".parse().unwrap());
1227 r.compress = Some(true);
1228 Ok(UnaryResponse::from_encoded(r, CodecFormat::Proto))
1229 })
1230 .await
1231 .unwrap();
1232 let encoded = resp.into_encoded().unwrap();
1233 assert_eq!(encoded.headers.get("x-h").unwrap(), "1");
1234 assert_eq!(encoded.trailers.get("x-t").unwrap(), "2");
1235 assert_eq!(encoded.compress, Some(true));
1236 assert_eq!(&*encoded.body.into_contiguous(), b"x");
1237 }
1238
1239 #[tokio::test]
1240 async fn empty_chain_is_no_op() {
1241 struct Echo;
1249 impl crate::Dispatcher for Echo {
1250 fn lookup(&self, _: &str) -> Option<crate::dispatcher::MethodDescriptor> {
1251 None
1252 }
1253 fn call_unary(
1254 &self,
1255 _: &str,
1256 _: RequestContext,
1257 request: Payload,
1258 _: CodecFormat,
1259 ) -> crate::dispatcher::UnaryResult {
1260 Box::pin(async move { Ok(EncodedResponse::new(request.encoded()?.into())) })
1261 }
1262 fn call_server_streaming(
1263 &self,
1264 _: &str,
1265 _: RequestContext,
1266 _: Bytes,
1267 _: CodecFormat,
1268 ) -> crate::dispatcher::StreamingResult {
1269 unimplemented!()
1270 }
1271 fn call_client_streaming(
1272 &self,
1273 _: &str,
1274 _: RequestContext,
1275 _: crate::dispatcher::RequestStream,
1276 _: CodecFormat,
1277 ) -> crate::dispatcher::UnaryResult {
1278 unimplemented!()
1279 }
1280 fn call_bidi_streaming(
1281 &self,
1282 _: &str,
1283 _: RequestContext,
1284 _: crate::dispatcher::RequestStream,
1285 _: CodecFormat,
1286 ) -> crate::dispatcher::StreamingResult {
1287 unimplemented!()
1288 }
1289 }
1290 let body = Bytes::from_static(b"x");
1291 let resp = call_unary_intercepted(
1292 &Echo,
1293 &[],
1294 "p",
1295 RequestContext::default(),
1296 body.clone(),
1297 CodecFormat::Proto,
1298 )
1299 .await
1300 .unwrap();
1301 assert!(std::ptr::eq(
1303 resp.body.into_contiguous().as_ptr(),
1304 body.as_ptr()
1305 ));
1306 }
1307
1308 #[tokio::test]
1319 async fn dispatch_terminal_forwards_payload_to_handler() {
1320 let captured = Arc::new(Mutex::new(None::<String>));
1321
1322 struct Capture(Arc<Mutex<Option<String>>>);
1325 impl crate::Dispatcher for Capture {
1326 fn lookup(&self, _: &str) -> Option<crate::dispatcher::MethodDescriptor> {
1327 None
1328 }
1329 fn call_unary(
1330 &self,
1331 _: &str,
1332 _: RequestContext,
1333 request: Payload,
1334 _: CodecFormat,
1335 ) -> crate::dispatcher::UnaryResult {
1336 let captured = Arc::clone(&self.0);
1337 Box::pin(async move {
1338 let m: StringValue = request.take_message()?;
1339 *captured.lock().unwrap() = Some(m.value);
1340 Ok(EncodedResponse::new(Bytes::new().into()))
1341 })
1342 }
1343 fn call_server_streaming(
1344 &self,
1345 _: &str,
1346 _: RequestContext,
1347 _: Bytes,
1348 _: CodecFormat,
1349 ) -> crate::dispatcher::StreamingResult {
1350 unreachable!()
1351 }
1352 fn call_client_streaming(
1353 &self,
1354 _: &str,
1355 _: RequestContext,
1356 _: crate::dispatcher::RequestStream,
1357 _: CodecFormat,
1358 ) -> crate::dispatcher::UnaryResult {
1359 unreachable!()
1360 }
1361 fn call_bidi_streaming(
1362 &self,
1363 _: &str,
1364 _: RequestContext,
1365 _: crate::dispatcher::RequestStream,
1366 _: CodecFormat,
1367 ) -> crate::dispatcher::StreamingResult {
1368 unreachable!()
1369 }
1370 }
1371
1372 struct Replace;
1375 #[async_trait::async_trait]
1376 impl Interceptor for Replace {
1377 async fn intercept_unary(
1378 &self,
1379 mut req: UnaryRequest,
1380 next: Next<'_>,
1381 ) -> Result<UnaryResponse, ConnectError> {
1382 req.payload.set_message(StringValue {
1383 value: "from interceptor".into(),
1384 ..Default::default()
1385 });
1386 next.run(req).await
1387 }
1388 }
1389
1390 let chain: Vec<Arc<dyn Interceptor>> = vec![Arc::new(Replace)];
1391 call_unary_intercepted(
1392 &Capture(Arc::clone(&captured)),
1393 &chain,
1394 "p",
1395 RequestContext::default(),
1396 Bytes::from_static(&[0xff, 0xff, 0xff]),
1398 CodecFormat::Proto,
1399 )
1400 .await
1401 .unwrap();
1402
1403 assert_eq!(
1404 captured.lock().unwrap().as_deref(),
1405 Some("from interceptor"),
1406 "the dispatcher must see the interceptor's replacement, not re-decode the wire bytes"
1407 );
1408 }
1409
1410 fn payload_stream(values: &[&'static str]) -> PayloadStream {
1416 let items: Vec<Result<Payload, ConnectError>> = values
1417 .iter()
1418 .map(|v| {
1419 let bytes = encode_proto(&StringValue {
1420 value: (*v).into(),
1421 ..Default::default()
1422 })
1423 .unwrap();
1424 Ok(Payload::new(bytes, CodecFormat::Proto))
1425 })
1426 .collect();
1427 Box::pin(futures::stream::iter(items))
1428 }
1429
1430 async fn collect_strings(stream: PayloadStream) -> Vec<String> {
1432 stream
1433 .map(|item| {
1434 item.unwrap()
1435 .message::<StringValue>()
1436 .unwrap()
1437 .value
1438 .clone()
1439 })
1440 .collect()
1441 .await
1442 }
1443
1444 struct StreamTagger(&'static str);
1447
1448 #[async_trait::async_trait]
1449 impl Interceptor for StreamTagger {
1450 async fn intercept_streaming(
1451 &self,
1452 mut req: StreamRequest,
1453 inbound: PayloadStream,
1454 next: NextStream<'_>,
1455 ) -> Result<StreamResponse, ConnectError> {
1456 req.ctx
1457 .extensions
1458 .get_or_insert_default::<Trace>()
1459 .0
1460 .lock()
1461 .unwrap()
1462 .push(self.0);
1463 let resp = next.run(req, inbound).await?;
1464 Ok(resp.with_header("x-trace", format!("{}-out", self.0)))
1465 }
1466 }
1467
1468 struct RecordingStreamTerminal {
1471 ran: Mutex<bool>,
1472 respond_with: Vec<&'static str>,
1473 }
1474
1475 #[async_trait::async_trait]
1476 impl StreamTerminal for RecordingStreamTerminal {
1477 async fn call(
1478 &self,
1479 _req: StreamRequest,
1480 inbound: PayloadStream,
1481 ) -> Result<StreamResponse, ConnectError> {
1482 *self.ran.lock().unwrap() = true;
1483 let inbound_values = collect_strings(inbound).await;
1484 let body: PayloadStream = payload_stream(&self.respond_with);
1485 let resp = Response {
1486 body,
1487 headers: http::HeaderMap::new(),
1488 trailers: http::HeaderMap::new(),
1489 compress: None,
1490 };
1491 Ok(resp.with_header("x-inbound", inbound_values.join(",")))
1492 }
1493 }
1494
1495 fn stream_req() -> StreamRequest {
1496 StreamRequest::new(RequestContext::default())
1497 }
1498
1499 #[tokio::test]
1500 async fn streaming_ordering_first_registered_is_outermost() {
1501 let trace = Trace::default();
1502 let chain: Vec<Arc<dyn Interceptor>> = vec![
1503 Arc::new(StreamTagger("a")),
1504 Arc::new(StreamTagger("b")),
1505 Arc::new(StreamTagger("c")),
1506 ];
1507 let terminal = RecordingStreamTerminal {
1508 ran: Mutex::new(false),
1509 respond_with: vec!["ok"],
1510 };
1511 let mut request = stream_req();
1512 request.ctx.extensions.insert(trace.clone());
1513 let resp = NextStream::new(&chain, &terminal)
1514 .run(request, payload_stream(&["x"]))
1515 .await
1516 .unwrap();
1517 assert!(*terminal.ran.lock().unwrap(), "terminal should have run");
1518 assert_eq!(*trace.0.lock().unwrap(), vec!["a", "b", "c"]);
1520 let outs: Vec<_> = resp
1522 .headers
1523 .get_all("x-trace")
1524 .iter()
1525 .map(|v| v.to_str().unwrap().to_owned())
1526 .collect();
1527 assert_eq!(outs, vec!["c-out", "b-out", "a-out"]);
1528 }
1529
1530 #[tokio::test]
1531 async fn streaming_short_circuit_skips_terminal() {
1532 struct Reject;
1533 #[async_trait::async_trait]
1534 impl Interceptor for Reject {
1535 async fn intercept_streaming(
1536 &self,
1537 _req: StreamRequest,
1538 _inbound: PayloadStream,
1539 _next: NextStream<'_>,
1540 ) -> Result<StreamResponse, ConnectError> {
1541 let mut headers = http::HeaderMap::new();
1542 headers.insert("x-deny-policy", "p1".parse().unwrap());
1543 Err(ConnectError::permission_denied("nope").with_headers(headers))
1544 }
1545 }
1546 let chain: Vec<Arc<dyn Interceptor>> =
1547 vec![Arc::new(Reject), Arc::new(StreamTagger("never"))];
1548 let terminal = RecordingStreamTerminal {
1549 ran: Mutex::new(false),
1550 respond_with: vec!["ok"],
1551 };
1552 let err = match NextStream::new(&chain, &terminal)
1553 .run(stream_req(), payload_stream(&["x"]))
1554 .await
1555 {
1556 Ok(_) => panic!("expected error"),
1557 Err(e) => e,
1558 };
1559 assert_eq!(err.code, crate::ErrorCode::PermissionDenied);
1560 assert!(!*terminal.ran.lock().unwrap(), "terminal must not run");
1561 assert_eq!(
1565 err.response_headers().get("x-deny-policy").unwrap(),
1566 "p1",
1567 "diagnostic headers must survive a streaming short-circuit"
1568 );
1569 }
1570
1571 #[tokio::test]
1572 async fn streaming_passthrough_preserves_items_and_metadata() {
1573 struct Passthrough;
1574 #[async_trait::async_trait]
1575 impl Interceptor for Passthrough {}
1576 let chain: Vec<Arc<dyn Interceptor>> = vec![Arc::new(Passthrough)];
1577 let resp = run_chain_streaming(
1578 &chain,
1579 stream_req(),
1580 payload_stream(&["a", "b"]),
1581 |_req, inbound| async move {
1582 let inbound_values = collect_strings(inbound).await;
1583 let body: PayloadStream = payload_stream(&["x", "y", "z"]);
1584 let mut r = Response {
1585 body,
1586 headers: http::HeaderMap::new(),
1587 trailers: http::HeaderMap::new(),
1588 compress: Some(true),
1589 };
1590 r.headers.insert("x-h", "1".parse().unwrap());
1591 r.trailers.insert("x-t", "2".parse().unwrap());
1592 r.headers
1593 .insert("x-inbound", inbound_values.join(",").parse().unwrap());
1594 Ok(r)
1595 },
1596 )
1597 .await
1598 .unwrap();
1599 assert_eq!(resp.headers.get("x-h").unwrap(), "1");
1600 assert_eq!(resp.trailers.get("x-t").unwrap(), "2");
1601 assert_eq!(resp.compress, Some(true));
1602 assert_eq!(resp.headers.get("x-inbound").unwrap(), "a,b");
1603 let out = collect_strings(resp.body).await;
1604 assert_eq!(out, vec!["x", "y", "z"]);
1605 }
1606
1607 #[tokio::test]
1608 async fn streaming_interceptor_wraps_inbound() {
1609 struct RedactInbound;
1611 #[async_trait::async_trait]
1612 impl Interceptor for RedactInbound {
1613 async fn intercept_streaming(
1614 &self,
1615 req: StreamRequest,
1616 inbound: PayloadStream,
1617 next: NextStream<'_>,
1618 ) -> Result<StreamResponse, ConnectError> {
1619 let wrapped: PayloadStream = Box::pin(inbound.map(|item| {
1620 item.map(|mut payload| {
1621 payload.set_message(StringValue {
1622 value: "redacted".into(),
1623 ..Default::default()
1624 });
1625 payload
1626 })
1627 }));
1628 next.run(req, wrapped).await
1629 }
1630 }
1631 let chain: Vec<Arc<dyn Interceptor>> = vec![Arc::new(RedactInbound)];
1632 let resp = run_chain_streaming(
1633 &chain,
1634 stream_req(),
1635 payload_stream(&["secret-a", "secret-b"]),
1636 |_req, inbound| async move {
1637 let inbound_values = collect_strings(inbound).await;
1638 let body: PayloadStream = payload_stream(&[]);
1639 let resp = Response {
1640 body,
1641 headers: http::HeaderMap::new(),
1642 trailers: http::HeaderMap::new(),
1643 compress: None,
1644 };
1645 Ok(resp.with_header("x-inbound", inbound_values.join(",")))
1646 },
1647 )
1648 .await
1649 .unwrap();
1650 assert_eq!(resp.headers.get("x-inbound").unwrap(), "redacted,redacted");
1652 }
1653
1654 #[tokio::test]
1655 async fn streaming_interceptor_wraps_outbound() {
1656 struct RedactOutbound;
1658 #[async_trait::async_trait]
1659 impl Interceptor for RedactOutbound {
1660 async fn intercept_streaming(
1661 &self,
1662 req: StreamRequest,
1663 inbound: PayloadStream,
1664 next: NextStream<'_>,
1665 ) -> Result<StreamResponse, ConnectError> {
1666 let resp = next.run(req, inbound).await?;
1667 Ok(resp.map_body(|stream| -> PayloadStream {
1668 Box::pin(stream.map(|item| {
1669 item.map(|mut payload| {
1670 payload.set_message(StringValue {
1671 value: "redacted".into(),
1672 ..Default::default()
1673 });
1674 payload
1675 })
1676 }))
1677 }))
1678 }
1679 }
1680 let chain: Vec<Arc<dyn Interceptor>> = vec![Arc::new(RedactOutbound)];
1681 let terminal = RecordingStreamTerminal {
1682 ran: Mutex::new(false),
1683 respond_with: vec!["secret-1", "secret-2"],
1684 };
1685 let resp = NextStream::new(&chain, &terminal)
1686 .run(stream_req(), payload_stream(&["x"]))
1687 .await
1688 .unwrap();
1689 let out = collect_strings(resp.body).await;
1690 assert_eq!(out, vec!["redacted", "redacted"]);
1691 }
1692
1693 #[tokio::test]
1694 async fn streaming_closure_interceptor_works() {
1695 let i = streaming_interceptor(|req, inbound, next| {
1696 Box::pin(async move {
1697 let resp = next.run(req, inbound).await?;
1698 Ok(resp.with_header("x-fn", "1"))
1699 })
1700 });
1701 let chain: Vec<Arc<dyn Interceptor>> = vec![Arc::new(i)];
1702 let resp = run_chain_streaming(
1703 &chain,
1704 stream_req(),
1705 payload_stream(&[]),
1706 |_req, _in| async {
1707 let body: PayloadStream = payload_stream(&[]);
1708 Ok(Response {
1709 body,
1710 headers: http::HeaderMap::new(),
1711 trailers: http::HeaderMap::new(),
1712 compress: None,
1713 })
1714 },
1715 )
1716 .await
1717 .unwrap();
1718 assert_eq!(resp.headers.get("x-fn").unwrap(), "1");
1719 }
1720
1721 struct StreamEcho;
1724 impl crate::Dispatcher for StreamEcho {
1725 fn lookup(&self, _: &str) -> Option<crate::dispatcher::MethodDescriptor> {
1726 None
1727 }
1728 fn call_unary(
1729 &self,
1730 _: &str,
1731 _: RequestContext,
1732 _: Payload,
1733 _: CodecFormat,
1734 ) -> crate::dispatcher::UnaryResult {
1735 unimplemented!()
1736 }
1737 fn call_server_streaming(
1738 &self,
1739 _: &str,
1740 _: RequestContext,
1741 request: Bytes,
1742 _: CodecFormat,
1743 ) -> crate::dispatcher::StreamingResult {
1744 Box::pin(async move {
1745 let body: BoxStream<Result<Bytes, ConnectError>> =
1746 Box::pin(futures::stream::once(async move { Ok(request) }));
1747 Ok(Response {
1748 body,
1749 headers: http::HeaderMap::new(),
1750 trailers: http::HeaderMap::new(),
1751 compress: None,
1752 })
1753 })
1754 }
1755 fn call_client_streaming(
1756 &self,
1757 _: &str,
1758 _: RequestContext,
1759 requests: crate::dispatcher::RequestStream,
1760 _: CodecFormat,
1761 ) -> crate::dispatcher::UnaryResult {
1762 Box::pin(async move {
1763 let mut total = 0usize;
1764 let mut requests = requests;
1765 while let Some(item) = requests.next().await {
1766 total += item?.len();
1767 }
1768 Ok(EncodedResponse::new(Bytes::from(total.to_string()).into()))
1769 })
1770 }
1771 fn call_bidi_streaming(
1772 &self,
1773 _: &str,
1774 _: RequestContext,
1775 requests: crate::dispatcher::RequestStream,
1776 _: CodecFormat,
1777 ) -> crate::dispatcher::StreamingResult {
1778 Box::pin(async move {
1779 Ok(Response {
1780 body: requests,
1781 headers: http::HeaderMap::new(),
1782 trailers: http::HeaderMap::new(),
1783 compress: None,
1784 })
1785 })
1786 }
1787 }
1788
1789 #[tokio::test]
1794 async fn streaming_empty_chain_is_no_op() {
1795 let body = Bytes::from_static(b"x");
1797 let resp = call_server_streaming_intercepted(
1798 &StreamEcho,
1799 &[],
1800 "p",
1801 RequestContext::default(),
1802 body.clone(),
1803 CodecFormat::Proto,
1804 )
1805 .await
1806 .unwrap();
1807 let out: Vec<_> = resp.body.collect().await;
1808 assert_eq!(out.len(), 1);
1809 assert!(std::ptr::eq(
1810 out[0].as_ref().unwrap().as_ptr(),
1811 body.as_ptr()
1812 ));
1813
1814 let inbound: RequestStream = Box::pin(futures::stream::iter(vec![
1816 Ok(Bytes::from_static(b"ab")),
1817 Ok(Bytes::from_static(b"cd")),
1818 ]));
1819 let resp = call_client_streaming_intercepted(
1820 &StreamEcho,
1821 &[],
1822 "p",
1823 RequestContext::default(),
1824 inbound,
1825 CodecFormat::Proto,
1826 )
1827 .await
1828 .unwrap();
1829 assert_eq!(&*resp.body.into_contiguous(), b"4");
1830
1831 let body = Bytes::from_static(b"z");
1833 let inbound: RequestStream = Box::pin(futures::stream::once({
1834 let body = body.clone();
1835 async move { Ok(body) }
1836 }));
1837 let resp = call_bidi_streaming_intercepted(
1838 &StreamEcho,
1839 &[],
1840 "p",
1841 RequestContext::default(),
1842 inbound,
1843 CodecFormat::Proto,
1844 )
1845 .await
1846 .unwrap();
1847 let out: Vec<_> = resp.body.collect().await;
1848 assert_eq!(out.len(), 1);
1849 assert!(std::ptr::eq(
1850 out[0].as_ref().unwrap().as_ptr(),
1851 body.as_ptr()
1852 ));
1853 }
1854
1855 #[tokio::test]
1859 async fn call_streaming_intercepted_propagates_error_headers() {
1860 struct Reject;
1861 #[async_trait::async_trait]
1862 impl Interceptor for Reject {
1863 async fn intercept_streaming(
1864 &self,
1865 _req: StreamRequest,
1866 _inbound: PayloadStream,
1867 _next: NextStream<'_>,
1868 ) -> Result<StreamResponse, ConnectError> {
1869 let mut headers = http::HeaderMap::new();
1870 headers.insert("x-deny-policy", "p1".parse().unwrap());
1871 Err(ConnectError::permission_denied("nope").with_headers(headers))
1872 }
1873 }
1874 struct PanickyDispatcher;
1875 impl crate::Dispatcher for PanickyDispatcher {
1876 fn lookup(&self, _: &str) -> Option<crate::dispatcher::MethodDescriptor> {
1877 None
1878 }
1879 fn call_unary(
1880 &self,
1881 _: &str,
1882 _: RequestContext,
1883 _: Payload,
1884 _: CodecFormat,
1885 ) -> crate::dispatcher::UnaryResult {
1886 unreachable!()
1887 }
1888 fn call_server_streaming(
1889 &self,
1890 _: &str,
1891 _: RequestContext,
1892 _: Bytes,
1893 _: CodecFormat,
1894 ) -> crate::dispatcher::StreamingResult {
1895 unreachable!("dispatcher must not run when an interceptor short-circuits")
1896 }
1897 fn call_client_streaming(
1898 &self,
1899 _: &str,
1900 _: RequestContext,
1901 _: crate::dispatcher::RequestStream,
1902 _: CodecFormat,
1903 ) -> crate::dispatcher::UnaryResult {
1904 unreachable!("dispatcher must not run when an interceptor short-circuits")
1905 }
1906 fn call_bidi_streaming(
1907 &self,
1908 _: &str,
1909 _: RequestContext,
1910 _: crate::dispatcher::RequestStream,
1911 _: CodecFormat,
1912 ) -> crate::dispatcher::StreamingResult {
1913 unreachable!("dispatcher must not run when an interceptor short-circuits")
1914 }
1915 }
1916 let chain: Vec<Arc<dyn Interceptor>> = vec![Arc::new(Reject)];
1917
1918 let err = match call_server_streaming_intercepted(
1919 &PanickyDispatcher,
1920 &chain,
1921 "p",
1922 RequestContext::default(),
1923 Bytes::new(),
1924 CodecFormat::Proto,
1925 )
1926 .await
1927 {
1928 Ok(_) => panic!("expected error"),
1929 Err(e) => e,
1930 };
1931 assert_eq!(err.code, crate::ErrorCode::PermissionDenied);
1932 assert_eq!(err.response_headers().get("x-deny-policy").unwrap(), "p1");
1933
1934 let err = call_client_streaming_intercepted(
1935 &PanickyDispatcher,
1936 &chain,
1937 "p",
1938 RequestContext::default(),
1939 Box::pin(futures::stream::empty()),
1940 CodecFormat::Proto,
1941 )
1942 .await
1943 .unwrap_err();
1944 assert_eq!(err.code, crate::ErrorCode::PermissionDenied);
1945
1946 let err = match call_bidi_streaming_intercepted(
1947 &PanickyDispatcher,
1948 &chain,
1949 "p",
1950 RequestContext::default(),
1951 Box::pin(futures::stream::empty()),
1952 CodecFormat::Proto,
1953 )
1954 .await
1955 {
1956 Ok(_) => panic!("expected error"),
1957 Err(e) => e,
1958 };
1959 assert_eq!(err.code, crate::ErrorCode::PermissionDenied);
1960 }
1961
1962 #[tokio::test]
1966 async fn streaming_intercepted_un_unifies_through_passthrough_chain() {
1967 struct Passthrough;
1968 #[async_trait::async_trait]
1969 impl Interceptor for Passthrough {}
1970 let chain: Vec<Arc<dyn Interceptor>> = vec![Arc::new(Passthrough)];
1971
1972 let body = Bytes::from_static(b"ss");
1975 let resp = call_server_streaming_intercepted(
1976 &StreamEcho,
1977 &chain,
1978 "p",
1979 RequestContext::default(),
1980 body.clone(),
1981 CodecFormat::Proto,
1982 )
1983 .await
1984 .unwrap();
1985 let out: Vec<_> = resp.body.collect().await;
1986 assert_eq!(out.len(), 1);
1987 assert_eq!(out[0].as_ref().unwrap(), &body);
1988
1989 let inbound: RequestStream = Box::pin(futures::stream::iter(vec![
1992 Ok(Bytes::from_static(b"abc")),
1993 Ok(Bytes::from_static(b"de")),
1994 ]));
1995 let resp = call_client_streaming_intercepted(
1996 &StreamEcho,
1997 &chain,
1998 "p",
1999 RequestContext::default(),
2000 inbound,
2001 CodecFormat::Proto,
2002 )
2003 .await
2004 .unwrap();
2005 assert_eq!(&*resp.body.into_contiguous(), b"5");
2006
2007 let inbound: RequestStream = Box::pin(futures::stream::iter(vec![
2009 Ok(Bytes::from_static(b"1")),
2010 Ok(Bytes::from_static(b"2")),
2011 ]));
2012 let resp = call_bidi_streaming_intercepted(
2013 &StreamEcho,
2014 &chain,
2015 "p",
2016 RequestContext::default(),
2017 inbound,
2018 CodecFormat::Proto,
2019 )
2020 .await
2021 .unwrap();
2022 let out: Vec<_> = resp.body.collect().await;
2023 assert_eq!(out.len(), 2);
2024 assert_eq!(out[0].as_ref().unwrap(), &Bytes::from_static(b"1"));
2025 assert_eq!(out[1].as_ref().unwrap(), &Bytes::from_static(b"2"));
2026 }
2027}