Skip to main content

aws_smithy_http_server/schema/protocol/
mod.rs

1/*
2 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 * SPDX-License-Identifier: Apache-2.0
4 */
5
6//! Schema-driven [`ServerProtocol`] implementations and protocol registration.
7//!
8//! [`ProtocolRegistry::BUILTIN`] documents the built-in claim priority order, how custom
9//! protocols fit into it, and its effect on ambiguous requests.
10//!
11//! Each protocol frames modeled errors with its discriminator:
12//!
13//! | Protocol   | Discriminator                                            |
14//! |------------|----------------------------------------------------------|
15//! | restJson1  | `x-amzn-errortype` header, shape name only; none in body |
16//! | awsJson1.0 | `__type` body member, full `namespace#Name`, written last |
17//! | awsJson1.1 | `__type` body member, shape name only, written last      |
18//! | rpcv2Cbor  | `__type` body member, full `namespace#Name`, written first |
19//! | restXml    | none                                                     |
20//!
21//! `@httpHeader`-bound error members are split out of the body on the REST protocols.
22
23mod aws_json;
24// The `discriminator`, `response` and `rpc` modules are `#[doc(hidden)]` seams for
25// `ServerProtocol` implementations living outside this crate. They are not part of the crate's
26// stable API: no semver guarantee, subject to change with the in-tree protocols that share them.
27#[doc(hidden)]
28pub mod discriminator;
29mod registry;
30pub(crate) mod request;
31#[doc(hidden)]
32pub mod response;
33pub(crate) mod rest;
34mod rest_json_1;
35mod rest_xml;
36#[doc(hidden)]
37pub mod rpc;
38mod rpc_v2_cbor;
39mod rpc_v2_cbor_streaming;
40#[cfg(test)]
41mod tests;
42
43use std::num::NonZeroUsize;
44use std::time::Duration;
45
46use aws_smithy_runtime_api::http::Headers;
47use aws_smithy_schema::codec::DynCodec;
48use aws_smithy_schema::serde::{SerializableStruct, ShapeDeserializer};
49use aws_smithy_schema::{Schema, ShapeId};
50use bytes::Bytes;
51
52use crate::body::BoxBody;
53use crate::body::__private::{CollectBodyError, CollectBodyFuture, CollectionConfig, HttpBody};
54use crate::config::RequestBodyCollectionConfig;
55use crate::response::Response;
56use crate::schema::routing::RouterBuildError;
57use crate::schema::OperationSchema;
58
59pub use aws_json::{AwsJson1_0, AwsJson1_1};
60pub use rest_json_1::RestJson1;
61pub use rest_xml::RestXml;
62pub use rpc_v2_cbor::RpcV2Cbor;
63
64pub use registry::{ProtocolBuildContext, ProtocolFactory, ProtocolOrder, ProtocolRegistration, ProtocolRegistry};
65
66use super::{DeserializeError, HttpModeledError};
67
68/// Shared, erased server protocol selected by routing.
69///
70/// Wraps one of the routing kinds — [`metadata_routed`](Self::metadata_routed) or
71/// [`body_routed`](Self::body_routed) — or a routing-less serde handle
72/// ([`serde_only`](Self::serde_only)). The kind decides which
73/// [`SharedProtocolRouter`](crate::schema::routing::SharedProtocolRouter) variant
74/// is constructed and whether the protocol can answer the
75/// event-stream question; everything after routing works through the [`ServerProtocol`] this
76/// dereferences to. The kind stays private: the constructors' trait bounds are what guarantee
77/// each variant builds the matching router, so nothing else may assemble one.
78#[derive(Clone, Debug)]
79pub struct SharedServerProtocol(ProtocolKind);
80
81#[derive(Clone, Debug)]
82enum ProtocolKind {
83    /// Routes using request metadata alone and may support event-stream framing.
84    Metadata(std::sync::Arc<dyn ErasedMetadataRoutedProtocol>),
85    /// Routes non-streaming operations with access to collected request bodies when needed.
86    BodyCollected(std::sync::Arc<dyn ErasedBodyRoutedProtocol>),
87    /// Provides serialization and deserialization without routing or event-stream support.
88    SerdeOnly(std::sync::Arc<dyn ServerProtocol>),
89}
90
91impl SharedServerProtocol {
92    /// Borrows the protocol selected for this request from its extensions.
93    ///
94    /// Reads the protocol through [`SelectedOperation::get_from_request`](crate::schema::SelectedOperation::get_from_request).
95    /// Returns `None` if no selection is stored, including before schema routing runs.
96    pub fn get_from_request<B>(request: &http::Request<B>) -> Option<&Self> {
97        super::SelectedOperation::get_from_request(request).map(super::SelectedOperation::protocol)
98    }
99
100    /// Wraps a protocol that selects operations from request metadata alone.
101    pub fn metadata_routed(protocol: impl MetadataRoutedProtocol) -> Self {
102        Self(ProtocolKind::Metadata(std::sync::Arc::new(protocol)))
103    }
104
105    /// Wraps a protocol that reads the request body to select operations.
106    ///
107    /// A body-routed protocol cannot express event-stream framing — the subtrait has no such
108    /// method — so this handle always answers `None` from [`Self::event_stream_framing`].
109    pub fn body_routed(protocol: impl BodyRoutedProtocol) -> Self {
110        Self(ProtocolKind::BodyCollected(std::sync::Arc::new(protocol)))
111    }
112
113    /// Wraps a protocol for serialization only, without routing or event-stream support.
114    ///
115    /// For handles that never route: middleware serializing through a hand-built protocol, and
116    /// tests. Registration requires one of the routed constructors, so this handle is never
117    /// asked to build a router.
118    pub fn serde_only(protocol: impl ServerProtocol) -> Self {
119        Self(ProtocolKind::SerdeOnly(std::sync::Arc::new(protocol)))
120    }
121
122    /// Builds this protocol's operation router; see
123    /// [`MetadataRoutedProtocol::build_router`] and [`BodyRoutedProtocol::build_router`].
124    ///
125    /// `ctx.targets` carries every bound operation; `non_streaming` the subset without
126    /// streaming members. The kind picks here: a body-routed protocol may buffer the body to
127    /// select, so a streaming operation is never its to claim and its router is built without
128    /// them.
129    pub(crate) fn build_router<'a>(
130        &self,
131        mut ctx: crate::schema::routing::RouterBuildContext<'a>,
132        non_streaming: &'a [crate::schema::routing::OperationTarget],
133    ) -> Result<crate::schema::routing::SharedProtocolRouter, RouterBuildError> {
134        use crate::schema::routing::SharedProtocolRouter;
135        match &self.0 {
136            ProtocolKind::Metadata(protocol) => Ok(SharedProtocolRouter::Metadata(protocol.build_router(ctx)?)),
137            ProtocolKind::BodyCollected(protocol) => {
138                ctx.targets = non_streaming;
139                Ok(SharedProtocolRouter::Body(protocol.build_router(ctx)?))
140            }
141            ProtocolKind::SerdeOnly(protocol) => Err(RouterBuildError::Configuration(format!(
142                "protocol {} was wrapped without routing support",
143                protocol.protocol_id()
144            ))),
145        }
146    }
147
148    /// Event-frame support, when this protocol supports event streams.
149    ///
150    /// Only a metadata-routed protocol can answer: the [`BodyRoutedProtocol`] subtrait has no
151    /// framing method (an event-stream body must not be collected), and a serde-only handle
152    /// never routes.
153    pub fn event_stream_framing(&self) -> Option<EventStreamFraming<'_>> {
154        match &self.0 {
155            ProtocolKind::Metadata(protocol) => protocol.event_stream_framing(),
156            ProtocolKind::BodyCollected(_) | ProtocolKind::SerdeOnly(_) => None,
157        }
158    }
159}
160
161impl std::ops::Deref for SharedServerProtocol {
162    type Target = dyn ServerProtocol;
163
164    fn deref(&self) -> &Self::Target {
165        match &self.0 {
166            ProtocolKind::Metadata(protocol) => protocol.as_ref(),
167            ProtocolKind::BodyCollected(protocol) => protocol.as_ref(),
168            ProtocolKind::SerdeOnly(protocol) => protocol.as_ref(),
169        }
170    }
171}
172
173/// Object-safe facade over [`MetadataRoutedProtocol`], which cannot be a trait object itself:
174/// its `from_build_context` returns `Self` and its `build_router` returns
175/// `impl MetadataProtocolRouter`. Blanket-implemented for every metadata-routed protocol, so
176/// the one boxing step lives here and [`ProtocolKind::Metadata`] holds the protocol directly.
177trait ErasedMetadataRoutedProtocol: ServerProtocol {
178    fn build_router(
179        &self,
180        ctx: crate::schema::routing::RouterBuildContext<'_>,
181    ) -> Result<Box<dyn crate::schema::routing::MetadataProtocolRouter>, RouterBuildError>;
182    fn event_stream_framing(&self) -> Option<EventStreamFraming<'_>>;
183}
184
185impl<P: MetadataRoutedProtocol> ErasedMetadataRoutedProtocol for P {
186    fn build_router(
187        &self,
188        ctx: crate::schema::routing::RouterBuildContext<'_>,
189    ) -> Result<Box<dyn crate::schema::routing::MetadataProtocolRouter>, RouterBuildError> {
190        Ok(Box::new(MetadataRoutedProtocol::build_router(self, ctx)?))
191    }
192    fn event_stream_framing(&self) -> Option<EventStreamFraming<'_>> {
193        MetadataRoutedProtocol::event_stream_framing(self)
194    }
195}
196
197/// Object-safe facade over [`BodyRoutedProtocol`], exactly as [`ErasedMetadataRoutedProtocol`] is
198/// over [`MetadataRoutedProtocol`].
199trait ErasedBodyRoutedProtocol: ServerProtocol {
200    fn build_router(
201        &self,
202        ctx: crate::schema::routing::RouterBuildContext<'_>,
203    ) -> Result<Box<dyn crate::schema::routing::BodyProtocolRouter>, RouterBuildError>;
204}
205
206impl<P: BodyRoutedProtocol> ErasedBodyRoutedProtocol for P {
207    fn build_router(
208        &self,
209        ctx: crate::schema::routing::RouterBuildContext<'_>,
210    ) -> Result<Box<dyn crate::schema::routing::BodyProtocolRouter>, RouterBuildError> {
211        Ok(Box::new(BodyRoutedProtocol::build_router(self, ctx)?))
212    }
213}
214
215/// Whether request deserialization needs the body to be collected.
216#[derive(Debug, Clone, Copy, PartialEq, Eq)]
217#[non_exhaustive]
218pub enum BodyDirective {
219    /// Collect the body under the operation's limits (the default).
220    Collect,
221    /// Skip collection; the input never reads the body.
222    Skip,
223}
224
225/// The event-frame capability of a metadata-routed protocol, as plain data.
226///
227/// Answered by [`MetadataRoutedProtocol::event_stream_framing`]; a protocol that does not
228/// support event streams answers `None`. Body-routed protocols cannot express this at all:
229/// body-first routing collects the body before selecting an operation, and an event-stream
230/// body must not be collected.
231#[derive(Clone, Copy, Debug)]
232#[non_exhaustive]
233pub struct EventStreamFraming<'a> {
234    /// Codec for structured event and initial-message payloads.
235    pub payload_codec: &'a dyn DynCodec,
236    /// Media type of structured event payloads.
237    pub media_type: &'a str,
238    /// Whether non-stream members travel in initial-message frames.
239    ///
240    /// This controls their payload and initial-request handling, not whether an explicitly
241    /// requested initial-response frame is emitted.
242    pub initial_messages_in_frames: bool,
243    /// Initial-response payload when non-stream members travel in HTTP bindings instead.
244    ///
245    /// Used when [`initial_messages_in_frames`](Self::initial_messages_in_frames) is false.
246    /// Generated serde REST JSON sends `{}`; REST XML sends zero bytes.
247    pub unset_initial_response_payload: &'static [u8],
248    /// Optional body discriminator for modeled exception frames.
249    pub exception_discriminator: Option<discriminator::BodyDiscriminator>,
250    /// Whether HTTP-bound exception members are excluded from the frame payload.
251    pub exception_http_bindings: bool,
252    /// Codec for received exception frames that carry a REST XML error envelope, as generated serde
253    /// restXml servers expect. The codec reads the `<Error>` element without validating its
254    /// root name.
255    pub xml_exception_codec: Option<&'a dyn DynCodec>,
256    /// With [`xml_exception_codec`](Self::xml_exception_codec): whether the `<Error>` element is
257    /// wrapped in `<ErrorResponse>` (the default) or is the root (`@restXml(noErrorWrapping: true)`).
258    pub xml_exceptions_wrapped: bool,
259}
260
261impl<'a> EventStreamFraming<'a> {
262    /// Framing with the given payload codec and media type; non-stream members do not travel
263    /// in initial-message frames unless [`initial_messages_in_frames`](Self::initial_messages_in_frames)
264    /// is set.
265    pub fn new(payload_codec: &'a dyn DynCodec, media_type: &'a str) -> Self {
266        Self {
267            payload_codec,
268            media_type,
269            initial_messages_in_frames: false,
270            unset_initial_response_payload: b"",
271            exception_discriminator: None,
272            exception_http_bindings: false,
273            xml_exception_codec: None,
274            xml_exceptions_wrapped: true,
275        }
276    }
277
278    /// Reads received exception frames from a REST XML error envelope with `codec`, which must
279    /// not validate the root element name: `<ErrorResponse><Error>..</Error></ErrorResponse>`
280    /// when `wrapped`, otherwise `<Error>..</Error>`.
281    pub fn xml_exceptions(mut self, codec: &'a dyn DynCodec, wrapped: bool) -> Self {
282        self.xml_exception_codec = Some(codec);
283        self.xml_exceptions_wrapped = wrapped;
284        self
285    }
286
287    /// Sets the body discriminator used for modeled exception payloads.
288    pub fn exception_discriminator(mut self, discriminator: discriminator::BodyDiscriminator) -> Self {
289        self.exception_discriminator = Some(discriminator);
290        self
291    }
292
293    /// Applies REST error-body bindings to structured exception payloads.
294    pub fn exception_http_bindings(mut self, enabled: bool) -> Self {
295        self.exception_http_bindings = enabled;
296        self
297    }
298
299    /// Sets whether non-stream members travel in initial-message frames.
300    pub fn initial_messages_in_frames(mut self, initial_messages_in_frames: bool) -> Self {
301        self.initial_messages_in_frames = initial_messages_in_frames;
302        self
303    }
304
305    /// Sets the initial-response payload for protocols whose non-stream members use HTTP
306    /// bindings. Defaults to zero bytes; ignored when initial messages carry output members.
307    pub fn unset_initial_response_payload(mut self, payload: &'static [u8]) -> Self {
308        self.unset_initial_response_payload = payload;
309        self
310    }
311}
312
313/// Schema-driven serialization for one protocol, keyed on struct schemas.
314///
315/// The request side takes the operation's input schema, the response side its output schema,
316/// mirroring the client's `ClientProtocolInner` with the two directions swapped. The trait is
317/// object-safe: routing stores the protocol in a private request-extension carrier for
318/// [`SelectedOperation`](crate::schema::SelectedOperation). Everything after routing works
319/// through that erased handle, so nothing downstream names a concrete protocol.
320///
321/// A response plan is precomputed per operation: Bindings, Media types, URI templates
322/// and status codes are cached.
323///
324/// `Debug` is a supertrait, as on the client's protocol trait, so generated marshallers holding
325/// the handle can derive it.
326///
327/// # Serializing from middleware
328///
329/// After routing, an HTTP plugin can read the selected protocol from the request extensions and
330/// serialize any [`SerializableStruct`] whose schema it holds:
331///
332/// ```no_run
333/// use aws_smithy_http_server::schema::SharedServerProtocol;
334/// use aws_smithy_schema::serde::{SerdeError, SerializableStruct, ShapeSerializer};
335/// use aws_smithy_schema::{shape_id, Schema, ShapeType};
336///
337/// static MESSAGE: Schema<'static> =
338///     Schema::new_member(shape_id!("example", "Teapot", "message"), ShapeType::String, "message", 0);
339/// static MEMBERS: [&Schema<'static>; 1] = [&MESSAGE];
340/// static TEAPOT: Schema<'static> =
341///     Schema::new_struct(shape_id!("example", "Teapot"), ShapeType::Structure, &MEMBERS)
342///         .with_original_name("Teapot");
343///
344/// struct Teapot;
345///
346/// impl SerializableStruct for Teapot {
347///     fn schema(&self) -> &Schema<'_> { &TEAPOT }
348///
349///     fn serialize_members(&self, serializer: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
350///         serializer.write_string(&MESSAGE, "short and stout")
351///     }
352/// }
353///
354/// fn short_circuit(request: &http::Request<()>) -> Option<http::Response<aws_smithy_http_server::body::BoxBody>> {
355///     let protocol = SharedServerProtocol::get_from_request(request)?;
356///     let mut response = protocol.serialize_response(&TEAPOT, &Teapot);
357///     *response.status_mut() = http::StatusCode::IM_A_TEAPOT;
358///     Some(response)
359/// }
360/// ```
361pub trait ServerProtocol: Send + Sync + std::fmt::Debug + 'static {
362    /// The protocol trait's shape ID, such as `aws.protocols#restJson1`.
363    fn protocol_id(&self) -> &'static ShapeId<'static>;
364
365    /// Validates request headers for the selected operation before upgrade body collection
366    /// and [`Self::deserialize_request`]. Header failures therefore take precedence over
367    /// deserialization failures. The built-in protocols check `Accept` against the response
368    /// content type. The default accepts all headers.
369    fn validate_request_headers(
370        &self,
371        _operation: &OperationSchema<'_>,
372        _headers: &Headers,
373    ) -> Result<(), DeserializeError> {
374        Ok(())
375    }
376
377    /// Specifies whether deserialization needs the request body. The default collects it.
378    ///
379    /// Returning [`BodyDirective::Skip`] leaves the deserialization request's body empty.
380    /// Event-stream inputs bypass collection and receive the live body separately.
381    /// Streaming blob inputs honor `Collect`: the codec reads the blob and ordinary fields
382    /// before handler dispatch. With `Skip`, streaming glue receives the live body separately.
383    fn request_body_requirement(&self, _operation: &OperationSchema<'_>) -> BodyDirective {
384        BodyDirective::Collect
385    }
386
387    /// Presents `request` as a deserializer for `input`.
388    ///
389    /// The `Content-Type` check happens here; the returned deserializer resolves `@http` bindings
390    /// from the request and hands body members to its internal codec. Synchronous over an
391    /// already collected body. The request retains HTTP metadata, including extensions.
392    /// Its body is empty when collection is skipped or the input is an event stream;
393    /// streaming glue receives the live body separately. Collected streaming blobs are
394    /// decoded from the document and exposed to handlers as buffered byte streams.
395    fn deserialize_request<'a>(
396        &'a self,
397        input: &Schema<'_>,
398        request: &'a aws_smithy_runtime_api::http::Request<bytes::Bytes>,
399    ) -> Result<Box<dyn ShapeDeserializer + 'a>, DeserializeError>;
400
401    /// Serializes `value` as a complete in-memory response for `output`.
402    ///
403    /// The status is the `@httpResponseCode` member when bound and set, else the output schema's
404    /// `@http` code, else `200`; a caller wanting another status mutates the returned response. A
405    /// serialization failure is logged and answered with the protocol's `RuntimeError::Serialization`
406    /// response, so this never fails outward.
407    ///
408    /// Built-in protocols add the buffered body's byte count as `Content-Length` only when
409    /// no modeled value is present. Matching generated serde responses, modeled values take
410    /// precedence without length-specific validation or normalization.
411    fn serialize_response(&self, output: &Schema<'_>, value: &dyn SerializableStruct) -> Response;
412
413    /// Serializes a streaming response for `output` around a caller-supplied `body`.
414    ///
415    /// The protocol interprets `output` and `value` to produce the response head and any
416    /// ordinary body members. Generated glue builds `body` from the streaming blob or
417    /// preframed event stream. CBOR frames blob chunks after the ordinary serialized members.
418    ///
419    /// `None` means the modeled streaming member is absent. `Some(body)` means it is
420    /// present, even when the body yields no bytes. CBOR must distinguish these cases:
421    /// absence omits the blob member; a present empty blob encodes an explicit empty byte string.
422    /// REST and event-stream responses use an empty payload when the member is absent.
423    /// The body is passed separately because `serialize_members` omits streaming members
424    /// and the response must own their streams.
425    ///
426    /// Built-in protocols do not infer a length or collect the stream. Matching generated serde
427    /// responses, modeled `Content-Length` values are preserved without length-specific validation
428    /// or normalization.
429    fn serialize_streaming_response(
430        &self,
431        output: &Schema<'_>,
432        value: &dyn SerializableStruct,
433        streaming_member: &Schema<'_>,
434        body: Option<BoxBody>,
435    ) -> Response;
436
437    /// Serializes a modeled error with the protocol's discriminator framing.
438    fn serialize_error(&self, error: &dyn HttpModeledError) -> Response;
439
440    /// Frames a shared extractor runtime error using this protocol's runtime-error contract.
441    ///
442    /// Schema upgrades supply the protocol selected for the request. This is distinct from
443    /// modeled constraint validation and must preserve the protocol's existing runtime error
444    /// status, headers, body, and response extensions. External protocols implement this
445    /// method alongside [`Self::serialize_rejection`] to define their own framing.
446    fn serialize_runtime_error(&self, error: crate::runtime_error::RuntimeError) -> Response;
447
448    /// Converts a routing rejection into this protocol's response.
449    ///
450    /// The routing service classifies why routing failed
451    /// ([`RoutingError::kind`](crate::schema::routing::RoutingError::kind)); the protocol owns the
452    /// wire form. The default keeps the historical smithy-rs responses — the rejection is a
453    /// member-less modeled error (`UnknownOperationException` at `404`,
454    /// `MethodNotAllowedException` at `405`) framed by [`serialize_error`](Self::serialize_error).
455    /// A protocol whose clients expect different routing error responses overrides this;
456    /// returning a full [`Response`] permits any status, header — including
457    /// `Connection: close` — and body.
458    fn serialize_routing_error(&self, err: &crate::schema::routing::RoutingError) -> Response {
459        self.serialize_error(err)
460    }
461
462    /// Converts a rejected request into the protocol's response.
463    ///
464    /// Each protocol answers with its `RuntimeError` responses, quirks included, such as awsJson
465    /// and rpcv2Cbor collapsing `Accept` and `Content-Type` failures into a plain 400, and
466    /// [`DeserializeError::InternalFailure`] becoming the 500 internal-failure response. These
467    /// responses are the protocol's wire contract and must not change shape.
468    fn serialize_rejection(&self, err: DeserializeError) -> Response;
469}
470
471/// A [`ServerProtocol`] that selects operations from request metadata alone.
472///
473/// Registered with [`ProtocolRegistration::metadata_routed`]; the registry calls
474/// [`from_build_context`](Self::from_build_context) only for services whose schema declares the
475/// registered protocol trait, and the routing service calls
476/// [`build_router`](Self::build_router) once per service. Only a metadata-routed protocol may
477/// support event streams: routing never collects the body, so an event-stream body is never
478/// read before its operation is known.
479pub trait MetadataRoutedProtocol: ServerProtocol + Sized {
480    /// Builds this protocol for a service, from its registered configuration. Invalid
481    /// configuration fails the service build.
482    fn from_build_context(ctx: &ProtocolBuildContext<'_>) -> Result<Self, RouterBuildError>;
483
484    /// Builds operation routing once for this service.
485    ///
486    /// The context carries the protocol's own settings section next to the server-global
487    /// configuration; invalid settings are rejected with
488    /// [`RouterBuildError::Configuration`], failing the service build.
489    ///
490    /// `use<Self>` pins down the capture contract: the returned router owns its state and
491    /// borrows nothing from the protocol or the context, so a caller may build a router from
492    /// a temporary protocol. Implementations write `use<>` in their return type.
493    fn build_router(
494        &self,
495        ctx: crate::schema::routing::RouterBuildContext<'_>,
496    ) -> Result<impl crate::schema::routing::MetadataProtocolRouter + 'static + use<Self>, RouterBuildError>;
497
498    /// Event-frame support, when this protocol supports event streams.
499    fn event_stream_framing(&self) -> Option<EventStreamFraming<'_>> {
500        None
501    }
502}
503
504/// A [`ServerProtocol`] that reads the request body to select operations.
505///
506/// Registered with [`ProtocolRegistration::body_routed`]. Claiming is head-first; the router
507/// escalates to body bytes by returning a requirement the routing service satisfies — the
508/// service owns all body I/O, the protocol owns interpretation. The protocol is handed only
509/// the service's non-streaming operations — see
510/// [`BodyProtocolRouter`](crate::schema::routing::BodyProtocolRouter). Event-stream framing is not
511/// expressible here: the subtrait has no such method, so the conflict between body-based
512/// routing and serving event-stream bodies cannot arise. Metadata routers can recognize
513/// streaming inputs so claims requiring body bytes can be skipped.
514pub trait BodyRoutedProtocol: ServerProtocol + Sized {
515    /// Builds this protocol for a service, from its registered configuration. Invalid
516    /// configuration fails the service build.
517    fn from_build_context(ctx: &ProtocolBuildContext<'_>) -> Result<Self, RouterBuildError>;
518
519    /// Builds operation routing once for this service, over the service's non-streaming
520    /// operations.
521    ///
522    /// `use<Self>` pins down the capture contract exactly as on
523    /// [`MetadataRoutedProtocol::build_router`]; implementations write `use<>`.
524    fn build_router(
525        &self,
526        ctx: crate::schema::routing::RouterBuildContext<'_>,
527    ) -> Result<impl crate::schema::routing::BodyProtocolRouter + 'static + use<Self>, RouterBuildError>;
528}
529
530/// Converts a body collection failure into the protocol's rejection response, retaining
531/// generated serde wire behavior: the failure surfaces as an ordinary request-deserialization error.
532pub(crate) fn body_collection_rejection(
533    protocol: &dyn ServerProtocol,
534    error: RequestBodyCollectionError<crate::Error>,
535) -> Response {
536    protocol.serialize_rejection(error.into())
537}
538
539#[derive(Debug)]
540#[non_exhaustive]
541pub enum RequestBodyCollectionError<E> {
542    Body(E),
543    BodyLimitExceeded(NonZeroUsize),
544    Timeout { timeout: Duration },
545}
546
547impl<E> RequestBodyCollectionError<E> {
548    /// Maps a transport-specific error without changing collection-limit failures.
549    pub fn map_body_error<F>(self, map: impl FnOnce(E) -> F) -> RequestBodyCollectionError<F> {
550        match self {
551            Self::Body(error) => RequestBodyCollectionError::Body(map(error)),
552            Self::BodyLimitExceeded(limit) => RequestBodyCollectionError::BodyLimitExceeded(limit),
553            Self::Timeout { timeout } => RequestBodyCollectionError::Timeout { timeout },
554        }
555    }
556}
557
558impl<E: std::fmt::Display> std::fmt::Display for RequestBodyCollectionError<E> {
559    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
560        match self {
561            Self::Body(err) => write!(f, "error reading request body: {err}"),
562            Self::BodyLimitExceeded(limit) => {
563                write!(f, "request body exceeded the configured maximum of {limit} bytes")
564            }
565            Self::Timeout { timeout } => write!(f, "request body read timed out after {timeout:?}"),
566        }
567    }
568}
569
570impl<E: std::error::Error + 'static> std::error::Error for RequestBodyCollectionError<E> {
571    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
572        match self {
573            Self::Body(error) => Some(error),
574            Self::BodyLimitExceeded(_) | Self::Timeout { .. } => None,
575        }
576    }
577}
578
579pub async fn collect_request_body<B>(
580    body: B,
581    config: &RequestBodyCollectionConfig,
582) -> Result<Bytes, RequestBodyCollectionError<B::Error>>
583where
584    B: HttpBody + 'static,
585{
586    if let Some(bytes) = (&body as &dyn std::any::Any)
587        .downcast_ref::<crate::body::Body>()
588        .and_then(crate::body::Body::buffered_content)
589    {
590        if let Some(limit) = config.max_bytes.filter(|limit| bytes.len() > limit.get()) {
591            return Err(RequestBodyCollectionError::BodyLimitExceeded(limit));
592        }
593        return Ok(bytes.clone());
594    }
595    collect_request_body_with_trailers(body, config)
596        .await
597        .map(|(bytes, _)| bytes)
598}
599
600/// Shared complete-body collection for routing and operation deserialization.
601pub(crate) fn collect_request_body_with_trailers<B>(
602    body: B,
603    config: &RequestBodyCollectionConfig,
604) -> RequestBodyCollectionFuture<B>
605where
606    B: HttpBody,
607{
608    RequestBodyCollectionFuture {
609        inner: CollectionState::Start {
610            body: Some(body),
611            config: *config,
612        },
613    }
614}
615
616pin_project_lite::pin_project! {
617    #[project = CollectionStateProj]
618    enum CollectionState<B> {
619        Start {
620            body: Option<B>,
621            config: RequestBodyCollectionConfig,
622        },
623        Collect {
624            #[pin]
625            future: CollectBodyFuture<B, true>,
626        },
627        Timed {
628            #[pin]
629            // Tokio's timer cannot move once pinned. Keep the timed branch boxed so the
630            // public routing future retains its existing Unpin implementation.
631            future: std::pin::Pin<Box<tokio::time::Timeout<CollectBodyFuture<B, true>>>>,
632            timeout: Duration,
633        },
634        Done,
635    }
636}
637
638pin_project_lite::pin_project! {
639    /// A named collection future so routing can store body I/O without a box.
640    pub(crate) struct RequestBodyCollectionFuture<B> {
641        #[pin]
642        inner: CollectionState<B>,
643    }
644}
645
646impl<B: HttpBody> std::future::Future for RequestBodyCollectionFuture<B> {
647    type Output = Result<(Bytes, Option<http::HeaderMap>), RequestBodyCollectionError<B::Error>>;
648
649    fn poll(self: std::pin::Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> std::task::Poll<Self::Output> {
650        use std::task::Poll;
651        let mut this = self.project();
652        loop {
653            let result = match this.inner.as_mut().project() {
654                CollectionStateProj::Start { body, config } => {
655                    let future = CollectBodyFuture::<B, true>::new(
656                        body.take().expect("collection starts once"),
657                        Some(
658                            CollectionConfig::builder()
659                                .set_limit(config.max_bytes)
660                                .set_frame_budget(config.read_poll_budget)
661                                .build(),
662                        ),
663                    );
664                    let state = match config.read_timeout {
665                        Some(timeout) => CollectionState::Timed {
666                            future: Box::pin(tokio::time::timeout(timeout, future)),
667                            timeout,
668                        },
669                        None => CollectionState::Collect { future },
670                    };
671                    this.inner.set(state);
672                    continue;
673                }
674                CollectionStateProj::Collect { future } => std::task::ready!(future.poll(cx)),
675                CollectionStateProj::Timed { future, timeout } => match std::task::ready!(future.poll(cx)) {
676                    Ok(result) => result,
677                    Err(_) => {
678                        let timeout = *timeout;
679                        this.inner.set(CollectionState::Done);
680                        return Poll::Ready(Err(RequestBodyCollectionError::Timeout { timeout }));
681                    }
682                },
683                CollectionStateProj::Done => panic!("polled after completion"),
684            };
685            this.inner.set(CollectionState::Done);
686            return Poll::Ready(result.map_err(|err| match err {
687                CollectBodyError::Body(err) => RequestBodyCollectionError::Body(err),
688                CollectBodyError::BodyLimitExceeded(limit) => RequestBodyCollectionError::BodyLimitExceeded(limit),
689            }));
690        }
691    }
692}