aws_smithy_http_server/schema/routing/protocol_router.rs
1/*
2 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 * SPDX-License-Identifier: Apache-2.0
4 */
5
6//! Protocol router interfaces, route claims, and supporting construction types.
7
8use crate::config::ServiceConfig;
9use crate::schema::routing::RoutingError;
10use crate::schema::{OperationSchema, ServiceSchema};
11use crate::{config::ServiceRequestBodyConfig, error::BoxError};
12use aws_smithy_schema::ShapeId;
13use aws_smithy_types::Document;
14use bytes::Bytes;
15use http::Request;
16
17use std::{collections::HashMap, fmt};
18
19/// The kind of streaming member in an operation's input or output.
20#[derive(Clone, Copy, Debug, PartialEq, Eq)]
21enum StreamingKind {
22 /// A streaming blob payload.
23 Blob,
24 /// A streaming union of events.
25 EventStream,
26}
27
28/// Protocol-independent facts derived once from an operation's canonical schema.
29#[derive(Clone, Copy, Debug)]
30struct OperationMetadata {
31 input_streaming: Option<StreamingKind>,
32 output_streaming: Option<StreamingKind>,
33}
34impl OperationMetadata {
35 fn new(operation: &OperationSchema<'_>) -> Self {
36 fn streaming_kind(schema: &aws_smithy_schema::Schema<'_>) -> Option<StreamingKind> {
37 schema.members().iter().find(|member| member.streaming()).map(|member| {
38 // Smithy streaming members are blobs or event-stream unions.
39 if member.shape_type() == aws_smithy_schema::ShapeType::Blob {
40 StreamingKind::Blob
41 } else {
42 StreamingKind::EventStream
43 }
44 })
45 }
46 Self {
47 input_streaming: streaming_kind(operation.input()),
48 output_streaming: streaming_kind(operation.output()),
49 }
50 }
51}
52
53/// A router's operation target: its canonical schema, cached metadata and handler position.
54///
55/// Targets are assigned by the routing service and passed to protocol routers at construction.
56#[derive(Clone, Copy, Debug)]
57pub struct OperationTarget {
58 index: usize,
59 operation: &'static OperationSchema<'static>,
60 metadata: OperationMetadata,
61}
62impl OperationTarget {
63 pub(super) fn new(index: usize, operation: &'static OperationSchema<'static>) -> Self {
64 Self {
65 index,
66 operation,
67 metadata: OperationMetadata::new(operation),
68 }
69 }
70
71 /// Returns the assigned handler position.
72 pub fn index(self) -> usize {
73 self.index
74 }
75 /// Returns the operation's canonical schema.
76 pub fn operation(self) -> &'static OperationSchema<'static> {
77 self.operation
78 }
79 /// Whether the operation consumes a streaming input.
80 pub fn has_streaming_input(self) -> bool {
81 self.metadata.input_streaming.is_some()
82 }
83 /// Whether the operation consumes a streaming blob, rather than an event stream.
84 pub fn has_streaming_blob_input(self) -> bool {
85 self.metadata.input_streaming == Some(StreamingKind::Blob)
86 }
87 /// Whether the operation consumes an event stream, rather than a streaming blob.
88 pub fn has_event_stream_input(self) -> bool {
89 self.metadata.input_streaming == Some(StreamingKind::EventStream)
90 }
91 /// Whether the operation produces a streaming output.
92 pub fn has_streaming_output(self) -> bool {
93 self.metadata.output_streaming.is_some()
94 }
95 /// Whether either the input or output contains a streaming blob.
96 pub fn has_streaming_blob(self) -> bool {
97 self.metadata.input_streaming == Some(StreamingKind::Blob)
98 || self.metadata.output_streaming == Some(StreamingKind::Blob)
99 }
100}
101
102/// Shared service configuration and protocol settings, independent of generated implementations.
103#[derive(Clone, Debug, Default)]
104#[non_exhaustive]
105pub struct ProtocolOptions {
106 /// Typed service-wide configuration shared by every protocol.
107 pub service_config: ServiceConfig,
108 /// Per-protocol settings, keyed by protocol shape ID such as
109 /// `smithy.protocols#rpcv2Cbor`. Each protocol parses its own section when
110 /// constructing itself or its router, rejecting invalid values with
111 /// [`RouterBuildError::Configuration`].
112 pub protocol_settings: HashMap<ShapeId<'static>, Document>,
113}
114
115impl ProtocolOptions {
116 /// Sets the service-wide configuration.
117 pub fn with_service_config(mut self, service_config: ServiceConfig) -> Self {
118 self.service_config = service_config;
119 self
120 }
121
122 /// Sets the request body collection configuration.
123 pub fn with_request_body(mut self, request_body: ServiceRequestBodyConfig) -> Self {
124 self.service_config.request_body = request_body;
125 self
126 }
127
128 /// Sets the per-protocol settings.
129 pub fn with_protocol_settings(mut self, protocol_settings: HashMap<ShapeId<'static>, Document>) -> Self {
130 self.protocol_settings = protocol_settings;
131 self
132 }
133}
134
135/// Everything a protocol sees when building its router: the service, the
136/// assigned targets, the shared service configuration, and the protocol's own
137/// settings section. [`Self::service_protocols`] lets a protocol detect conflicting
138/// combinations or adapt its claim behavior using the existing service schema.
139/// Constructed by the routing service builder, so a protocol never sees another
140/// protocol's settings.
141#[derive(Debug)]
142#[non_exhaustive]
143pub struct RouterBuildContext<'a> {
144 /// The service schema.
145 pub service: &'static ServiceSchema<'static>,
146 /// The operations to route, with targets assigned by the routing service.
147 pub targets: &'a [OperationTarget],
148 /// Typed service-wide configuration shared by every protocol.
149 pub config: &'a ServiceConfig,
150 /// This protocol's section of [`ProtocolOptions::protocol_settings`], when
151 /// one was configured.
152 pub protocol_settings: Option<&'a Document>,
153}
154
155impl RouterBuildContext<'_> {
156 /// Returns the protocols served by this service in schema order, not claim order.
157 ///
158 /// Includes this protocol, but excludes registered protocols the service does
159 /// not serve. The slice borrows the static service schema and can be retained
160 /// by the router without copying it or borrowing the build context.
161 pub fn service_protocols(&self) -> &'static [ShapeId<'static>] {
162 self.service.protocols()
163 }
164}
165
166/// Failure to bind handlers, resolve protocols, or construct service routing.
167#[derive(Debug, thiserror::Error)]
168#[non_exhaustive]
169pub enum RouterBuildError {
170 /// Invalid or missing operation handler bindings.
171 #[error("invalid operation binding: {0}")]
172 Binding(String),
173 /// Invalid service configuration, protocol settings, or schema bindings.
174 #[error("invalid routing configuration: {0}")]
175 Configuration(String),
176 /// Protocol registration or claim ordering could not be resolved.
177 #[error("{0}")]
178 ProtocolResolution(#[from] ProtocolResolutionError),
179 /// A protocol reported a failure while constructing itself or its router.
180 #[error("protocol or router construction failed: {0}")]
181 Protocol(#[source] BoxError),
182}
183
184/// Failure to resolve a service's protocol registrations or claim ordering.
185#[derive(Debug, thiserror::Error)]
186#[non_exhaustive]
187pub enum ProtocolResolutionError {
188 /// The service schema declares no protocols.
189 #[error("the service schema declares no protocols")]
190 NoProtocolsDeclared,
191 /// Declared service protocols without a runtime registration, in declaration order.
192 #[error("missing protocol registrations: {}", .protocols.join(", "))]
193 MissingRegistrations { protocols: Vec<String> },
194 /// The same protocol is registered more than once.
195 #[error("protocol {protocol} is registered more than once")]
196 DuplicateRegistration { protocol: String },
197 /// Registered protocols participating in ordering cycles, in registration order.
198 #[error("protocol ordering constraints form a cycle involving: {}", .protocols.join(", "))]
199 OrderCycle { protocols: Vec<String> },
200 /// Two served protocols have no ordering constraint relating them.
201 #[error("no ordering constraint relates protocols {first} and {second}; add a `ProtocolOrder` between them")]
202 AmbiguousOrder { first: String, second: String },
203}
204
205/// A protocol's answer to whether a request is its own.
206///
207/// A multi-protocol service asks its protocols in priority order and dispatches to the first that
208/// definitely claims the request. If none does, the first deferred claim owns it.
209/// Once selected, routing errors are terminal and framed by that protocol.
210#[derive(Debug)]
211#[non_exhaustive]
212pub enum RouteClaim {
213 /// The protocol claims the request and knows the operation. Dispatch directly.
214 ClaimedWithRoute(OperationTarget),
215 /// The protocol can handle the operation if no protocol makes a definite claim.
216 /// Continue arbitration; the first deferred claim wins over deferred rejections.
217 DeferredClaim(OperationTarget),
218 /// The protocol claims the request. Call [`MetadataProtocolRouter::route`] or
219 /// [`BodyProtocolRouter::route_with_body`] to select the operation or return a terminal
220 /// routing error. No other protocol is asked.
221 Claimed,
222 /// The request signals this protocol but does not meet its claiming requirements.
223 /// Continue asking other protocols. If none definitely or provisionally claims,
224 /// the first deferred rejection in precision order is serialized by its protocol.
225 DeferredRejection(RoutingError),
226 /// The protocol does not identify the request; the next protocol is asked.
227 NoClaim,
228}
229
230/// Selects an operation from request metadata alone.
231///
232/// The request carries no body: metadata routing never reads one, and keeping the trait
233/// body-free keeps it usable behind `dyn` for every transport body type.
234///
235/// Rejections are the standard [`RoutingError`], classified but not serialized: the routing
236/// service hands it to the rejecting protocol's
237/// [`serialize_routing_error`](crate::schema::ServerProtocol::serialize_routing_error), which
238/// owns the kind-to-wire mapping.
239pub trait MetadataProtocolRouter: Send + Sync + fmt::Debug {
240 /// Selects from the request URI, method and headers after [`RouteClaim::Claimed`].
241 /// All routing errors after a claim are terminal.
242 fn route(&self, request: &Request<()>) -> Result<OperationTarget, RoutingError>;
243
244 /// Decides whether the request meets this protocol's claiming requirements.
245 fn claim(&self, request: &Request<()>) -> RouteClaim;
246
247 /// Recognizes a potentially streaming input using only the request head. Output-only
248 /// streaming does not qualify. This must perform no body I/O or request-head mutation.
249 /// A match skips routers that need body bytes to claim; it neither claims nor rejects the request.
250 /// The service consults it only when its schema declares a streaming input. Routers
251 /// recognize streaming inputs belonging to their protocol, including inputs that routing
252 /// will reject as unsupported, so rejection does not require body collection. The default
253 /// recognizes none.
254 fn recognizes_streaming_input(&self, _request: &Request<()>) -> bool {
255 false
256 }
257}
258
259/// The complete request body collected by the routing service.
260///
261/// These are the raw wire bytes. The same bytes are replayed to the dispatched handler
262/// or the next protocol if the claim is declined.
263#[derive(Debug)]
264pub struct CollectedBody {
265 pub(super) bytes: Bytes,
266}
267
268impl CollectedBody {
269 /// The complete request body's raw wire bytes.
270 pub fn bytes(&self) -> &Bytes {
271 &self.bytes
272 }
273}
274
275/// A body-routed protocol's answer to whether a request is its own.
276///
277/// Distinct from [`RouteClaim`] so that needing the body stays unrepresentable for metadata
278/// protocols. Routing errors are returned by [`BodyProtocolRouter::route_with_body`] after
279/// the protocol claims the request.
280#[derive(Debug)]
281#[non_exhaustive]
282pub enum BodyRouteClaim {
283 /// The protocol claims the request and knows the operation. Dispatch directly without
284 /// calling [`BodyProtocolRouter::route_with_body`].
285 ClaimedWithRoute(OperationTarget),
286 /// Offer an operation without collecting the body. A later definite claim wins;
287 /// otherwise the first deferred claim in precision order is dispatched.
288 DeferredClaim(OperationTarget),
289 /// The protocol claims the request. The service collects the complete body,
290 /// then calls [`BodyProtocolRouter::route_with_body`].
291 /// No other protocol is asked, including when routing returns an error.
292 Claimed,
293 /// The protocol needs body bytes to decide whether the request is its own. The service
294 /// collects the complete body and calls [`BodyProtocolRouter::claim_with_body`].
295 NeedsBodyToClaim,
296 /// Defer a rejection from the head without collecting the body. A later claim wins;
297 /// otherwise a deferred claim wins, or the first deferred rejection supplies the response.
298 DeferredRejection(RoutingError),
299 /// The request is not this protocol's; the next protocol is asked.
300 NoClaim,
301}
302
303/// Routes operations using the request head and, when needed, the complete body.
304///
305/// [`claim`](Self::claim) checks the head first. After `Claimed` or `NeedsBodyToClaim`,
306/// the service collects the body under [`ServiceRequestBodyConfig::for_routing`]
307/// and preserves it for later routers and the handler. Decline from the head when
308/// possible to avoid waiting for a body the client may never send.
309///
310/// Body routers exclude streaming operations. If any metadata router recognizes
311/// streaming input, `NeedsBodyToClaim` is skipped; head claims retain priority.
312///
313/// Routing errors are terminal and serialized by the claiming protocol.
314/// Without a definite claim, the first deferred claim is dispatched. An unclaimed
315/// request uses the first deferred rejection, if any, or the
316/// service's protocol-neutral response, including for a single-protocol service.
317pub trait BodyProtocolRouter: Send + Sync + fmt::Debug {
318 /// Checks the request head, requesting body collection if needed.
319 fn claim(&self, request: &Request<()>) -> BodyRouteClaim;
320
321 /// Continues a [`BodyRouteClaim::NeedsBodyToClaim`] decision with the complete body.
322 /// Returning `Claimed` proceeds to [`Self::route_with_body`].
323 fn claim_with_body(&self, request: &Request<CollectedBody>) -> RouteClaim {
324 let _ = request;
325 RouteClaim::NoClaim
326 }
327
328 /// Selects an operation after [`BodyRouteClaim::Claimed`] or [`RouteClaim::Claimed`].
329 /// Errors are terminal and serialized by this protocol.
330 fn route_with_body(&self, request: &Request<CollectedBody>) -> Result<OperationTarget, RoutingError> {
331 let _ = request;
332 Err(RoutingError::unknown_operation())
333 }
334}
335
336/// Operation router built by a server protocol and owned by the shared routing state.
337///
338/// The variant is decided by the protocol's registration kind: a
339/// [`MetadataRoutedProtocol`](crate::schema::MetadataRoutedProtocol) can only build a
340/// [`Metadata`](Self::Metadata) router and a
341/// [`BodyRoutedProtocol`](crate::schema::BodyRoutedProtocol) a [`Body`](Self::Body) one,
342/// so dispatch matching on this enum speaks the claim protocol the registration promised.
343#[derive(Debug)]
344#[non_exhaustive]
345pub enum SharedProtocolRouter {
346 /// Selects operations from request metadata alone.
347 Metadata(Box<dyn MetadataProtocolRouter>),
348 /// May read collected body bytes to select operations.
349 Body(Box<dyn BodyProtocolRouter>),
350}
351
352impl SharedProtocolRouter {
353 /// Wraps a router that selects from request metadata alone.
354 pub fn new(router: impl MetadataProtocolRouter + 'static) -> Self {
355 Self::Metadata(Box::new(router))
356 }
357
358 /// Wraps a router that selects from the request body the routing service collects.
359 pub fn new_body_routed(router: impl BodyProtocolRouter + 'static) -> Self {
360 Self::Body(Box::new(router))
361 }
362}