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