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}