1use 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#[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 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 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 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 pub fn registries(mut self, registries: impl IntoIterator<Item = &'static ProtocolRegistry>) -> Self {
76 self.registries = registries.into_iter().collect();
77 self
78 }
79
80 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 pub fn options(mut self, options: ProtocolOptions) -> Self {
91 self.options = options;
92 self
93 }
94
95 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 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 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 let non_streaming: Vec<_> = targets
162 .iter()
163 .filter(|target| !target.has_streaming_input() && !target.has_streaming_output())
164 .copied()
165 .collect();
166 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 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
211fn 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 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 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 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 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, ®istrations);
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 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 = ®istrations[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
345fn 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 assert!(matches!(
380 MultiProtocolRoutingServiceBuilder::from_operation_handler_bindings(
381 &SERVICE,
382 [registry()],
383 [binding(&FIRST)]
384 )
385 .build(),
386 Err(RouterBuildError::Binding(_))
387 ));
388 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 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 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 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 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(©), 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 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 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}