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}