Skip to main content

aws_smithy_http_server/schema/routing/
builder.rs

1/*
2 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 * SPDX-License-Identifier: Apache-2.0
4 */
5
6//! Configuration and construction of the multi-protocol routing service.
7
8use super::service::{BoundHandler, ProtocolAndRouter, RoutingState};
9use super::{
10    MultiProtocolRoutingService, OperationTarget, ProtocolOptions, ProtocolResolutionError, RouterBuildContext,
11    RouterBuildError, SharedProtocolRouter,
12};
13use crate::{
14    body::BoxBody,
15    routing::SyncRoute,
16    schema::{
17        OperationSchema, ProtocolBuildContext, ProtocolOrder, ProtocolRegistration, ProtocolRegistry, ServiceSchema,
18        SharedServerProtocol,
19    },
20};
21use http::{Request, Response};
22use std::collections::HashMap;
23use std::{collections::HashSet, convert::Infallible, sync::Arc};
24use tower::{layer::util::Identity, Service};
25
26/// Collects routing inputs, validating and constructing them only in [`Self::build`].
27///
28/// The service schema is required. Built-in protocols are always available; additional
29/// registries contribute protocols implemented outside this crate.
30#[derive(Debug)]
31pub struct MultiProtocolRoutingServiceBuilder<L = Identity> {
32    service: &'static ServiceSchema<'static>,
33    registries: Vec<&'static ProtocolRegistry>,
34    bindings: Vec<(&'static OperationSchema<'static>, SyncRoute<crate::body::RequestBody>)>,
35    options: ProtocolOptions,
36    layer: L,
37}
38
39impl MultiProtocolRoutingServiceBuilder {
40    /// Starts configuring routing for the supplied schema.
41    pub fn new(service: &'static ServiceSchema<'static>) -> Self {
42        Self {
43            service,
44            registries: Vec::new(),
45            bindings: Vec::new(),
46            options: ProtocolOptions::default(),
47            layer: Identity::new(),
48        }
49    }
50
51    /// Collects operation bindings and external protocol registries with default options.
52    pub fn from_operation_handler_bindings(
53        service: &'static ServiceSchema<'static>,
54        registries: impl IntoIterator<Item = &'static ProtocolRegistry>,
55        bindings: impl IntoIterator<Item = (&'static OperationSchema<'static>, SyncRoute<crate::body::RequestBody>)>,
56    ) -> Self {
57        Self::new(service)
58            .registries(registries)
59            .operation_handler_bindings(bindings)
60    }
61
62    /// Collects operation bindings, external registries, and protocol options.
63    pub fn from_operation_handler_bindings_with_options(
64        service: &'static ServiceSchema<'static>,
65        registries: impl IntoIterator<Item = &'static ProtocolRegistry>,
66        bindings: impl IntoIterator<Item = (&'static OperationSchema<'static>, SyncRoute<crate::body::RequestBody>)>,
67        options: ProtocolOptions,
68    ) -> Self {
69        Self::from_operation_handler_bindings(service, registries, bindings).options(options)
70    }
71}
72
73impl<L> MultiProtocolRoutingServiceBuilder<L> {
74    /// Replaces the external registries. [`ProtocolRegistry::BUILTIN`] is always consulted.
75    pub fn registries(mut self, registries: impl IntoIterator<Item = &'static ProtocolRegistry>) -> Self {
76        self.registries = registries.into_iter().collect();
77        self
78    }
79
80    /// Replaces the protocol-independent `(operation schema, handler route)` bindings.
81    pub fn operation_handler_bindings(
82        mut self,
83        bindings: impl IntoIterator<Item = (&'static OperationSchema<'static>, SyncRoute<crate::body::RequestBody>)>,
84    ) -> Self {
85        self.bindings = bindings.into_iter().collect();
86        self
87    }
88
89    /// Replaces the protocol options.
90    pub fn options(mut self, options: ProtocolOptions) -> Self {
91        self.options = options;
92        self
93    }
94
95    /// Sets the handler layer, replacing any previously configured layer.
96    ///
97    /// The layer is applied during construction. It runs after routing; inspect the selection
98    /// through [`crate::schema::SelectedOperation::get_from_request`]. Forwarded requests must
99    /// preserve their extensions. Discarding them causes the upgrade or missing-handler fallback
100    /// to return an empty `500` without reading input or invoking the handler. Routing may already
101    /// have collected the input before the layer runs. Compose multiple layers with a Tower layer stack.
102    pub fn layer<N>(self, layer: N) -> MultiProtocolRoutingServiceBuilder<N> {
103        MultiProtocolRoutingServiceBuilder {
104            service: self.service,
105            registries: self.registries,
106            bindings: self.bindings,
107            options: self.options,
108            layer,
109        }
110    }
111
112    /// Validates the configuration and builds protocol routers and layered handlers.
113    pub fn build(self) -> Result<MultiProtocolRoutingService, RouterBuildError>
114    where
115        L: tower::Layer<SyncRoute<crate::body::RequestBody>>,
116        L::Service: Service<Request<crate::body::RequestBody>, Response = Response<BoxBody>, Error = Infallible>
117            + Clone
118            + Send
119            + Sync
120            + 'static,
121        <L::Service as Service<Request<crate::body::RequestBody>>>::Future: Send + 'static,
122    {
123        let Self {
124            service,
125            registries,
126            bindings,
127            options,
128            layer,
129        } = self;
130        let resolved = resolve_protocols(service, registries, &options)?;
131
132        // The operaiton must be listed in the Service shape.
133        let mut seen = HashSet::new();
134        for (operation, _) in &bindings {
135            let id = operation.shape_id().as_str();
136            if !seen.insert(id) || !service.operations().iter().any(|op| std::ptr::eq(*op, *operation)) {
137                return Err(RouterBuildError::Binding(id.to_owned()));
138            }
139        }
140        for operation in service.operations() {
141            if !seen.contains(operation.shape_id().as_str()) {
142                return Err(RouterBuildError::Binding(format!("missing {}", operation.shape_id())));
143            }
144        }
145        if seen.len() != service.operations().len() {
146            return Err(RouterBuildError::Binding("duplicate service operation schemas".into()));
147        }
148        for id in options.service_config.request_body.per_operation.keys() {
149            if !seen.contains(id.as_str()) {
150                return Err(RouterBuildError::Configuration(format!("unknown operation {id}")));
151            }
152        }
153        let targets: Vec<_> = bindings
154            .iter()
155            .enumerate()
156            .map(|(index, (operation, _))| OperationTarget::new(index, operation))
157            .collect();
158        // A body-routed protocol may buffer the body to select, so it never sees a streaming
159        // operation. Recognized streaming inputs skip claims that require body bytes;
160        // claims already made from the head retain their priority.
161        let non_streaming: Vec<_> = targets
162            .iter()
163            .filter(|target| !target.has_streaming_input() && !target.has_streaming_output())
164            .copied()
165            .collect();
166        // `resolved` is already in claim order; build each protocol's router in place. The
167        // protocol's kind picks its target set inside `build_router`: a body-routed protocol
168        // gets only the non-streaming operations.
169        let mut protocols = Vec::with_capacity(resolved.len());
170        for protocol in resolved {
171            let router = protocol.build_router(
172                RouterBuildContext {
173                    service,
174                    targets: &targets,
175                    config: &options.service_config,
176                    protocol_settings: options.protocol_settings.get(protocol.protocol_id().as_str()),
177                },
178                &non_streaming,
179            )?;
180            protocols.push(ProtocolAndRouter { router, protocol });
181        }
182        // Whether recognition is needed comes from the service schema. Each metadata
183        // router owns recognition of the streaming operations its protocol supports.
184        let has_streaming_inputs = targets.iter().any(|target| target.has_streaming_input());
185        let metadata_routers = has_streaming_inputs.then(|| {
186            protocols
187                .iter()
188                .enumerate()
189                .filter_map(|(index, route)| matches!(route.router, SharedProtocolRouter::Metadata(_)).then_some(index))
190                .collect::<Box<[_]>>()
191        });
192        let bindings = bindings
193            .into_iter()
194            .map(|(operation, route)| BoundHandler {
195                operation,
196                collection_config: options.service_config.request_body.for_operation(operation.shape_id()),
197                handler_route: SyncRoute::new(layer.layer(route)),
198            })
199            .collect();
200        Ok(MultiProtocolRoutingService {
201            state: Arc::new(RoutingState {
202                protocols: protocols.into(),
203                metadata_routers,
204                handlers: bindings,
205                body_collection_config: options.service_config.request_body.for_routing(),
206            }),
207        })
208    }
209}
210
211/// Resolves the registered protocols the service declares, in claim order.
212///
213/// The order comes from [`ProtocolOrder`] constraints alone, resolved over the **global** set of
214/// registered protocols: a constraint against a protocol the service does not serve still orders
215/// the ones it does, transitively. Registry and declaration order carry no meaning. Errors:
216/// a declared protocol without a registration, a protocol registered twice, a constraint naming
217/// an unregistered protocol, a constraint cycle, or two served protocols left unordered.
218fn resolve_protocols(
219    service: &'static ServiceSchema<'static>,
220    registries: impl IntoIterator<Item = &'static ProtocolRegistry>,
221    options: &ProtocolOptions,
222) -> Result<Vec<SharedServerProtocol>, RouterBuildError> {
223    let mut registrations = Vec::new();
224    let mut positions: HashMap<&str, usize> = HashMap::new();
225
226    let registered_protocols = ProtocolRegistry::BUILTIN.registrations().iter().chain(
227        registries
228            .into_iter()
229            .flat_map(|registry| registry.registrations().iter()),
230    );
231
232    for p in registered_protocols {
233        if positions.insert(p.protocol_id(), registrations.len()).is_some() {
234            return Err(ProtocolResolutionError::DuplicateRegistration {
235                protocol: p.protocol_id().to_string(),
236            }
237            .into());
238        }
239
240        registrations.push(p);
241    }
242
243    // Validate all declared protocols are registered.
244    let missing: Vec<String> = service
245        .protocols()
246        .iter()
247        .filter(|protocol| !positions.contains_key(protocol.as_str()))
248        .map(|protocol| protocol.to_string())
249        .collect();
250    if !missing.is_empty() {
251        return Err(ProtocolResolutionError::MissingRegistrations { protocols: missing }.into());
252    }
253
254    // Reachability over the global constraint graph, absent protocols included as transit nodes.
255
256    let protocol_position = |id: &str, registration: &ProtocolRegistration| {
257        positions.get(id).copied().ok_or_else(|| {
258            RouterBuildError::Configuration(format!(
259                "protocol {} orders against unregistered protocol {id}",
260                registration.protocol_id()
261            ))
262        })
263    };
264
265    let count = registrations.len();
266
267    let mut reaches = vec![vec![false; count]; count];
268    for (index, registration) in registrations.iter().enumerate() {
269        for constraint in registration.order() {
270            let (from, to) = match *constraint {
271                ProtocolOrder::Before(id) => (index, protocol_position(id, registration)?),
272                ProtocolOrder::After(id) => (protocol_position(id, registration)?, index),
273            };
274
275            reaches[from][to] = true
276        }
277    }
278
279    // Set transitive edges:
280    // A -> B
281    // B -> C
282    // A -> C
283    for via in 0..count {
284        let (before, via_and_after) = reaches.split_at_mut(via);
285        let (via_row, after) = via_and_after.split_first_mut().expect("via is a valid row index");
286        // Combining the via row with itself leaves it unchanged.
287        for row in before.iter_mut().chain(after.iter_mut()) {
288            if row[via] {
289                for (reachable, via_reachable) in row.iter_mut().zip(via_row.iter()) {
290                    *reachable |= *via_reachable;
291                }
292            }
293        }
294    }
295
296    let cyclic_protocols: Vec<String> = (0..count)
297        .filter(|&index| reaches[index][index])
298        .map(|index| registrations[index].protocol_id().to_string())
299        .collect();
300    if !cyclic_protocols.is_empty() {
301        return Err(ProtocolResolutionError::OrderCycle {
302            protocols: cyclic_protocols,
303        }
304        .into());
305    }
306
307    let mut served = service_protocols(service, &registrations);
308    if served.is_empty() {
309        return Err(ProtocolResolutionError::NoProtocolsDeclared.into());
310    }
311
312    for (nth, &first) in served.iter().enumerate() {
313        for &second in &served[nth + 1..] {
314            if !reaches[first][second] && !reaches[second][first] {
315                return Err(ProtocolResolutionError::AmbiguousOrder {
316                    first: registrations[first].protocol_id().to_string(),
317                    second: registrations[second].protocol_id().to_string(),
318                }
319                .into());
320            }
321        }
322    }
323
324    // Every served pair is comparable and the graph is acyclic, so reachability totally orders them.
325    served.sort_by(|&first, &second| {
326        if reaches[first][second] {
327            std::cmp::Ordering::Less
328        } else {
329            std::cmp::Ordering::Greater
330        }
331    });
332
333    served
334        .into_iter()
335        .map(|index| {
336            let registration = &registrations[index];
337            let context = ProtocolBuildContext::new(service)
338                .with_config(&options.service_config)
339                .with_settings(options.protocol_settings.get(registration.protocol_id()));
340            registration.build(&context)
341        })
342        .collect()
343}
344
345/// Returns protocol indices that the service uses.
346fn service_protocols(
347    service: &'static ServiceSchema<'static>,
348    registrations: &Vec<&ProtocolRegistration>,
349) -> Vec<usize> {
350    registrations
351        .iter()
352        .enumerate()
353        .filter(|(_, registration)| {
354            service
355                .protocols()
356                .iter()
357                .any(|protocol| protocol.as_str() == registration.protocol_id())
358        })
359        .map(|(index, _)| index)
360        .collect()
361}
362
363#[cfg(test)]
364mod tests {
365    use super::super::test_helpers::*;
366    use super::super::MultiProtocolRoutingService;
367    use super::*;
368    use crate::response::Response;
369    use crate::schema::SelectedOperation;
370    use aws_smithy_schema::shape_id;
371    use bytes::Bytes;
372    use http::HeaderValue;
373    use http_body_util::BodyExt;
374    use tower::ServiceExt;
375
376    #[test]
377    fn binding_and_protocol_validation() {
378        // SERVICE declares FIRST and SECOND; omitting SECOND's handler must fail the build.
379        assert!(matches!(
380            MultiProtocolRoutingServiceBuilder::from_operation_handler_bindings(
381                &SERVICE,
382                [registry()],
383                [binding(&FIRST)]
384            )
385            .build(),
386            Err(RouterBuildError::Binding(_))
387        ));
388        // Registering FIRST's handler twice must fail the build.
389        assert!(matches!(
390            MultiProtocolRoutingServiceBuilder::from_operation_handler_bindings(
391                &SERVICE,
392                [registry()],
393                [binding(&FIRST), binding(&FIRST)]
394            )
395            .build(),
396            Err(RouterBuildError::Binding(_))
397        ));
398        // Both handlers are present, but SERVICE's custom protocol has no registration.
399        assert!(matches!(
400            MultiProtocolRoutingServiceBuilder::from_operation_handler_bindings(&SERVICE, [], [binding(&FIRST), binding(&SECOND)]).build(),
401            Err(RouterBuildError::ProtocolResolution(ProtocolResolutionError::MissingRegistrations { protocols })) if protocols == ["test#bodyRouting"]
402        ));
403        static NONE: ServiceSchema<'static> = ServiceSchema::new(SERVICE_ID, None, &[], OPERATIONS);
404        static MANY: ServiceSchema<'static> = ServiceSchema::new(
405            SERVICE_ID,
406            None,
407            &[
408                shape_id!("aws.protocols", "restJson1"),
409                shape_id!("aws.protocols", "restXml"),
410            ],
411            OPERATIONS,
412        );
413        // A service must declare at least one protocol, even when all handlers are present.
414        assert!(matches!(
415            MultiProtocolRoutingServiceBuilder::from_operation_handler_bindings(
416                &NONE,
417                [],
418                [binding(&FIRST), binding(&SECOND)]
419            )
420            .build(),
421            Err(RouterBuildError::ProtocolResolution(
422                ProtocolResolutionError::NoProtocolsDeclared
423            ))
424        ));
425        // Both declared built-in protocols must be included in the constructed service.
426        let many = MultiProtocolRoutingServiceBuilder::from_operation_handler_bindings(
427            &MANY,
428            [],
429            [binding(&FIRST), binding(&SECOND)],
430        )
431        .build()
432        .expect("every declared protocol is served");
433        assert_eq!(many.state.protocols.len(), 2);
434        // Matching FIRST's shape ID is insufficient: the binding must reference the
435        // exact operation schema declared by SERVICE.
436        static COPY: OperationSchema<'static> = OperationSchema::new(FIRST_ID, &FIRST_INPUT, &UNIT, &[]);
437        assert!(matches!(
438            MultiProtocolRoutingServiceBuilder::from_operation_handler_bindings(
439                &SERVICE,
440                [registry()],
441                [binding(&COPY), binding(&SECOND)]
442            )
443            .build(),
444            Err(RouterBuildError::Binding(_))
445        ));
446    }
447
448    #[test]
449    fn partially_registered_service_reports_every_missing_protocol_before_building() {
450        static PARTIAL: ServiceSchema<'static> = ServiceSchema::new(
451            SERVICE_ID,
452            None,
453            &[
454                shape_id!("aws.protocols", "restJson1"),
455                shape_id!("test", "unregisteredFirst"),
456                shape_id!("test", "unregisteredSecond"),
457            ],
458            OPERATIONS,
459        );
460        // Missing registrations fail even before the missing operation bindings are checked.
461        let error = MultiProtocolRoutingServiceBuilder::from_operation_handler_bindings(&PARTIAL, [], [])
462            .build()
463            .unwrap_err();
464        assert_eq!(
465            error.to_string(),
466            "missing protocol registrations: test#unregisteredFirst, test#unregisteredSecond"
467        );
468        let source = std::error::Error::source(&error).expect("protocol resolution retains its typed error source");
469        assert!(matches!(
470            source.downcast_ref::<ProtocolResolutionError>(),
471            Some(ProtocolResolutionError::MissingRegistrations { protocols })
472                if protocols == &["test#unregisteredFirst", "test#unregisteredSecond"]
473        ));
474        assert!(matches!(
475            error,
476            RouterBuildError::ProtocolResolution(ProtocolResolutionError::MissingRegistrations { protocols })
477                if protocols == ["test#unregisteredFirst", "test#unregisteredSecond"]
478        ));
479    }
480
481    #[test]
482    fn invalid_protocol_settings_fail_the_build() {
483        for settings in [
484            r#"{"capitalizeRoutes":"yes"}"#,
485            r#"{"methodNotAllowedAsNotFound":"yes"}"#,
486            r#"{"methodNotAllowedAsNotFound":null}"#,
487            r#""not an object""#,
488        ] {
489            let options = ProtocolOptions {
490                protocol_settings: HashMap::from([(
491                    shape_id!("smithy.protocols", "rpcv2Cbor"),
492                    crate::schema::settings::parse_settings_json(settings.as_bytes()),
493                )]),
494                ..Default::default()
495            };
496            assert!(matches!(
497                MultiProtocolRoutingServiceBuilder::from_operation_handler_bindings_with_options(
498                    &RPC,
499                    [],
500                    [binding(&SECOND), binding(&FIRST)],
501                    options,
502                )
503                .build(),
504                Err(RouterBuildError::Configuration(_))
505            ));
506        }
507    }
508
509    #[test]
510    fn builtins_follow_their_chained_constraints_whatever_the_declaration_order() {
511        assert_eq!(
512            priority(&app(&BUILTINS, [])),
513            [
514                "smithy.protocols#rpcv2Cbor",
515                "aws.protocols#awsJson1_0",
516                "aws.protocols#awsJson1_1",
517                "aws.protocols#restJson1",
518                "aws.protocols#restXml",
519            ]
520        );
521    }
522
523    #[test]
524    fn unordered_served_protocols_fail_the_build() {
525        let error = MultiProtocolRoutingServiceBuilder::from_operation_handler_bindings(
526            &WITH_BODY_ROUTING,
527            [body_routing(&[])],
528            OPS.iter().map(|operation| echo(operation)),
529        )
530        .build()
531        .unwrap_err();
532        assert!(
533            matches!(
534                error,
535                RouterBuildError::ProtocolResolution(ProtocolResolutionError::AmbiguousOrder { .. })
536            ),
537            "{error}"
538        );
539    }
540
541    #[test]
542    fn a_duplicate_protocol_fails_the_build() {
543        let error = MultiProtocolRoutingServiceBuilder::from_operation_handler_bindings(
544            &WITH_BODY_ROUTING,
545            [body_routing(&[]), body_routing(&[])],
546            OPS.iter().map(|operation| echo(operation)),
547        )
548        .build()
549        .unwrap_err();
550        assert!(
551            matches!(error, RouterBuildError::ProtocolResolution(ProtocolResolutionError::DuplicateRegistration { protocol }) if protocol == "test#bodyRouting"),
552        );
553    }
554
555    #[test]
556    fn a_constraint_against_an_unregistered_protocol_fails_the_build() {
557        static TYPO: &[ProtocolOrder] = &[ProtocolOrder::Before("aws.protocols#restJson2")];
558        let error = MultiProtocolRoutingServiceBuilder::from_operation_handler_bindings(
559            &WITH_BODY_ROUTING,
560            [body_routing(TYPO)],
561            OPS.iter().map(|operation| echo(operation)),
562        )
563        .build()
564        .unwrap_err();
565        assert!(matches!(error, RouterBuildError::Configuration(_)), "{error}");
566    }
567
568    #[test]
569    fn contradictory_ordering_fails_the_build() {
570        static CYCLE: &[ProtocolOrder] = &[
571            ProtocolOrder::Before("aws.protocols#restJson1"),
572            ProtocolOrder::After("aws.protocols#restJson1"),
573        ];
574        static ABSENT: &[ProtocolOrder] = &[ProtocolOrder::After("aws.protocols#restXml")];
575        let error = MultiProtocolRoutingServiceBuilder::from_operation_handler_bindings(
576            &WITH_BODY_ROUTING,
577            [body_routing(CYCLE)],
578            OPS.iter().map(|operation| echo(operation)),
579        )
580        .build()
581        .unwrap_err();
582        assert_eq!(
583            error.to_string(),
584            "protocol ordering constraints form a cycle involving: aws.protocols#restJson1, test#bodyRouting"
585        );
586        assert!(matches!(
587            error,
588            RouterBuildError::ProtocolResolution(ProtocolResolutionError::OrderCycle { protocols })
589                if protocols == ["aws.protocols#restJson1", "test#bodyRouting"]
590        ));
591        // A constraint against an unserved protocol still orders the served ones transitively:
592        // restJson1 comes before restXml (builtin chain) and restXml before bodyRouting, so
593        // restJson1 precedes bodyRouting even though restXml is not served.
594        assert_eq!(
595            priority(&app(&WITH_BODY_ROUTING, [body_routing(ABSENT)])),
596            ["aws.protocols#restJson1", "test#bodyRouting"]
597        );
598    }
599
600    #[test]
601    fn ordering_cycle_reports_transitive_members_including_unserved_protocols() {
602        static CYCLE: &[ProtocolOrder] = &[
603            ProtocolOrder::Before("aws.protocols#restJson1"),
604            ProtocolOrder::After("aws.protocols#restXml"),
605        ];
606        let error = MultiProtocolRoutingServiceBuilder::from_operation_handler_bindings(
607            &WITH_BODY_ROUTING,
608            [body_routing(CYCLE)],
609            OPS.iter().map(|operation| echo(operation)),
610        )
611        .build()
612        .unwrap_err();
613        assert!(matches!(
614            error,
615            RouterBuildError::ProtocolResolution(ProtocolResolutionError::OrderCycle { protocols })
616                if protocols == ["aws.protocols#restJson1", "aws.protocols#restXml", "test#bodyRouting"]
617        ));
618    }
619
620    #[test]
621    fn self_ordering_cycle_reports_only_the_cyclic_protocol() {
622        static CYCLE: &[ProtocolOrder] = &[ProtocolOrder::Before("test#bodyRouting")];
623        let error = MultiProtocolRoutingServiceBuilder::from_operation_handler_bindings(
624            &WITH_BODY_ROUTING,
625            [body_routing(CYCLE)],
626            OPS.iter().map(|operation| echo(operation)),
627        )
628        .build()
629        .unwrap_err();
630        assert!(matches!(
631            error,
632            RouterBuildError::ProtocolResolution(ProtocolResolutionError::OrderCycle { protocols }) if protocols == ["test#bodyRouting"]
633        ));
634    }
635
636    #[test]
637    fn builder_allows_configuration_to_be_replaced_before_validation() {
638        let builder = MultiProtocolRoutingService::builder(&SERVICE)
639            .registries([registry(), registry()])
640            .operation_handler_bindings([binding(&FIRST), binding(&FIRST)])
641            .options(
642                ProtocolOptions::default().with_request_body(crate::config::ServiceRequestBodyConfig {
643                    per_operation: [("test#unknown".to_owned(), config(1, 1))].into(),
644                    ..Default::default()
645                }),
646            );
647        let app = builder
648            .registries([registry()])
649            .operation_handler_bindings([binding(&FIRST), binding(&SECOND)])
650            .options(ProtocolOptions::default())
651            .build();
652        assert!(app.is_ok());
653        let invalid = MultiProtocolRoutingService::builder(&REST_JSON);
654        assert!(matches!(invalid.build(), Err(RouterBuildError::Binding(_))));
655    }
656
657    #[tokio::test]
658    async fn builder_supports_boxed_bodies_with_handler_layers() {
659        type Transport = http_body_util::Full<Bytes>;
660        for schema in [&REST_JSON, &SERVICE] {
661            let bindings = schema.operations().iter().map(|operation| {
662                (
663                    *operation,
664                    SyncRoute::new(tower::service_fn(
665                        |request: Request<crate::body::RequestBody>| async move {
666                            assert!(SelectedOperation::get_from_request(&request).is_some());
667                            Ok::<_, Infallible>(Response::new(crate::body::boxed(request.into_body())))
668                        },
669                    )),
670                )
671            });
672            let app = MultiProtocolRoutingService::builder(schema)
673                .registries([registry()])
674                .operation_handler_bindings(bindings)
675                .layer(tower::util::MapResponseLayer::new(|mut response: Response<BoxBody>| {
676                    response
677                        .headers_mut()
678                        .insert("x-layer", HeaderValue::from_static("applied"));
679                    response
680                }))
681                .build()
682                .unwrap();
683            let response = app
684                .oneshot(
685                    Request::builder()
686                        .method("POST")
687                        .uri("/first")
688                        .header("content-type", "application/json")
689                        .body(Transport::new(Bytes::from_static(b"first\npayload")))
690                        .unwrap(),
691                )
692                .await
693                .unwrap();
694            assert_eq!(response.headers()["x-layer"], "applied");
695            assert_eq!(
696                response.into_body().collect().await.unwrap().to_bytes(),
697                "first\npayload"
698            );
699        }
700    }
701}