Skip to main content

aws_smithy_http_server/schema/
deserialize.rs

1/*
2 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 * SPDX-License-Identifier: Apache-2.0
4 */
5
6use aws_smithy_schema::serde::{SerdeError, ShapeDeserializer};
7
8use super::{HttpModeledError, RequestBodyCollectionError};
9use crate::rejection::MissingContentTypeReason;
10
11/// Why a request failed without reaching its operation handler.
12///
13/// This is the one protocol-independent failure enum on the schema path: every way a request can
14/// fail before its handler runs lands here — almost always a failure to become the operation
15/// input, plus the missing-handler [`InternalFailure`](Self::InternalFailure) — and each protocol
16/// turns the whole enum into its wire response through
17/// [`ServerProtocol::serialize_rejection`](super::ServerProtocol::serialize_rejection).
18///
19/// Variants preserve the failure category even when their rejection responses are identical.
20/// The `Content-Type` and `Accept` failures are not folded into [`Serde`](Self::Serde): protocols answer them
21/// differently (restJson1 keeps 415 and 406 distinct from the 400 that `Serde` produces), and
22/// the renderer matches on the variant to pick the response. They also originate in different
23/// layers: [`SerdeError`] belongs to the codec in `aws-smithy-schema` and describes failures
24/// reading the payload and bindings into the input shape, while the `Content-Type` and `Accept`
25/// checks run in the HTTP protocol layer before the codec — the codec never sees headers, and
26/// the transport-agnostic schema crate carries no HTTP semantics.
27#[derive(Debug)]
28#[non_exhaustive]
29pub enum DeserializeError {
30    /// The request could not be read as the input shape: a malformed document, a type mismatch,
31    /// an unparseable header, label or query value, or a request that could not be converted
32    /// into its canonical form.
33    Serde(SerdeError),
34    /// Reading an RPC initial-request event frame failed. Preserve the generated serde raw
35    /// validation rejection rather than treating a frame failure as a codec error.
36    InitialRequestFrame(String),
37    /// Reading the request body failed, exceeded its size limit, or timed out.
38    /// Preserves the typed failure while rendering the protocol's ordinary deserialization rejection.
39    BodyCollection(RequestBodyCollectionError<crate::Error>),
40    /// The request was well-formed but violates a modeled constraint.
41    ConstraintViolation(Box<dyn HttpModeledError>),
42    /// The request's `Content-Type` header does not match what the operation expects.
43    ///
44    /// Raised by the protocol layer's header check, never by the codec; carries the expected
45    /// and found mime types the 415 response is built from.
46    // Boxed to keep the enum small: the reason carries two parsed mime types.
47    UnsupportedMediaType(Box<MissingContentTypeReason>),
48    /// The request's `Accept` header cannot accept the operation's response content type.
49    ///
50    /// Raised by the protocol layer's header check, never by the codec.
51    NotAcceptable,
52    /// The service cannot run the selected operation.
53    ///
54    /// Raised by the missing-handler fallback when a service built with `build_unchecked` receives
55    /// a request for an operation without a registered handler, never by request parsing. Renders
56    /// as the protocol's 500 internal-failure response; the carried error is logged, not written
57    /// to the wire.
58    InternalFailure(crate::Error),
59}
60
61impl std::fmt::Display for DeserializeError {
62    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
63        match self {
64            Self::Serde(err) => write!(f, "failed to deserialize request: {err}"),
65            Self::InitialRequestFrame(err) => write!(f, "failed to read the initial-request frame: {err}"),
66            Self::BodyCollection(err) => write!(f, "failed to collect request body: {err}"),
67            Self::ConstraintViolation(err) => {
68                write!(f, "request does not adhere to modeled constraints: {err}")
69            }
70            Self::UnsupportedMediaType(reason) => {
71                write!(f, "request has an unsupported `Content-Type`: {reason}")
72            }
73            Self::NotAcceptable => write!(f, "request contains an invalid value for the `Accept` header"),
74            Self::InternalFailure(err) => write!(f, "internal server error: {err}"),
75        }
76    }
77}
78
79impl std::error::Error for DeserializeError {
80    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
81        match self {
82            Self::Serde(err) => Some(err),
83            Self::BodyCollection(err) => Some(err),
84            Self::ConstraintViolation(err) => Some(&**err),
85            Self::UnsupportedMediaType(reason) => Some(&**reason),
86            Self::NotAcceptable | Self::InitialRequestFrame(_) => None,
87            Self::InternalFailure(err) => Some(err),
88        }
89    }
90}
91
92impl From<SerdeError> for DeserializeError {
93    fn from(err: SerdeError) -> Self {
94        Self::Serde(err)
95    }
96}
97
98impl From<RequestBodyCollectionError<crate::Error>> for DeserializeError {
99    fn from(err: RequestBodyCollectionError<crate::Error>) -> Self {
100        Self::BodyCollection(err)
101    }
102}
103
104impl From<MissingContentTypeReason> for DeserializeError {
105    fn from(reason: MissingContentTypeReason) -> Self {
106        Self::UnsupportedMediaType(Box::new(reason))
107    }
108}
109
110/// An operation input that can be read from a [`ShapeDeserializer`].
111///
112/// The runtime deserializes inputs generically: [`DynUpgrade`](crate::operation::DynUpgrade) and
113/// its streaming counterpart are written once over `Op::Input: DeserializableShape`, so every
114/// generated input implements this trait. It returns [`DeserializeError`] so a builder's typed
115/// constraint violation reaches the protocol renderer as
116/// [`DeserializeError::ConstraintViolation`] and becomes the modeled validation response instead
117/// of collapsing into a generic parse failure.
118///
119/// Generated inputs walk their schema into the internal builder and then `build()`, so constraint
120/// validation keeps happening in one place.
121pub trait DeserializableShape: Sized {
122    /// Reads `Self` from `deserializer`.
123    fn deserialize(deserializer: &mut dyn ShapeDeserializer) -> Result<Self, DeserializeError>;
124}