Skip to main content

aws_smithy_schema/schema/serde/
serializer.rs

1/*
2 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 * SPDX-License-Identifier: Apache-2.0
4 */
5
6//! Shape serialization interfaces for the Smithy data model.
7
8use super::error::SerdeError;
9use crate::Schema;
10use aws_smithy_types::Document;
11use aws_smithy_types::{BigDecimal, BigInteger, Blob, DateTime};
12
13/// Serializes Smithy shapes to a target format.
14///
15/// This trait provides a format-agnostic API for serializing the Smithy data model.
16/// Implementations serialize each data type to the corresponding encoding in their
17/// serial format (e.g., Smithy integers and floats to JSON numbers).
18///
19/// The serializer accepts a schema along with the value to provide additional
20/// information about how to serialize the value (e.g., timestamp format, JSON name).
21///
22/// This trait is object-safe so that generated `SerializableStruct` implementations
23/// can use `&mut dyn ShapeSerializer`, producing one compiled `serialize_members()`
24/// per shape regardless of how many codecs exist (`shapes + codecs` rather than
25/// `shapes * codecs` in binary size).
26///
27/// # Example
28///
29/// ```ignore
30/// let mut serializer = JsonSerializer::new();
31/// serializer.write_string(&STRING_SCHEMA, "hello")?;
32/// ```
33pub trait ShapeSerializer {
34    /// Writes a structure to the serializer.
35    ///
36    /// # Arguments
37    ///
38    /// * `schema` - The schema of the structure being serialized
39    /// * `value` - The structure to serialize
40    fn write_struct(
41        &mut self,
42        schema: &Schema<'_>,
43        value: &dyn SerializableStruct,
44    ) -> Result<(), SerdeError>;
45
46    /// Writes a list to the serializer.
47    ///
48    /// # Arguments
49    ///
50    /// * `schema` - The schema of the list being serialized
51    /// * `write_elements` - Callback that writes the list elements
52    fn write_list(
53        &mut self,
54        schema: &Schema<'_>,
55        write_elements: &dyn Fn(&mut dyn ShapeSerializer) -> Result<(), SerdeError>,
56    ) -> Result<(), SerdeError>;
57
58    /// Writes a map to the serializer.
59    ///
60    /// # Arguments
61    ///
62    /// * `schema` - The schema of the map being serialized
63    /// * `write_entries` - Callback that writes the map entries
64    fn write_map(
65        &mut self,
66        schema: &Schema<'_>,
67        write_entries: &dyn Fn(&mut dyn ShapeSerializer) -> Result<(), SerdeError>,
68    ) -> Result<(), SerdeError>;
69
70    /// Writes a boolean value.
71    fn write_boolean(&mut self, schema: &Schema<'_>, value: bool) -> Result<(), SerdeError>;
72
73    /// Writes a byte (i8) value.
74    fn write_byte(&mut self, schema: &Schema<'_>, value: i8) -> Result<(), SerdeError>;
75
76    /// Writes a short (i16) value.
77    fn write_short(&mut self, schema: &Schema<'_>, value: i16) -> Result<(), SerdeError>;
78
79    /// Writes an integer (i32) value.
80    fn write_integer(&mut self, schema: &Schema<'_>, value: i32) -> Result<(), SerdeError>;
81
82    /// Writes a long (i64) value.
83    fn write_long(&mut self, schema: &Schema<'_>, value: i64) -> Result<(), SerdeError>;
84
85    /// Writes a float (f32) value.
86    fn write_float(&mut self, schema: &Schema<'_>, value: f32) -> Result<(), SerdeError>;
87
88    /// Writes a double (f64) value.
89    fn write_double(&mut self, schema: &Schema<'_>, value: f64) -> Result<(), SerdeError>;
90
91    /// Writes a big integer value.
92    fn write_big_integer(
93        &mut self,
94        schema: &Schema<'_>,
95        value: &BigInteger,
96    ) -> Result<(), SerdeError>;
97
98    /// Writes a big decimal value.
99    fn write_big_decimal(
100        &mut self,
101        schema: &Schema<'_>,
102        value: &BigDecimal,
103    ) -> Result<(), SerdeError>;
104
105    /// Writes a string value.
106    fn write_string(&mut self, schema: &Schema<'_>, value: &str) -> Result<(), SerdeError>;
107
108    /// Writes a blob (byte array) value.
109    ///
110    /// Takes an owned [`Blob`] rather than `&[u8]` so that an implementor which
111    /// needs to *retain* the bytes past this call can do so without copying
112    /// them. [`Blob`] wraps `bytes::Bytes`, so moving the parameter — or cloning
113    /// it — is a refcount operation rather than a payload copy.
114    ///
115    /// This is load-bearing for the HTTP `@httpPayload` binding, which stores
116    /// the payload on the serializer and hands it to the request body after
117    /// serialization finishes. With a borrowed parameter there is no lifetime
118    /// relationship between `value` and `self`, so retaining it was only
119    /// possible by either copying the payload or unsoundly asserting a lifetime.
120    ///
121    /// Callers that hold something other than a `Blob` convert cheaply:
122    /// `Blob::from_maybe_shared(bytes)` reuses an existing `Bytes` allocation,
123    /// and `Blob::new(vec)` takes ownership of a `Vec<u8>`. Generated client code
124    /// clones the `Blob` field of its data carrier, which is a refcount bump.
125    ///
126    /// Note the deliberate asymmetry with [`Self::write_string`] and
127    /// [`Self::write_document`], which stay borrowed. `Blob` is `Bytes`-backed,
128    /// so an owned parameter costs two atomics; `String` and `Document` have no
129    /// shared representation, so an owned parameter there would force a real
130    /// copy on every caller including the majority that never retain the value.
131    /// Do not "fix" the inconsistency.
132    fn write_blob(&mut self, schema: &Schema<'_>, value: Blob) -> Result<(), SerdeError>;
133
134    /// Writes a timestamp value.
135    fn write_timestamp(&mut self, schema: &Schema<'_>, value: &DateTime) -> Result<(), SerdeError>;
136
137    /// Writes a document value.
138    ///
139    /// `value` is the [`aws_smithy_types::Document`] (fully
140    /// owned, no lifetime). Implementors clone the value into their
141    /// output representation.
142    fn write_document(&mut self, schema: &Schema<'_>, value: &Document) -> Result<(), SerdeError>;
143
144    /// Writes a null value (for sparse collections).
145    fn write_null(&mut self, schema: &Schema<'_>) -> Result<(), SerdeError>;
146
147    // --- Collection helper methods ---
148    //
149    // This is a **closed set** of helpers for the most common AWS collection
150    // patterns. No additional helpers will be added. New collection patterns
151    // should use the generic `write_list`/`write_map` with closures.
152    //
153    // These exist for two reasons:
154    // 1. Code size: each helper replaces ~6-8 lines of closure boilerplate in
155    //    generated code, yielding ~43% reduction for collection-heavy models.
156    // 2. Performance: the corresponding `ShapeDeserializer` helpers are
157    //    overridden by codec implementations (e.g., `JsonDeserializer`) to
158    //    avoid per-element vtable dispatch. Keeping them on the core trait
159    //    (rather than an extension trait) is required because they are called
160    //    through `&mut dyn ShapeSerializer`/`&mut dyn ShapeDeserializer` in
161    //    generated `serialize_members`/`deserialize` methods.
162
163    /// Writes a list of strings.
164    fn write_string_list(
165        &mut self,
166        schema: &Schema<'_>,
167        values: &[String],
168    ) -> Result<(), SerdeError> {
169        self.write_list(schema, &|ser| {
170            for item in values {
171                ser.write_string(&crate::prelude::STRING, item)?;
172            }
173            Ok(())
174        })
175    }
176
177    /// Writes a list of blobs.
178    fn write_blob_list(
179        &mut self,
180        schema: &Schema<'_>,
181        values: &[aws_smithy_types::Blob],
182    ) -> Result<(), SerdeError> {
183        self.write_list(schema, &|ser| {
184            for item in values {
185                // Refcount bump, not a payload copy: `Blob` wraps `bytes::Bytes`.
186                ser.write_blob(&crate::prelude::BLOB, item.clone())?;
187            }
188            Ok(())
189        })
190    }
191
192    /// Writes a list of integers.
193    fn write_integer_list(
194        &mut self,
195        schema: &Schema<'_>,
196        values: &[i32],
197    ) -> Result<(), SerdeError> {
198        self.write_list(schema, &|ser| {
199            for item in values {
200                ser.write_integer(&crate::prelude::INTEGER, *item)?;
201            }
202            Ok(())
203        })
204    }
205
206    /// Writes a list of longs.
207    fn write_long_list(&mut self, schema: &Schema<'_>, values: &[i64]) -> Result<(), SerdeError> {
208        self.write_list(schema, &|ser| {
209            for item in values {
210                ser.write_long(&crate::prelude::LONG, *item)?;
211            }
212            Ok(())
213        })
214    }
215
216    /// Writes a map with string keys and string values.
217    fn write_string_string_map(
218        &mut self,
219        schema: &Schema<'_>,
220        values: &std::collections::HashMap<String, String>,
221    ) -> Result<(), SerdeError> {
222        self.write_map(schema, &|ser| {
223            for (key, value) in values {
224                ser.write_string(&crate::prelude::STRING, key)?;
225                ser.write_string(&crate::prelude::STRING, value)?;
226            }
227            Ok(())
228        })
229    }
230}
231
232/// Trait for structures that can be serialized via a schema.
233///
234/// Implemented by generated structure types. Because `ShapeSerializer` is object-safe,
235/// each struct gets one compiled `serialize_members()` that works with any serializer
236/// through dynamic dispatch.
237///
238/// # Example
239///
240/// ```ignore
241/// impl SerializableStruct for MyStruct {
242///     fn schema(&self) -> &Schema<'_> {
243///         &MY_STRUCT_SCHEMA
244///     }
245///
246///     fn serialize_members(&self, serializer: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
247///         serializer.write_string(&NAME_SCHEMA, &self.name)?;
248///         serializer.write_integer(&AGE_SCHEMA, self.age)?;
249///         Ok(())
250///     }
251/// }
252/// ```
253pub trait SerializableStruct {
254    /// Returns the schema of this structure or union itself, not of a member targeting it.
255    ///
256    /// Borrowed from `self` so a value can report a schema built at runtime; generated
257    /// types return their `'static` schema, which coerces to the shorter lifetime.
258    fn schema(&self) -> &Schema<'_>;
259
260    /// Serializes this structure's members using the provided serializer.
261    fn serialize_members(&self, serializer: &mut dyn ShapeSerializer) -> Result<(), SerdeError>;
262}
263
264impl<T: SerializableStruct + ?Sized> SerializableStruct for Box<T> {
265    fn schema(&self) -> &Schema<'_> {
266        (**self).schema()
267    }
268
269    fn serialize_members(&self, serializer: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
270        (**self).serialize_members(serializer)
271    }
272}
273
274// An infallible operation still satisfies generic serialization bounds; there is no value to serialize.
275impl SerializableStruct for std::convert::Infallible {
276    fn schema(&self) -> &Schema<'_> {
277        match *self {}
278    }
279
280    fn serialize_members(&self, _: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
281        match *self {}
282    }
283}
284
285#[cfg(test)]
286mod schema_tests {
287    use super::*;
288    use crate::{ShapeId, ShapeType};
289
290    #[test]
291    fn boxed_trait_object_reports_a_runtime_built_schema() {
292        struct Value<'a> {
293            schema: Schema<'a>,
294        }
295        impl SerializableStruct for Value<'_> {
296            fn schema(&self) -> &Schema<'_> {
297                &self.schema
298            }
299
300            fn serialize_members(&self, _: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
301                Ok(())
302            }
303        }
304
305        let shape_name = String::from("RuntimeValue");
306        let id = String::from("test#RuntimeValue");
307        let value = Value {
308            schema: Schema::new_struct(
309                ShapeId::from_parts(&id, "test", &shape_name),
310                ShapeType::Structure,
311                &[],
312            ),
313        };
314        let value: Box<dyn SerializableStruct + '_> = Box::new(value);
315        assert_eq!(value.schema().shape_id().as_str(), id);
316        let forwarded = <Box<dyn SerializableStruct> as SerializableStruct>::schema(&value);
317        assert!(std::ptr::eq(forwarded, value.as_ref().schema()));
318    }
319}