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}