Skip to main content

aws_smithy_schema/schema/serde/
error.rs

1/*
2 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 * SPDX-License-Identifier: Apache-2.0
4 */
5
6//! Error types for shape serialization and deserialization.
7
8use std::fmt;
9
10/// Error type for shape serialization and deserialization operations.
11#[derive(Debug)]
12#[non_exhaustive]
13pub enum SerdeError {
14    /// The data did not match the expected type described by the schema.
15    #[non_exhaustive]
16    TypeMismatch {
17        /// Description of what was expected vs what was found.
18        message: String,
19    },
20    /// A required structure member was missing during deserialization.
21    ///
22    /// **Not currently emitted.** The schema-serde deserializers use the
23    /// push/consumer pattern and defer required-member enforcement to the
24    /// generated builder's `build()`. This variant is reserved for
25    /// deserializers that choose to enforce member presence during the read.
26    #[non_exhaustive]
27    MissingMember {
28        /// The name of the missing member.
29        member_name: String,
30    },
31    /// An unknown member was encountered during deserialization.
32    #[non_exhaustive]
33    UnknownMember {
34        /// The name of the unknown member.
35        member_name: String,
36    },
37    /// The input data was malformed or invalid for the format.
38    #[non_exhaustive]
39    InvalidInput {
40        /// Description of the problem.
41        message: String,
42    },
43    /// The operation is not supported by this serializer or deserializer.
44    #[non_exhaustive]
45    UnsupportedOperation {
46        /// Description of what was attempted.
47        message: String,
48    },
49    /// An error occurred while writing output during serialization.
50    #[non_exhaustive]
51    WriteFailed {
52        /// Description of the write failure.
53        message: String,
54    },
55    /// A numeric coercion overflowed the target type's representable
56    /// range.
57    ///
58    /// Emitted by the narrow numeric reads on
59    /// [`ShapeDeserializer`](crate::serde::ShapeDeserializer)
60    /// (`read_byte`, `read_short`, `read_integer`, `read_long`) when the
61    /// source value is outside the target's `[min, max]` range.
62    #[non_exhaustive]
63    NumericCoercionOverflow {
64        /// Target type name (e.g. `"byte"`, `"integer"`, `"long"`).
65        target: String,
66        /// String representation of the overflowing value, included
67        /// for diagnostics.
68        value: String,
69    },
70    /// A numeric coercion would lose precision in a context where
71    /// precision loss is not acceptable.
72    ///
73    /// **Not currently emitted by the standard numeric reads.** Per the
74    /// SEP "Number coercion" rules, precision loss is silently ignored
75    /// on the standard coercion path. This variant is reserved for
76    /// callers (e.g. strict deserializers, customer code using a custom
77    /// accessor) that choose to enforce lossless coercion.
78    #[non_exhaustive]
79    NumericCoercionLossy {
80        /// Target type name.
81        target: String,
82        /// String representation of the value that would lose precision.
83        value: String,
84    },
85    /// Failed to decode a blob from its wire-format representation —
86    /// for example, invalid base64 in a JSON payload.
87    ///
88    /// **Not currently emitted.** In-tree codecs surface blob-decode
89    /// failures as [`InvalidInput`](Self::InvalidInput). This variant is
90    /// reserved for codecs that want to distinguish blob-decode failures as a
91    /// separate category.
92    #[non_exhaustive]
93    BlobDecodeFailed {
94        /// Description of the decode failure.
95        message: String,
96    },
97    /// Failed to parse a timestamp from its wire-format representation —
98    /// for example, a malformed RFC-3339 string or an out-of-range
99    /// epoch-seconds value.
100    ///
101    /// **Not currently emitted.** In-tree codecs surface timestamp-parse
102    /// failures as [`InvalidInput`](Self::InvalidInput). This variant is
103    /// reserved for codecs that want to distinguish timestamp-parse failures
104    /// as a separate category.
105    #[non_exhaustive]
106    TimestampParseFailed {
107        /// Description of the parse failure.
108        message: String,
109    },
110    /// Catch-all for errors not covered by other variants.
111    #[non_exhaustive]
112    Custom {
113        /// Explanatory message.
114        message: String,
115    },
116}
117
118impl fmt::Display for SerdeError {
119    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
120        match self {
121            SerdeError::TypeMismatch { message } => write!(f, "type mismatch: {message}"),
122            SerdeError::MissingMember { member_name } => {
123                write!(f, "missing required member: {member_name}")
124            }
125            SerdeError::UnknownMember { member_name } => {
126                write!(f, "unknown member: {member_name}")
127            }
128            SerdeError::InvalidInput { message } => write!(f, "invalid input: {message}"),
129            SerdeError::UnsupportedOperation { message } => {
130                write!(f, "unsupported operation: {message}")
131            }
132            SerdeError::WriteFailed { message } => write!(f, "write failed: {message}"),
133            SerdeError::NumericCoercionOverflow { target, value } => {
134                write!(f, "numeric value {value} out of range for {target}")
135            }
136            SerdeError::NumericCoercionLossy { target, value } => {
137                write!(
138                    f,
139                    "numeric value {value} cannot be coerced to {target} without precision loss"
140                )
141            }
142            SerdeError::BlobDecodeFailed { message } => write!(f, "blob decode failed: {message}"),
143            SerdeError::TimestampParseFailed { message } => {
144                write!(f, "timestamp parse failed: {message}")
145            }
146            SerdeError::Custom { message } => f.write_str(message),
147        }
148    }
149}
150
151impl std::error::Error for SerdeError {}
152
153impl SerdeError {
154    /// Creates a `TypeMismatch` error describing what was expected
155    /// versus what was found.
156    pub fn type_mismatch(message: impl Into<String>) -> Self {
157        SerdeError::TypeMismatch {
158            message: message.into(),
159        }
160    }
161
162    /// Creates a `MissingMember` error for the given member name.
163    pub fn missing_member(member_name: impl Into<String>) -> Self {
164        SerdeError::MissingMember {
165            member_name: member_name.into(),
166        }
167    }
168
169    /// Creates an `UnknownMember` error for the given member name.
170    pub fn unknown_member(member_name: impl Into<String>) -> Self {
171        SerdeError::UnknownMember {
172            member_name: member_name.into(),
173        }
174    }
175
176    /// Creates an `InvalidInput` error with the given message.
177    pub fn invalid_input(message: impl Into<String>) -> Self {
178        SerdeError::InvalidInput {
179            message: message.into(),
180        }
181    }
182
183    /// Creates an `UnsupportedOperation` error with the given message.
184    pub fn unsupported(message: impl Into<String>) -> Self {
185        SerdeError::UnsupportedOperation {
186            message: message.into(),
187        }
188    }
189
190    /// Creates a `WriteFailed` error with the given message.
191    pub fn write_failed(message: impl Into<String>) -> Self {
192        SerdeError::WriteFailed {
193            message: message.into(),
194        }
195    }
196
197    /// Creates a `NumericCoercionOverflow` error for a `value` that is
198    /// outside the representable range of `target` (e.g. `"byte"`).
199    pub fn numeric_coercion_overflow(target: impl Into<String>, value: impl Into<String>) -> Self {
200        SerdeError::NumericCoercionOverflow {
201            target: target.into(),
202            value: value.into(),
203        }
204    }
205
206    /// Creates a `NumericCoercionLossy` error for a `value` that cannot
207    /// be coerced to `target` without losing precision.
208    pub fn numeric_coercion_lossy(target: impl Into<String>, value: impl Into<String>) -> Self {
209        SerdeError::NumericCoercionLossy {
210            target: target.into(),
211            value: value.into(),
212        }
213    }
214
215    /// Creates a `BlobDecodeFailed` error with the given message.
216    pub fn blob_decode_failed(message: impl Into<String>) -> Self {
217        SerdeError::BlobDecodeFailed {
218            message: message.into(),
219        }
220    }
221
222    /// Creates a `TimestampParseFailed` error with the given message.
223    pub fn timestamp_parse_failed(message: impl Into<String>) -> Self {
224        SerdeError::TimestampParseFailed {
225            message: message.into(),
226        }
227    }
228
229    /// Creates a custom error with the given message.
230    pub fn custom(message: impl Into<String>) -> Self {
231        SerdeError::Custom {
232            message: message.into(),
233        }
234    }
235}
236
237/// Lift a [`DocumentError`](aws_smithy_types::DocumentError) coming out
238/// of the format-aware coercion accessors on
239/// [`DiscriminatedDocument`](aws_smithy_types::DiscriminatedDocument)
240/// and the [`DocumentSettings`](aws_smithy_types::DocumentSettings)
241/// hooks they dispatch through, into the schema crate's broader
242/// [`SerdeError`].
243///
244/// The variant shapes line up one-to-one — `DocumentError` is a
245/// strict subset (Document-shaped failures only); `SerdeError` adds
246/// shape-serde concerns like missing/unknown members and write
247/// failures that are out of scope for the types crate.
248impl From<aws_smithy_types::DocumentError> for SerdeError {
249    fn from(err: aws_smithy_types::DocumentError) -> Self {
250        use aws_smithy_types::DocumentError as DE;
251        match err {
252            DE::TypeMismatch { message, .. } => SerdeError::type_mismatch(message),
253            DE::NumericCoercionOverflow { target, value, .. } => {
254                SerdeError::numeric_coercion_overflow(target, value)
255            }
256            DE::InvalidInput { message, .. } => SerdeError::invalid_input(message),
257            DE::UnsupportedOperation { message, .. } => SerdeError::unsupported(message),
258            DE::Custom { message, .. } => SerdeError::custom(message),
259            // `DocumentError` is `#[non_exhaustive]`. Future variants
260            // surface as `Custom` so callers always see a reasonable
261            // mapping.
262            other => SerdeError::custom(format!("{other}")),
263        }
264    }
265}