Skip to main content

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}