aws-smithy-schema 0.1.0

Schema types for the smithy-rs ecosystem
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
/*
 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
 * SPDX-License-Identifier: Apache-2.0
 */

//! Codec trait for creating shape serializers and deserializers.
//!
//! A codec represents a specific serialization format (e.g., JSON, XML, CBOR)
//! and provides methods to create serializers and deserializers for that format.

pub mod http_string;

use crate::serde::{ShapeDeserializer, ShapeSerializer};

/// Trait for serializers that can produce a final byte output.
///
/// This is separate from [`ShapeSerializer`] to preserve object safety on
/// [`ShapeSerializer`] (which is used as `&mut dyn ShapeSerializer` in generated code).
///
/// # Why isn't `FinishSerializer` itself object-safe?
///
/// [`FinishSerializer::finish`] takes `self` by value so it can consume and tear down the
/// serializer (e.g., return an owned `Vec<u8>` without a leftover borrow on the serializer's
/// internal buffer). Methods that receive `self` by value are not dispatchable through a
/// trait object: `dyn FinishSerializer` doesn't know the concrete size of `Self`, so it
/// cannot move it. This is the standard Rust object-safety restriction.
///
/// The consequence is that `FinishSerializer` can only be used with a statically-known
/// serializer type, which is fine for generated code that knows the concrete [`Codec`].
/// For call sites that need dynamic dispatch (e.g., event stream marshallers that receive
/// a `Box<dyn PayloadSerializer>` from `ClientProtocol::payload_codec`), use
/// [`PayloadSerializer::finish_boxed`] instead — it takes `self: Box<Self>`, which *is*
/// object-safe because the `Box` owns the value and knows how to drop it.
pub trait FinishSerializer {
    /// Consumes the serializer and returns the serialized bytes.
    fn finish(self) -> Vec<u8>;
}

/// A codec for a specific serialization format.
///
/// Codecs are responsible for creating [`ShapeSerializer`] and [`ShapeDeserializer`]
/// instances that can serialize and deserialize shapes to and from a specific format.
///
/// # Examples
///
/// Implementing a custom codec:
///
/// ```ignore
/// use aws_smithy_schema::codec::Codec;
/// use aws_smithy_schema::serde::{ShapeSerializer, ShapeDeserializer};
///
/// struct MyCodec {
///     // codec configuration
/// }
///
/// impl Codec for MyCodec {
///     type Serializer = MySerializer;
///     type Deserializer = MyDeserializer;
///
///     fn create_serializer(&self) -> Self::Serializer {
///         MySerializer::new()
///     }
///
///     fn create_deserializer(&self, input: &[u8]) -> Self::Deserializer {
///         MyDeserializer::new(input)
///     }
/// }
/// ```
pub trait Codec {
    /// The serializer type for this codec.
    type Serializer: ShapeSerializer + FinishSerializer;

    /// The deserializer type for this codec.
    type Deserializer<'a>: ShapeDeserializer + 'a;

    /// Creates a new serializer for this codec.
    fn create_serializer(&self) -> Self::Serializer;

    /// Creates a new deserializer for this codec from the given input bytes.
    fn create_deserializer<'a>(&self, input: &'a [u8]) -> Self::Deserializer<'a>;
}

/// Object-safe view of a codec's serializer.
///
/// Combines [`ShapeSerializer`] with an object-safe finish operation. [`FinishSerializer::finish`]
/// takes `self` by value, which cannot be called through `&mut dyn ShapeSerializer` — so this
/// trait exposes `finish(self: Box<Self>)` instead, which is object-safe.
///
/// A blanket impl is provided for every `ShapeSerializer + FinishSerializer`, so every concrete
/// codec serializer (e.g., `JsonSerializer`) is automatically usable through `Box<dyn PayloadSerializer>`
/// without requiring codec authors to write extra impls.
pub trait PayloadSerializer: ShapeSerializer {
    /// Consumes this boxed serializer and returns the serialized bytes.
    fn finish_boxed(self: Box<Self>) -> Vec<u8>;
}

impl<S> PayloadSerializer for S
where
    S: ShapeSerializer + FinishSerializer,
{
    fn finish_boxed(self: Box<Self>) -> Vec<u8> {
        <S as FinishSerializer>::finish(*self)
    }
}

/// Object-safe sibling of [`Codec`] exposing dynamic deserializer creation.
///
/// # Why a sibling trait?
///
/// [`Codec`] uses associated types (`Serializer`, `Deserializer<'a>`) and
/// returns them by value from its methods. This gives codec consumers
/// zero-cost static dispatch — the compiler can inline and monomorphize
/// serializer/deserializer creation at call sites that know the concrete
/// codec type. That is the right choice for the common case (generated code
/// that knows the protocol statically).
///
/// However, some features require accessing a codec through a trait object.
/// The SEP-specified `ClientProtocol::payload_codec()` method returns "the
/// codec" in a context where the `ClientProtocol` itself is accessed via
/// `dyn ClientProtocol` (see [`SharedClientProtocol`](crate::protocol::SharedClientProtocol),
/// which stores `Arc<dyn ClientProtocol>` for runtime protocol swapping).
/// Returning a [`Codec`] through `dyn` is not possible in Rust because
/// associated types and by-value returns are not object-safe.
///
/// `DynCodec` is the minimal object-safe view that covers the operations
/// needed through a trait object. It exists purely as a Rust adaptation of
/// the SEP's object-oriented `Codec` design; it is not additional user-facing
/// API. A blanket `impl<C: Codec> DynCodec for C` makes every concrete codec
/// automatically usable through `&dyn DynCodec` without any extra work from
/// codec authors.
///
/// Both deserializer and serializer creation are exposed through this trait
/// to support event-stream marshalling (input streams) and unmarshalling
/// (output streams) through `dyn ClientProtocol`.
///
/// # Returning (de)serializers as boxed trait objects
///
/// [`ShapeDeserializer`] implementations typically hold cursor state over an
/// input byte slice (e.g., `JsonDeserializer` holds `input: &'a [u8]` and a
/// `position: usize`). Producing a fresh deserializer positioned at the start
/// of the input is the standard way to read independent messages — as is
/// required for event-stream frames, where each frame is an independent
/// serialized payload. The returned `Box<dyn ShapeDeserializer + 'a>`
/// borrows from `input`, so the caller retains ownership of the bytes for
/// the duration of deserialization. Similarly each event frame requires a
/// fresh serializer.
pub trait DynCodec: Send + Sync + std::fmt::Debug {
    /// Creates a new deserializer over the given input bytes.
    fn create_deserializer<'a>(&self, input: &'a [u8]) -> Box<dyn ShapeDeserializer + 'a>;

    /// Creates a new serializer. Use [`PayloadSerializer::finish_boxed`] to
    /// consume the serializer and obtain the serialized bytes.
    fn create_serializer(&self) -> Box<dyn PayloadSerializer + '_>;
}

// Blanket implementation: any statically-dispatched `Codec` is automatically
// available as a `DynCodec`. The boxed (de)serializer here incurs one heap
// allocation per call, which is acceptable for the per-event-frame use case.
// Callers using `Codec` directly pay no such cost.
impl<C> DynCodec for C
where
    C: Codec + Send + Sync + std::fmt::Debug,
{
    fn create_deserializer<'a>(&self, input: &'a [u8]) -> Box<dyn ShapeDeserializer + 'a> {
        Box::new(<C as Codec>::create_deserializer(self, input))
    }

    fn create_serializer(&self) -> Box<dyn PayloadSerializer + '_> {
        Box::new(<C as Codec>::create_serializer(self))
    }
}

#[cfg(test)]
mod test {
    use super::*;
    use crate::serde::{SerdeError, SerializableStruct, ShapeDeserializer, ShapeSerializer};
    use crate::{prelude::*, Schema};

    // Mock serializer
    struct MockSerializer {
        output: Vec<u8>,
    }

    impl MockSerializer {
        fn finish(self) -> Vec<u8> {
            self.output
        }
    }

    impl FinishSerializer for MockSerializer {
        fn finish(self) -> Vec<u8> {
            self.output
        }
    }

    impl ShapeSerializer for MockSerializer {
        fn write_struct(
            &mut self,
            _schema: &Schema,
            _value: &dyn SerializableStruct,
        ) -> Result<(), SerdeError> {
            Ok(())
        }

        fn write_list(
            &mut self,
            _schema: &Schema,
            _write_elements: &dyn Fn(&mut dyn ShapeSerializer) -> Result<(), SerdeError>,
        ) -> Result<(), SerdeError> {
            Ok(())
        }

        fn write_map(
            &mut self,
            _schema: &Schema,
            _write_entries: &dyn Fn(&mut dyn ShapeSerializer) -> Result<(), SerdeError>,
        ) -> Result<(), SerdeError> {
            Ok(())
        }

        fn write_boolean(&mut self, _schema: &Schema, _value: bool) -> Result<(), SerdeError> {
            Ok(())
        }

        fn write_byte(&mut self, _schema: &Schema, _value: i8) -> Result<(), SerdeError> {
            Ok(())
        }

        fn write_short(&mut self, _schema: &Schema, _value: i16) -> Result<(), SerdeError> {
            Ok(())
        }

        fn write_integer(&mut self, _schema: &Schema, _value: i32) -> Result<(), SerdeError> {
            Ok(())
        }

        fn write_long(&mut self, _schema: &Schema, _value: i64) -> Result<(), SerdeError> {
            Ok(())
        }

        fn write_float(&mut self, _schema: &Schema, _value: f32) -> Result<(), SerdeError> {
            Ok(())
        }

        fn write_double(&mut self, _schema: &Schema, _value: f64) -> Result<(), SerdeError> {
            Ok(())
        }

        fn write_big_integer(
            &mut self,
            _schema: &Schema,
            _value: &aws_smithy_types::BigInteger,
        ) -> Result<(), SerdeError> {
            Ok(())
        }

        fn write_big_decimal(
            &mut self,
            _schema: &Schema,
            _value: &aws_smithy_types::BigDecimal,
        ) -> Result<(), SerdeError> {
            Ok(())
        }

        fn write_string(&mut self, _schema: &Schema, _value: &str) -> Result<(), SerdeError> {
            Ok(())
        }

        fn write_blob(
            &mut self,
            _schema: &Schema,
            _value: &aws_smithy_types::Blob,
        ) -> Result<(), SerdeError> {
            Ok(())
        }

        fn write_timestamp(
            &mut self,
            _schema: &Schema,
            _value: &aws_smithy_types::DateTime,
        ) -> Result<(), SerdeError> {
            Ok(())
        }

        fn write_document(
            &mut self,
            _schema: &Schema,
            _value: &aws_smithy_types::Document,
        ) -> Result<(), SerdeError> {
            Ok(())
        }

        fn write_null(&mut self, _schema: &Schema) -> Result<(), SerdeError> {
            Ok(())
        }
    }

    // Mock deserializer
    struct MockDeserializer<'a> {
        #[allow(dead_code)]
        input: &'a [u8],
    }

    impl<'a> ShapeDeserializer for MockDeserializer<'a> {
        fn read_struct(
            &mut self,
            _schema: &Schema,
            _consumer: &mut dyn FnMut(
                &Schema,
                &mut dyn ShapeDeserializer,
            ) -> Result<(), SerdeError>,
        ) -> Result<(), SerdeError> {
            Ok(())
        }

        fn read_list(
            &mut self,
            _schema: &Schema,
            _consumer: &mut dyn FnMut(&mut dyn ShapeDeserializer) -> Result<(), SerdeError>,
        ) -> Result<(), SerdeError> {
            Ok(())
        }

        fn read_map(
            &mut self,
            _schema: &Schema,
            _consumer: &mut dyn FnMut(String, &mut dyn ShapeDeserializer) -> Result<(), SerdeError>,
        ) -> Result<(), SerdeError> {
            Ok(())
        }

        fn read_boolean(&mut self, _schema: &Schema) -> Result<bool, SerdeError> {
            Ok(false)
        }

        fn read_byte(&mut self, _schema: &Schema) -> Result<i8, SerdeError> {
            Ok(0)
        }

        fn read_short(&mut self, _schema: &Schema) -> Result<i16, SerdeError> {
            Ok(0)
        }

        fn read_integer(&mut self, _schema: &Schema) -> Result<i32, SerdeError> {
            Ok(0)
        }

        fn read_long(&mut self, _schema: &Schema) -> Result<i64, SerdeError> {
            Ok(0)
        }

        fn read_float(&mut self, _schema: &Schema) -> Result<f32, SerdeError> {
            Ok(0.0)
        }

        fn read_double(&mut self, _schema: &Schema) -> Result<f64, SerdeError> {
            Ok(0.0)
        }

        fn read_big_integer(
            &mut self,
            _schema: &Schema,
        ) -> Result<aws_smithy_types::BigInteger, SerdeError> {
            use std::str::FromStr;
            Ok(aws_smithy_types::BigInteger::from_str("0").unwrap())
        }

        fn read_big_decimal(
            &mut self,
            _schema: &Schema,
        ) -> Result<aws_smithy_types::BigDecimal, SerdeError> {
            use std::str::FromStr;
            Ok(aws_smithy_types::BigDecimal::from_str("0").unwrap())
        }

        fn read_string(&mut self, _schema: &Schema) -> Result<String, SerdeError> {
            Ok(String::new())
        }

        fn read_blob(&mut self, _schema: &Schema) -> Result<aws_smithy_types::Blob, SerdeError> {
            Ok(aws_smithy_types::Blob::new(Vec::new()))
        }

        fn read_timestamp(
            &mut self,
            _schema: &Schema,
        ) -> Result<aws_smithy_types::DateTime, SerdeError> {
            Ok(aws_smithy_types::DateTime::from_secs(0))
        }

        fn read_document(
            &mut self,
            _schema: &Schema,
        ) -> Result<aws_smithy_types::Document, SerdeError> {
            Ok(aws_smithy_types::Document::Null)
        }

        fn is_null(&self) -> bool {
            false
        }

        fn container_size(&self) -> Option<usize> {
            None
        }
    }

    // Mock codec
    struct MockCodec;

    impl Codec for MockCodec {
        type Serializer = MockSerializer;
        type Deserializer<'a> = MockDeserializer<'a>;

        fn create_serializer(&self) -> Self::Serializer {
            MockSerializer { output: Vec::new() }
        }

        fn create_deserializer<'a>(&self, input: &'a [u8]) -> Self::Deserializer<'a> {
            MockDeserializer { input }
        }
    }

    #[test]
    fn test_codec_create_serializer() {
        let codec = MockCodec;
        let mut serializer = codec.create_serializer();

        // Test that we can use the serializer
        serializer.write_string(&STRING, "test").unwrap();
        let output = serializer.finish();
        assert_eq!(output, Vec::<u8>::new());
    }

    #[test]
    fn test_codec_create_deserializer() {
        let codec = MockCodec;
        let input = b"test data";
        let mut deserializer = codec.create_deserializer(input);

        // Test that we can use the deserializer
        let result = deserializer.read_string(&STRING).unwrap();
        assert_eq!(result, "");
    }

    #[test]
    fn test_codec_roundtrip() {
        let codec = MockCodec;

        // Serialize
        let mut serializer = codec.create_serializer();
        serializer.write_integer(&INTEGER, 42).unwrap();
        let bytes = serializer.finish();

        // Deserialize
        let mut deserializer = codec.create_deserializer(&bytes);
        let value = deserializer.read_integer(&INTEGER).unwrap();
        assert_eq!(value, 0); // Mock deserializer always returns 0
    }
}