aws_smithy_http_server/schema/protocol/
registry.rs1use aws_smithy_types::Document;
35
36use crate::config::ServiceConfig;
37use crate::schema::routing::RouterBuildError;
38use crate::schema::ServiceSchema;
39
40use super::{BodyRoutedProtocol, MetadataRoutedProtocol, SharedServerProtocol};
41
42#[derive(Clone, Copy, Debug, PartialEq, Eq)]
46#[non_exhaustive]
47pub enum ProtocolOrder {
48 Before(&'static str),
50 After(&'static str),
52}
53
54#[non_exhaustive]
58#[derive(Debug)]
59pub struct ProtocolBuildContext<'a> {
60 pub service: &'static ServiceSchema<'static>,
62 pub config: &'a ServiceConfig,
64 pub settings: Option<&'a Document>,
66}
67
68impl<'a> ProtocolBuildContext<'a> {
69 pub fn new(service: &'static ServiceSchema<'static>) -> Self {
71 static DEFAULT_CONFIG: std::sync::LazyLock<ServiceConfig> = std::sync::LazyLock::new(ServiceConfig::default);
72 Self {
73 service,
74 config: &DEFAULT_CONFIG,
75 settings: None,
76 }
77 }
78
79 pub fn with_config(mut self, config: &'a ServiceConfig) -> Self {
81 self.config = config;
82 self
83 }
84
85 pub fn with_settings(mut self, settings: Option<&'a Document>) -> Self {
87 self.settings = settings;
88 self
89 }
90}
91
92pub type ProtocolFactory = fn(&ProtocolBuildContext<'_>) -> Result<SharedServerProtocol, RouterBuildError>;
96
97fn build_metadata_routed<P: MetadataRoutedProtocol>(
98 ctx: &ProtocolBuildContext<'_>,
99) -> Result<SharedServerProtocol, RouterBuildError> {
100 Ok(SharedServerProtocol::metadata_routed(P::from_build_context(ctx)?))
101}
102
103fn build_body_routed<P: BodyRoutedProtocol>(
104 ctx: &ProtocolBuildContext<'_>,
105) -> Result<SharedServerProtocol, RouterBuildError> {
106 Ok(SharedServerProtocol::body_routed(P::from_build_context(ctx)?))
107}
108
109#[derive(Clone, Copy)]
114pub struct ProtocolRegistration {
115 protocol_id: &'static str,
116 build: ProtocolFactory,
117 order: &'static [ProtocolOrder],
118}
119
120impl ProtocolRegistration {
121 pub const fn metadata_routed<P: MetadataRoutedProtocol>(protocol_id: &'static str) -> Self {
126 Self {
127 protocol_id,
128 build: build_metadata_routed::<P>,
129 order: &[],
130 }
131 }
132
133 pub const fn body_routed<P: BodyRoutedProtocol>(protocol_id: &'static str) -> Self {
140 Self {
141 protocol_id,
142 build: build_body_routed::<P>,
143 order: &[],
144 }
145 }
146
147 pub const fn with_order(mut self, order: &'static [ProtocolOrder]) -> Self {
149 self.order = order;
150 self
151 }
152
153 pub(crate) fn protocol_id(&self) -> &'static str {
154 self.protocol_id
155 }
156
157 pub(crate) fn order(&self) -> &'static [ProtocolOrder] {
158 self.order
159 }
160
161 pub(crate) fn build(&self, ctx: &ProtocolBuildContext<'_>) -> Result<SharedServerProtocol, RouterBuildError> {
162 let protocol = (self.build)(ctx)?;
163 if protocol.protocol_id().as_str() != self.protocol_id {
164 return Err(RouterBuildError::Configuration(format!(
165 "protocol registered as `{}` answers `{}` from `protocol_id()`",
166 self.protocol_id,
167 protocol.protocol_id()
168 )));
169 }
170 Ok(protocol)
171 }
172}
173
174impl std::fmt::Debug for ProtocolRegistration {
175 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
176 f.debug_struct("ProtocolRegistration")
177 .field("protocol_id", &self.protocol_id)
178 .finish_non_exhaustive()
179 }
180}
181
182#[derive(Clone, Copy, Debug)]
194pub struct ProtocolRegistry {
195 registrations: &'static [ProtocolRegistration],
196}
197
198impl ProtocolRegistry {
199 pub const BUILTIN: ProtocolRegistry = ProtocolRegistry::new(&[
229 ProtocolRegistration::metadata_routed::<crate::schema::protocol::RpcV2Cbor>("smithy.protocols#rpcv2Cbor"),
230 ProtocolRegistration::metadata_routed::<crate::schema::protocol::AwsJson1_0>("aws.protocols#awsJson1_0")
231 .with_order(&[ProtocolOrder::After("smithy.protocols#rpcv2Cbor")]),
232 ProtocolRegistration::metadata_routed::<crate::schema::protocol::AwsJson1_1>("aws.protocols#awsJson1_1")
233 .with_order(&[ProtocolOrder::After("aws.protocols#awsJson1_0")]),
234 ProtocolRegistration::metadata_routed::<crate::schema::protocol::RestJson1>("aws.protocols#restJson1")
235 .with_order(&[ProtocolOrder::After("aws.protocols#awsJson1_1")]),
236 ProtocolRegistration::metadata_routed::<crate::schema::protocol::RestXml>("aws.protocols#restXml")
237 .with_order(&[ProtocolOrder::After("aws.protocols#restJson1")]),
238 ]);
239
240 pub const fn new(registrations: &'static [ProtocolRegistration]) -> Self {
242 Self { registrations }
243 }
244
245 pub(crate) fn registrations(&self) -> &'static [ProtocolRegistration] {
246 self.registrations
247 }
248
249 pub fn resolve_id(
255 &self,
256 service_schema: &'static ServiceSchema<'static>,
257 protocol_id: &str,
258 ) -> Result<Option<SharedServerProtocol>, RouterBuildError> {
259 self.resolve_id_with_settings(service_schema, protocol_id, None)
260 }
261
262 pub fn resolve_id_with_settings(
268 &self,
269 service_schema: &'static ServiceSchema<'static>,
270 protocol_id: &str,
271 settings: Option<&Document>,
272 ) -> Result<Option<SharedServerProtocol>, RouterBuildError> {
273 let declared = service_schema
274 .protocols()
275 .iter()
276 .any(|protocol| protocol.as_str() == protocol_id);
277 if !declared {
278 return Ok(None);
279 }
280 self.registrations
281 .iter()
282 .find(|registration| registration.protocol_id() == protocol_id)
283 .map(|registration| registration.build(&ProtocolBuildContext::new(service_schema).with_settings(settings)))
284 .transpose()
285 }
286}
287
288#[cfg(test)]
289mod tests {
290 use crate::schema::ServiceSchema;
291 use aws_smithy_schema::{shape_id, ShapeId};
292
293 use super::*;
294
295 const SERVICE_ID: ShapeId<'static> = shape_id!("example", "Service");
296
297 static REST_JSON_1: [ShapeId<'static>; 1] = [shape_id!("aws.protocols", "restJson1")];
298 static UNKNOWN: [ShapeId<'static>; 1] = [shape_id!("example.protocols", "myProtocol")];
299
300 static REST_JSON_1_SERVICE: ServiceSchema<'static> = ServiceSchema::new(SERVICE_ID, None, &REST_JSON_1, &[]);
301 static UNKNOWN_SERVICE: ServiceSchema<'static> = ServiceSchema::new(SERVICE_ID, None, &UNKNOWN, &[]);
302
303 #[test]
304 fn builtin_resolves_each_builtin_protocol() {
305 static PROTOCOLS: [[ShapeId<'static>; 1]; 5] = [
306 [shape_id!("aws.protocols", "restJson1")],
307 [shape_id!("aws.protocols", "restXml")],
308 [shape_id!("aws.protocols", "awsJson1_0")],
309 [shape_id!("aws.protocols", "awsJson1_1")],
310 [shape_id!("smithy.protocols", "rpcv2Cbor")],
311 ];
312 static SERVICES: [ServiceSchema<'static>; 5] = [
313 ServiceSchema::new(SERVICE_ID, None, &PROTOCOLS[0], &[]),
314 ServiceSchema::new(SERVICE_ID, None, &PROTOCOLS[1], &[]),
315 ServiceSchema::new(SERVICE_ID, None, &PROTOCOLS[2], &[]),
316 ServiceSchema::new(SERVICE_ID, None, &PROTOCOLS[3], &[]),
317 ServiceSchema::new(SERVICE_ID, None, &PROTOCOLS[4], &[]),
318 ];
319 for (service, id) in SERVICES.iter().zip([
320 "aws.protocols#restJson1",
321 "aws.protocols#restXml",
322 "aws.protocols#awsJson1_0",
323 "aws.protocols#awsJson1_1",
324 "smithy.protocols#rpcv2Cbor",
325 ]) {
326 let protocol = ProtocolRegistry::BUILTIN
327 .resolve_id(service, id)
328 .expect("factory succeeds")
329 .expect(id);
330 assert_eq!(protocol.protocol_id().as_str(), id);
331 }
332 }
333
334 #[test]
335 fn unknown_protocol_resolves_to_none() {
336 assert!(ProtocolRegistry::BUILTIN
337 .resolve_id(&UNKNOWN_SERVICE, "example.protocols#myProtocol")
338 .expect("unavailable protocol is not a build error")
339 .is_none());
340 }
341
342 #[test]
343 fn undeclared_protocol_resolves_to_none() {
344 assert!(ProtocolRegistry::BUILTIN
345 .resolve_id(&REST_JSON_1_SERVICE, "aws.protocols#restXml")
346 .expect("unavailable protocol is not a build error")
347 .is_none());
348 }
349
350 #[test]
351 fn resolution_passes_settings_to_the_selected_factory() {
352 static AWS_JSON_SERVICE: ServiceSchema<'static> =
353 ServiceSchema::new(SERVICE_ID, None, &[shape_id!("aws.protocols", "awsJson1_0")], &[]);
354 let settings = crate::schema::settings::parse_settings_json(br#"{"responseBindings":"invalid"}"#);
355 let err = ProtocolRegistry::BUILTIN
356 .resolve_id_with_settings(&AWS_JSON_SERVICE, "aws.protocols#awsJson1_0", Some(&settings))
357 .expect_err("AWS JSON must validate the supplied settings");
358 assert!(matches!(err, RouterBuildError::Configuration(message) if message.contains("responseBindings")));
359 }
360
361 #[test]
362 fn a_mismatched_protocol_id_is_an_error() {
363 static REGISTRY: ProtocolRegistry = ProtocolRegistry::new(&[ProtocolRegistration::metadata_routed::<
364 crate::schema::protocol::RestXml,
365 >("aws.protocols#restJson1")]);
366 let err = REGISTRY
367 .resolve_id(&REST_JSON_1_SERVICE, "aws.protocols#restJson1")
368 .expect_err("IDs disagree");
369 assert!(matches!(err, RouterBuildError::Configuration(_)));
370 }
371
372 #[test]
373 fn factory_failure_is_preserved() {
374 static REGISTRY: ProtocolRegistry = ProtocolRegistry::new(&[ProtocolRegistration {
375 protocol_id: "aws.protocols#restJson1",
376 build: |_| Err(RouterBuildError::Configuration("factory failed".into())),
377 order: &[],
378 }]);
379 let err = REGISTRY
380 .resolve_id(&REST_JSON_1_SERVICE, "aws.protocols#restJson1")
381 .unwrap_err();
382 assert!(matches!(err, RouterBuildError::Configuration(message) if message == "factory failed"));
383 }
384}