Skip to main content

aws_smithy_cbor/codec/
serializer.rs

1/*
2 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 * SPDX-License-Identifier: Apache-2.0
4 */
5
6//! CBOR serializer implementation.
7
8use aws_smithy_schema::codec::FinishSerializer;
9use aws_smithy_schema::serde::{SerdeError, SerializableStruct, ShapeSerializer};
10use aws_smithy_schema::Schema;
11use aws_smithy_types::{BigDecimal, BigInteger, Blob, DateTime, Document};
12
13/// Tracks the kind of CBOR container currently being written.
14///
15/// In RPC v2 CBOR a structure is a map keyed by member name, but list elements
16/// are positional and map values follow a key that is written separately. A
17/// member schema carries a `member_name` (e.g. a list's `member` or a map's
18/// `value` member), so without tracking the surrounding container that name
19/// would be wrongly emitted as a key when the member appears as a list element
20/// or map value. Member-name keys are therefore only emitted in [`Container::Struct`].
21#[derive(Clone, Copy, PartialEq, Eq)]
22enum Container {
23    Struct,
24    List,
25    Map,
26}
27
28/// Callback for writing members into the currently open structure map.
29///
30/// Runs after the map and its container state are opened, before modeled members.
31/// The schema is the value's structure or union schema, obtained through
32/// [`SerializableStruct::schema`]. Any error is returned immediately without
33/// writing modeled members.
34pub type StructPrefix = fn(&Schema<'_>, &mut dyn ShapeSerializer) -> Result<(), SerdeError>;
35
36/// CBOR serializer that implements the [`ShapeSerializer`] trait.
37///
38/// Wraps the existing optimized `Encoder` which uses `minicbor` with
39/// infallible writes to `Vec<u8>`.
40pub struct CborSerializer {
41    encoder: crate::Encoder,
42    container_stack: Vec<Container>,
43    struct_prefix: Option<StructPrefix>,
44    use_definite_lengths: bool,
45}
46
47impl CborSerializer {
48    pub(crate) fn new() -> Self {
49        Self {
50            encoder: crate::Encoder::new(Vec::new()),
51            container_stack: Vec::new(),
52            struct_prefix: None,
53            use_definite_lengths: false,
54        }
55    }
56
57    pub(crate) fn with_definite_lengths(mut self, value: bool) -> Self {
58        self.use_definite_lengths = value;
59        self
60    }
61
62    /// Installs a callback that writes members into each currently open structure
63    /// map before its modeled members. The callback receives the value's schema,
64    /// preserving the supplied member schema for field naming. Errors stop
65    /// serialization immediately. By default, no callback is installed.
66    pub fn with_struct_prefix(mut self, prefix: StructPrefix) -> Self {
67        self.struct_prefix = Some(prefix);
68        self
69    }
70
71    /// Writes the member name as a CBOR text string key, but only for structure
72    /// members. List elements and map values are not keyed by member name even
73    /// though their schemas carry one (see [`Container`]).
74    #[inline]
75    fn write_member_key(&mut self, schema: &Schema<'_>) {
76        if self.container_stack.last() == Some(&Container::Struct) {
77            if let Some(name) = schema.member_name() {
78                self.encoder.str(name);
79            }
80        }
81    }
82}
83
84impl FinishSerializer for CborSerializer {
85    fn finish(self) -> Vec<u8> {
86        self.encoder.into_writer()
87    }
88}
89
90impl ShapeSerializer for CborSerializer {
91    fn write_struct(
92        &mut self,
93        schema: &Schema<'_>,
94        value: &dyn SerializableStruct,
95    ) -> Result<(), SerdeError> {
96        self.write_member_key(schema);
97        self.encoder.begin_map();
98        self.container_stack.push(Container::Struct);
99        if let Some(prefix) = self.struct_prefix {
100            prefix(value.schema(), self)?;
101        }
102        value.serialize_members(self)?;
103        self.container_stack.pop();
104        self.encoder.end();
105        Ok(())
106    }
107
108    fn write_list(
109        &mut self,
110        schema: &Schema<'_>,
111        write_elements: &dyn Fn(&mut dyn ShapeSerializer) -> Result<(), SerdeError>,
112    ) -> Result<(), SerdeError> {
113        self.write_member_key(schema);
114        self.encoder.begin_array();
115        self.container_stack.push(Container::List);
116        write_elements(self)?;
117        self.container_stack.pop();
118        self.encoder.end();
119        Ok(())
120    }
121
122    fn write_map(
123        &mut self,
124        schema: &Schema<'_>,
125        write_entries: &dyn Fn(&mut dyn ShapeSerializer) -> Result<(), SerdeError>,
126    ) -> Result<(), SerdeError> {
127        self.write_member_key(schema);
128        self.encoder.begin_map();
129        self.container_stack.push(Container::Map);
130        write_entries(self)?;
131        self.container_stack.pop();
132        self.encoder.end();
133        Ok(())
134    }
135
136    fn write_list_with_length(
137        &mut self,
138        schema: &Schema<'_>,
139        length: usize,
140        write_elements: &dyn Fn(&mut dyn ShapeSerializer) -> Result<(), SerdeError>,
141    ) -> Result<(), SerdeError> {
142        if !self.use_definite_lengths {
143            return self.write_list(schema, write_elements);
144        }
145        self.write_member_key(schema);
146        self.encoder.array(length);
147        self.container_stack.push(Container::List);
148        let result = write_elements(self);
149        self.container_stack.pop();
150        result
151    }
152
153    fn write_map_with_length(
154        &mut self,
155        schema: &Schema<'_>,
156        length: usize,
157        write_entries: &dyn Fn(&mut dyn ShapeSerializer) -> Result<(), SerdeError>,
158    ) -> Result<(), SerdeError> {
159        if !self.use_definite_lengths {
160            return self.write_map(schema, write_entries);
161        }
162        self.write_member_key(schema);
163        self.encoder.map(length);
164        self.container_stack.push(Container::Map);
165        let result = write_entries(self);
166        self.container_stack.pop();
167        result
168    }
169
170    fn write_boolean(&mut self, schema: &Schema<'_>, value: bool) -> Result<(), SerdeError> {
171        self.write_member_key(schema);
172        self.encoder.boolean(value);
173        Ok(())
174    }
175
176    fn write_byte(&mut self, schema: &Schema<'_>, value: i8) -> Result<(), SerdeError> {
177        self.write_member_key(schema);
178        self.encoder.byte(value);
179        Ok(())
180    }
181
182    fn write_short(&mut self, schema: &Schema<'_>, value: i16) -> Result<(), SerdeError> {
183        self.write_member_key(schema);
184        self.encoder.short(value);
185        Ok(())
186    }
187
188    fn write_integer(&mut self, schema: &Schema<'_>, value: i32) -> Result<(), SerdeError> {
189        self.write_member_key(schema);
190        self.encoder.integer(value);
191        Ok(())
192    }
193
194    fn write_integer_list(
195        &mut self,
196        schema: &Schema<'_>,
197        values: &[i32],
198    ) -> Result<(), SerdeError> {
199        self.write_member_key(schema);
200        if self.use_definite_lengths {
201            self.encoder.array(values.len());
202        } else {
203            self.encoder.begin_array();
204        }
205        // Integer writes cannot fail or call back into the serializer, so
206        // neither per-element dispatch nor a new container state is needed.
207        for value in values {
208            self.encoder.integer(*value);
209        }
210        if !self.use_definite_lengths {
211            self.encoder.end();
212        }
213        Ok(())
214    }
215
216    fn write_long(&mut self, schema: &Schema<'_>, value: i64) -> Result<(), SerdeError> {
217        self.write_member_key(schema);
218        self.encoder.long(value);
219        Ok(())
220    }
221
222    fn write_float(&mut self, schema: &Schema<'_>, value: f32) -> Result<(), SerdeError> {
223        self.write_member_key(schema);
224        self.encoder.float(value);
225        Ok(())
226    }
227
228    fn write_double(&mut self, schema: &Schema<'_>, value: f64) -> Result<(), SerdeError> {
229        self.write_member_key(schema);
230        self.encoder.double(value);
231        Ok(())
232    }
233
234    fn write_big_integer(
235        &mut self,
236        schema: &Schema<'_>,
237        value: &BigInteger,
238    ) -> Result<(), SerdeError> {
239        self.write_member_key(schema);
240        self.encoder.big_integer(value);
241        Ok(())
242    }
243
244    fn write_big_decimal(
245        &mut self,
246        schema: &Schema<'_>,
247        value: &BigDecimal,
248    ) -> Result<(), SerdeError> {
249        self.write_member_key(schema);
250        self.encoder.big_decimal(value);
251        Ok(())
252    }
253
254    fn write_string(&mut self, schema: &Schema<'_>, value: &str) -> Result<(), SerdeError> {
255        self.write_member_key(schema);
256        self.encoder.str(value);
257        Ok(())
258    }
259
260    fn write_blob(&mut self, schema: &Schema<'_>, value: Blob) -> Result<(), SerdeError> {
261        self.write_member_key(schema);
262        self.encoder.blob_bytes(value.as_ref());
263        Ok(())
264    }
265
266    fn write_timestamp(&mut self, schema: &Schema<'_>, value: &DateTime) -> Result<(), SerdeError> {
267        self.write_member_key(schema);
268        self.encoder.timestamp(value);
269        Ok(())
270    }
271
272    fn write_document(
273        &mut self,
274        _schema: &Schema<'_>,
275        _value: &Document,
276    ) -> Result<(), SerdeError> {
277        Err(SerdeError::unsupported(
278            "document types are not supported by rpcv2Cbor protocol",
279        ))
280    }
281
282    fn write_null(&mut self, schema: &Schema<'_>) -> Result<(), SerdeError> {
283        self.write_member_key(schema);
284        self.encoder.null();
285        Ok(())
286    }
287}
288
289#[cfg(test)]
290mod tests {
291    use super::*;
292    use aws_smithy_schema::codec::{Codec, FinishSerializer};
293    use aws_smithy_schema::prelude::*;
294    use aws_smithy_schema::serde::ShapeSerializer;
295    use aws_smithy_schema::{shape_id, ShapeType};
296    use aws_smithy_types::Blob;
297
298    use crate::codec::CborCodec;
299
300    fn round_trip(f: impl FnOnce(&mut CborSerializer)) -> Vec<u8> {
301        let codec = CborCodec::default();
302        let mut ser = codec.create_serializer();
303        f(&mut ser);
304        ser.finish()
305    }
306
307    #[test]
308    fn test_write_boolean() {
309        let bytes = round_trip(|s| s.write_boolean(&BOOLEAN, true).unwrap());
310        let mut dec = crate::Decoder::new(&bytes);
311        assert!(dec.boolean().unwrap());
312    }
313
314    #[test]
315    fn test_write_integer() {
316        let bytes = round_trip(|s| s.write_integer(&INTEGER, 42).unwrap());
317        let mut dec = crate::Decoder::new(&bytes);
318        assert_eq!(dec.integer().unwrap(), 42);
319    }
320
321    #[test]
322    fn test_write_long() {
323        let bytes = round_trip(|s| s.write_long(&LONG, i64::MAX).unwrap());
324        let mut dec = crate::Decoder::new(&bytes);
325        assert_eq!(dec.long().unwrap(), i64::MAX);
326    }
327
328    #[test]
329    fn test_write_float_nan() {
330        let bytes = round_trip(|s| s.write_float(&FLOAT, f32::NAN).unwrap());
331        let mut dec = crate::Decoder::new(&bytes);
332        assert!(dec.float().unwrap().is_nan());
333    }
334
335    #[test]
336    fn test_write_double_infinity() {
337        let bytes = round_trip(|s| s.write_double(&DOUBLE, f64::INFINITY).unwrap());
338        let mut dec = crate::Decoder::new(&bytes);
339        assert_eq!(dec.double().unwrap(), f64::INFINITY);
340    }
341
342    #[test]
343    fn test_write_big_integer() {
344        let value = "18446744073709551616".parse().unwrap();
345        let bytes = round_trip(|s| s.write_big_integer(&BIG_INTEGER, &value).unwrap());
346        let mut dec = crate::Decoder::new(&bytes);
347        assert_eq!(dec.big_integer().unwrap(), value);
348    }
349
350    #[test]
351    fn test_write_big_decimal() {
352        let value = "273.15".parse().unwrap();
353        let bytes = round_trip(|s| s.write_big_decimal(&BIG_DECIMAL, &value).unwrap());
354        let mut dec = crate::Decoder::new(&bytes);
355        assert_eq!(dec.big_decimal().unwrap(), value);
356    }
357
358    #[test]
359    fn test_write_string() {
360        let bytes = round_trip(|s| s.write_string(&STRING, "hello").unwrap());
361        let mut dec = crate::Decoder::new(&bytes);
362        assert_eq!(dec.str().unwrap().as_ref(), "hello");
363    }
364
365    #[test]
366    fn test_write_blob() {
367        let blob = Blob::new(b"binary data");
368        let bytes = round_trip(|s| s.write_blob(&BLOB, blob.clone()).unwrap());
369        let mut dec = crate::Decoder::new(&bytes);
370        assert_eq!(dec.blob().unwrap(), blob);
371    }
372
373    #[test]
374    fn test_write_timestamp() {
375        let ts = DateTime::from_secs_f64(1700000000.5);
376        let bytes = round_trip(|s| s.write_timestamp(&TIMESTAMP, &ts).unwrap());
377        let mut dec = crate::Decoder::new(&bytes);
378        let decoded = dec.timestamp().unwrap();
379        // Timestamp truncates to millisecond precision
380        assert_eq!(decoded.as_secs_f64(), 1700000000.5);
381    }
382
383    #[test]
384    fn test_write_null() {
385        let bytes = round_trip(|s| s.write_null(&STRING).unwrap());
386        let mut dec = crate::Decoder::new(&bytes);
387        dec.null().unwrap();
388    }
389
390    #[test]
391    fn test_write_list() {
392        let list_schema = Schema::new(shape_id!("test", "List"), ShapeType::List);
393        let bytes = round_trip(|s| {
394            s.write_list(&list_schema, &|s| {
395                s.write_integer(&INTEGER, 1)?;
396                s.write_integer(&INTEGER, 2)?;
397                s.write_integer(&INTEGER, 3)?;
398                Ok(())
399            })
400            .unwrap()
401        });
402        let mut dec = crate::Decoder::new(&bytes);
403        // Indefinite-length array
404        let len = dec.list().unwrap();
405        assert!(len.is_none()); // indefinite
406        assert_eq!(dec.integer().unwrap(), 1);
407        assert_eq!(dec.integer().unwrap(), 2);
408        assert_eq!(dec.integer().unwrap(), 3);
409    }
410
411    #[test]
412    fn known_collection_lengths_are_opt_in() {
413        let schema = Schema::new(shape_id!("test", "List"), ShapeType::List);
414        for (enabled, expected) in [
415            (false, vec![0x9f, 1, 2, 3, 0xff]),
416            (true, vec![0x83, 1, 2, 3]),
417        ] {
418            let codec = CborCodec::new(
419                crate::codec::CborCodecSettings::default().use_definite_lengths(enabled),
420            );
421            let mut ser = codec.create_serializer();
422            ser.write_integer_list(&schema, &[1, 2, 3]).unwrap();
423            assert_eq!(ser.finish(), expected);
424        }
425    }
426
427    #[test]
428    fn integer_list_helper_matches_generic_writes_in_each_container() {
429        static VALUES: Schema = Schema::new_member(
430            shape_id!("test", "Value", "values"),
431            ShapeType::List,
432            "values",
433            0,
434        );
435        static NESTED: Schema = Schema::new_member(
436            shape_id!("test", "Value", "nested"),
437            ShapeType::List,
438            "nested",
439            1,
440        );
441        static MAP: Schema =
442            Schema::new_member(shape_id!("test", "Value", "map"), ShapeType::Map, "map", 2);
443        static AFTER: Schema = Schema::new_member(
444            shape_id!("test", "Value", "after"),
445            ShapeType::Integer,
446            "after",
447            3,
448        );
449        static VALUE: Schema = Schema::new_struct(
450            shape_id!("test", "Value"),
451            ShapeType::Structure,
452            &[&VALUES, &NESTED, &MAP, &AFTER],
453        );
454
455        struct Value<'a> {
456            values: &'a [i32],
457            helper: bool,
458        }
459        impl Value<'_> {
460            fn write_values(
461                &self,
462                ser: &mut dyn ShapeSerializer,
463                schema: &Schema<'_>,
464            ) -> Result<(), SerdeError> {
465                if self.helper {
466                    ser.write_integer_list(schema, self.values)
467                } else {
468                    ser.write_list_with_length(schema, self.values.len(), &|ser| {
469                        for value in self.values {
470                            ser.write_integer(&INTEGER, *value)?;
471                        }
472                        Ok(())
473                    })
474                }
475            }
476        }
477        impl SerializableStruct for Value<'_> {
478            fn schema(&self) -> &Schema<'_> {
479                &VALUE
480            }
481
482            fn serialize_members(&self, ser: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
483                self.write_values(ser, &VALUES)?;
484                ser.write_list_with_length(&NESTED, 1, &|ser| self.write_values(ser, &VALUES))?;
485                ser.write_map_with_length(&MAP, 1, &|ser| {
486                    ser.write_string(&STRING, "key")?;
487                    self.write_values(ser, &VALUES)
488                })?;
489                ser.write_integer(&AFTER, 17)
490            }
491        }
492
493        let integers = [i32::MIN, -25, -1, 0, 23, 24, i32::MAX];
494        for enabled in [false, true] {
495            let codec = CborCodec::new(
496                crate::codec::CborCodecSettings::default().use_definite_lengths(enabled),
497            );
498            for length in [0, 1, 23, 24, 255, 256] {
499                let values: Vec<_> = integers.into_iter().cycle().take(length).collect();
500                let mut generic = codec.create_serializer();
501                generic
502                    .write_struct(
503                        &VALUE,
504                        &Value {
505                            values: &values,
506                            helper: false,
507                        },
508                    )
509                    .unwrap();
510                let mut helper = codec.create_serializer();
511                helper
512                    .write_struct(
513                        &VALUE,
514                        &Value {
515                            values: &values,
516                            helper: true,
517                        },
518                    )
519                    .unwrap();
520                assert_eq!(
521                    helper.finish(),
522                    generic.finish(),
523                    "enabled={enabled}, length={length}"
524                );
525
526                // Independently compare the top-level array with the direct encoder.
527                let mut helper = codec.create_serializer();
528                helper.write_integer_list(&VALUES, &values).unwrap();
529                let mut encoder = crate::Encoder::new(Vec::new());
530                if enabled {
531                    encoder.array(length);
532                } else {
533                    encoder.begin_array();
534                }
535                for value in &values {
536                    encoder.integer(*value);
537                }
538                if !enabled {
539                    encoder.end();
540                }
541                assert_eq!(helper.finish(), encoder.into_writer());
542            }
543        }
544    }
545
546    #[test]
547    fn definite_collections_match_legacy_encoder_at_length_boundaries() {
548        let list = Schema::new(shape_id!("test", "List"), ShapeType::List);
549        let map = Schema::new(shape_id!("test", "Map"), ShapeType::Map);
550        let codec =
551            CborCodec::new(crate::codec::CborCodecSettings::default().use_definite_lengths(true));
552        for length in [0, 1, 23, 24, 255, 256] {
553            let mut ser = codec.create_serializer();
554            ser.write_list_with_length(&list, length, &|ser| {
555                for _ in 0..length {
556                    ser.write_null(&STRING)?;
557                }
558                Ok(())
559            })
560            .unwrap();
561            let mut legacy = crate::Encoder::new(Vec::new());
562            legacy.array(length);
563            for _ in 0..length {
564                legacy.null();
565            }
566            assert_eq!(ser.finish(), legacy.into_writer());
567
568            let mut ser = codec.create_serializer();
569            ser.write_map_with_length(&map, length, &|ser| {
570                for i in 0..length {
571                    ser.write_string(&STRING, &i.to_string())?;
572                    ser.write_null(&STRING)?;
573                }
574                Ok(())
575            })
576            .unwrap();
577            let mut legacy = crate::Encoder::new(Vec::new());
578            legacy.map(length);
579            for i in 0..length {
580                legacy.str(&i.to_string()).null();
581            }
582            assert_eq!(ser.finish(), legacy.into_writer());
583        }
584    }
585
586    #[test]
587    fn definite_nested_collections_preserve_keys_and_unknown_lengths() {
588        static VALUES: Schema = Schema::new_member(
589            shape_id!("test", "Value", "values"),
590            ShapeType::List,
591            "values",
592            0,
593        );
594        static MAP: Schema = Schema::new(shape_id!("test", "Map"), ShapeType::Map);
595        static VALUE: Schema =
596            Schema::new_struct(shape_id!("test", "Value"), ShapeType::Structure, &[&VALUES]);
597        struct Value;
598        impl SerializableStruct for Value {
599            fn schema(&self) -> &Schema<'_> {
600                &VALUE
601            }
602            fn serialize_members(&self, ser: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
603                ser.write_list_with_length(&VALUES, 2, &|ser| {
604                    ser.write_map_with_length(&MAP, 1, &|ser| {
605                        ser.write_string(&STRING, "key")?;
606                        ser.write_null(&STRING)
607                    })?;
608                    ser.write_list(&VALUES, &|ser| ser.write_integer(&INTEGER, 7))
609                })
610            }
611        }
612        let codec =
613            CborCodec::new(crate::codec::CborCodecSettings::default().use_definite_lengths(true));
614        let mut ser = codec.create_serializer();
615        ser.write_struct(&VALUE, &Value).unwrap();
616        let mut legacy = crate::Encoder::new(Vec::new());
617        legacy
618            .begin_map()
619            .str("values")
620            .array(2)
621            .map(1)
622            .str("key")
623            .null();
624        legacy.begin_array().integer(7).end().end();
625        assert_eq!(ser.finish(), legacy.into_writer());
626    }
627
628    #[test]
629    fn test_write_struct() {
630        static NAME_MEMBER: Schema =
631            Schema::new_member(shape_id!("test", "Struct"), ShapeType::String, "name", 0);
632        static AGE_MEMBER: Schema =
633            Schema::new_member(shape_id!("test", "Struct"), ShapeType::Integer, "age", 1);
634        static STRUCT_SCHEMA: Schema = Schema::new_struct(
635            shape_id!("test", "Struct"),
636            ShapeType::Structure,
637            &[&NAME_MEMBER, &AGE_MEMBER],
638        );
639
640        struct TestStruct;
641        impl SerializableStruct for TestStruct {
642            fn schema(&self) -> &Schema<'_> {
643                &STRUCT_SCHEMA
644            }
645
646            fn serialize_members(&self, s: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
647                s.write_string(&NAME_MEMBER, "Alice")?;
648                s.write_integer(&AGE_MEMBER, 30)?;
649                Ok(())
650            }
651        }
652
653        let bytes = round_trip(|s| s.write_struct(&STRUCT_SCHEMA, &TestStruct).unwrap());
654        let mut dec = crate::Decoder::new(&bytes);
655        let len = dec.map().unwrap();
656        assert!(len.is_none()); // indefinite-length map
657        assert_eq!(dec.str().unwrap().as_ref(), "name");
658        assert_eq!(dec.str().unwrap().as_ref(), "Alice");
659        assert_eq!(dec.str().unwrap().as_ref(), "age");
660        assert_eq!(dec.integer().unwrap(), 30);
661    }
662
663    mod struct_prefix {
664        use super::*;
665
666        static NAME: Schema = Schema::new_member(
667            shape_id!("test", "Value", "name"),
668            ShapeType::String,
669            "name",
670            0,
671        );
672        static VALUE: Schema =
673            Schema::new_struct(shape_id!("test", "Value"), ShapeType::Structure, &[&NAME]);
674        static MEMBER: Schema = Schema::new_member(
675            shape_id!("test", "Container", "value"),
676            ShapeType::Structure,
677            "value",
678            0,
679        );
680        static PREFIX: Schema = Schema::new_member(
681            shape_id!("test", "Value", "prefix"),
682            ShapeType::String,
683            "prefix",
684            1,
685        );
686
687        struct Value;
688        impl SerializableStruct for Value {
689            fn schema(&self) -> &Schema<'_> {
690                &VALUE
691            }
692
693            fn serialize_members(
694                &self,
695                serializer: &mut dyn ShapeSerializer,
696            ) -> Result<(), SerdeError> {
697                serializer.write_string(&NAME, "Alice")
698            }
699        }
700
701        #[test]
702        fn absent_by_default() {
703            let mut serializer = CborCodec::default().create_serializer();
704            serializer.write_struct(&VALUE, &Value).unwrap();
705            let mut expected = crate::Encoder::new(Vec::new());
706            expected.begin_map().str("name").str("Alice").end();
707            assert_eq!(serializer.finish(), expected.into_writer());
708        }
709
710        #[test]
711        fn prefix_precedes_members_and_receives_value_schema() {
712            let mut serializer = CborCodec::default().create_serializer().with_struct_prefix(
713                |schema, serializer| {
714                    let expected = &VALUE;
715                    assert_eq!(schema.shape_id(), expected.shape_id());
716                    assert_eq!(schema.member_name(), expected.member_name());
717                    serializer.write_string(&PREFIX, schema.shape_id().as_str())
718                },
719            );
720            serializer
721                .write_list(
722                    &Schema::new(shape_id!("test", "Values"), ShapeType::List),
723                    &|serializer| serializer.write_struct(&MEMBER, &Value),
724                )
725                .unwrap();
726            let mut expected = crate::Encoder::new(Vec::new());
727            expected
728                .begin_array()
729                .begin_map()
730                .str("prefix")
731                .str(VALUE.shape_id().as_str())
732                .str("name")
733                .str("Alice")
734                .end()
735                .end();
736            assert_eq!(serializer.finish(), expected.into_writer());
737        }
738
739        #[test]
740        fn prefix_failure_prevents_modeled_members() {
741            struct UnreachableMembers;
742            impl SerializableStruct for UnreachableMembers {
743                fn schema(&self) -> &Schema<'_> {
744                    &VALUE
745                }
746
747                fn serialize_members(&self, _: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
748                    panic!("modeled members must not be serialized after prefix failure")
749                }
750            }
751            let mut serializer = CborCodec::default()
752                .create_serializer()
753                .with_struct_prefix(|_, _| Err(SerdeError::custom("prefix failed")));
754            let error = serializer
755                .write_struct(&VALUE, &UnreachableMembers)
756                .unwrap_err();
757            assert!(error.to_string().contains("prefix failed"));
758            assert_eq!(serializer.finish(), vec![0xbf]);
759        }
760    }
761
762    #[test]
763    fn test_write_map() {
764        let map_schema = Schema::new(shape_id!("test", "Map"), ShapeType::Map);
765        let bytes = round_trip(|s| {
766            s.write_map(&map_schema, &|s| {
767                s.write_string(&STRING, "key1")?;
768                s.write_string(&STRING, "val1")?;
769                Ok(())
770            })
771            .unwrap()
772        });
773        let mut dec = crate::Decoder::new(&bytes);
774        let len = dec.map().unwrap();
775        assert!(len.is_none());
776        assert_eq!(dec.str().unwrap().as_ref(), "key1");
777        assert_eq!(dec.str().unwrap().as_ref(), "val1");
778    }
779
780    // --- Tagged-union request serialization over CBOR.
781    //
782    // A tagged union serializes as a single-entry CBOR map keyed by the active
783    // variant name (`{variantName: value}`), nested inside the surrounding
784    // indefinite-length map of the request. This mirrors a `CreateConnection`-
785    // style request whose input carries tagged-union members. The union's
786    // `serialize_members` writes exactly the active variant (mirroring the
787    // generated `renderSerializableUnion`). Verified at the wire-byte level
788    // (exact bytes via the Encoder) and by full serialize -> deserialize
789    // round-trip, including a scalar variant, a struct variant, two union
790    // members in one input, and indefinite-length map acceptance.
791    mod tagged_union_serialization {
792        use crate::codec::{CborCodec, CborDeserializer, CborSerializer};
793        use aws_smithy_schema::codec::{Codec, FinishSerializer};
794        use aws_smithy_schema::serde::{
795            SerdeError, SerializableStruct, ShapeDeserializer, ShapeSerializer,
796        };
797        use aws_smithy_schema::{shape_id, Schema, ShapeType};
798
799        fn serialize(f: impl FnOnce(&mut CborSerializer)) -> Vec<u8> {
800            let codec = CborCodec::default();
801            let mut ser = codec.create_serializer();
802            f(&mut ser);
803            ser.finish()
804        }
805
806        // Union `Endpoint { arn: String, config: EndpointConfig }`.
807        static EP_ARN: Schema =
808            Schema::new_member(shape_id!("test", "Endpoint"), ShapeType::String, "arn", 0);
809        static EP_CONFIG: Schema = Schema::new_member(
810            shape_id!("test", "Endpoint"),
811            ShapeType::Structure,
812            "config",
813            1,
814        );
815        static ENDPOINT_SCHEMA: Schema = Schema::new_struct(
816            shape_id!("test", "Endpoint"),
817            ShapeType::Union,
818            &[&EP_ARN, &EP_CONFIG],
819        );
820
821        // Struct `EndpointConfig { name: String }`.
822        static EC_NAME: Schema = Schema::new_member(
823            shape_id!("test", "EndpointConfig"),
824            ShapeType::String,
825            "name",
826            0,
827        );
828        static ENDPOINT_CONFIG_SCHEMA: Schema = Schema::new_struct(
829            shape_id!("test", "EndpointConfig"),
830            ShapeType::Structure,
831            &[&EC_NAME],
832        );
833
834        // Input `CreateConnectionInput { attachPoint: Endpoint, remoteAccount: Endpoint }`.
835        static CC_ATTACH: Schema = Schema::new_member(
836            shape_id!("test", "CreateConnectionInput"),
837            ShapeType::Union,
838            "attachPoint",
839            0,
840        );
841        static CC_REMOTE: Schema = Schema::new_member(
842            shape_id!("test", "CreateConnectionInput"),
843            ShapeType::Union,
844            "remoteAccount",
845            1,
846        );
847        static CREATE_CONNECTION_SCHEMA: Schema = Schema::new_struct(
848            shape_id!("test", "CreateConnectionInput"),
849            ShapeType::Structure,
850            &[&CC_ATTACH, &CC_REMOTE],
851        );
852
853        #[derive(Debug, Clone, PartialEq)]
854        struct EndpointConfig {
855            name: String,
856        }
857        impl SerializableStruct for EndpointConfig {
858            fn schema(&self) -> &Schema<'_> {
859                &ENDPOINT_CONFIG_SCHEMA
860            }
861
862            fn serialize_members(&self, s: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
863                s.write_string(&EC_NAME, &self.name)
864            }
865        }
866
867        #[derive(Debug, Clone, PartialEq)]
868        enum Endpoint {
869            Arn(String),
870            Config(EndpointConfig),
871        }
872        impl SerializableStruct for Endpoint {
873            fn schema(&self) -> &Schema<'_> {
874                &ENDPOINT_SCHEMA
875            }
876
877            fn serialize_members(&self, s: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
878                // Exactly the active variant is written (mirrors renderSerializableUnion).
879                match self {
880                    Endpoint::Arn(v) => s.write_string(&EP_ARN, v),
881                    Endpoint::Config(v) => s.write_struct(&EP_CONFIG, v),
882                }
883            }
884        }
885
886        #[derive(Debug, Clone, PartialEq)]
887        struct CreateConnectionInput {
888            attach_point: Endpoint,
889            remote_account: Endpoint,
890        }
891        impl SerializableStruct for CreateConnectionInput {
892            fn schema(&self) -> &Schema<'_> {
893                &CREATE_CONNECTION_SCHEMA
894            }
895
896            fn serialize_members(&self, s: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
897                s.write_struct(&CC_ATTACH, &self.attach_point)?;
898                s.write_struct(&CC_REMOTE, &self.remote_account)
899            }
900        }
901
902        // deserialize mirrors renderDeserializeUnion / struct deserialize.
903        fn deser_endpoint_config(
904            d: &mut dyn ShapeDeserializer,
905        ) -> Result<EndpointConfig, SerdeError> {
906            let mut name = String::new();
907            d.read_struct(&ENDPOINT_CONFIG_SCHEMA, &mut |m, de| {
908                if m.member_name() == Some("name") {
909                    name = de.read_string(m)?;
910                }
911                Ok(())
912            })?;
913            Ok(EndpointConfig { name })
914        }
915        fn deser_endpoint(d: &mut dyn ShapeDeserializer) -> Result<Endpoint, SerdeError> {
916            let mut out: Option<Endpoint> = None;
917            d.read_struct(&ENDPOINT_SCHEMA, &mut |m, de| {
918                out = Some(match m.member_index() {
919                    Some(0) => Endpoint::Arn(de.read_string(m)?),
920                    Some(1) => Endpoint::Config(deser_endpoint_config(de)?),
921                    _ => return Err(SerdeError::custom("unknown variant")),
922                });
923                Ok(())
924            })?;
925            out.ok_or_else(|| SerdeError::custom("expected a union variant"))
926        }
927        fn deser_create_connection(
928            d: &mut dyn ShapeDeserializer,
929        ) -> Result<CreateConnectionInput, SerdeError> {
930            let mut ap: Option<Endpoint> = None;
931            let mut ra: Option<Endpoint> = None;
932            d.read_struct(&CREATE_CONNECTION_SCHEMA, &mut |m, de| {
933                match m.member_index() {
934                    Some(0) => ap = Some(deser_endpoint(de)?),
935                    Some(1) => ra = Some(deser_endpoint(de)?),
936                    _ => {}
937                }
938                Ok(())
939            })?;
940            Ok(CreateConnectionInput {
941                attach_point: ap.ok_or_else(|| SerdeError::custom("missing attachPoint"))?,
942                remote_account: ra.ok_or_else(|| SerdeError::custom("missing remoteAccount"))?,
943            })
944        }
945
946        #[test]
947        fn scalar_variant_wire_bytes() {
948            let bytes = serialize(|s| {
949                s.write_struct(&ENDPOINT_SCHEMA, &Endpoint::Arn("dxcon-abc".to_string()))
950                    .unwrap()
951            });
952            // A single-entry indefinite-length map keyed by the variant name.
953            let mut expected = crate::Encoder::new(Vec::new());
954            expected.begin_map().str("arn").str("dxcon-abc").end();
955            assert_eq!(
956                bytes,
957                expected.into_writer(),
958                "tagged union must be a single-entry map keyed by the active variant name"
959            );
960            assert_eq!(bytes[0], 0xBF, "must use an indefinite-length map (0xBF)");
961            assert_eq!(
962                *bytes.last().unwrap(),
963                0xFF,
964                "must be terminated by a break (0xFF)"
965            );
966        }
967
968        #[test]
969        fn struct_variant_wire_bytes() {
970            let bytes = serialize(|s| {
971                s.write_struct(
972                    &ENDPOINT_SCHEMA,
973                    &Endpoint::Config(EndpointConfig {
974                        name: "eth0".to_string(),
975                    }),
976                )
977                .unwrap()
978            });
979            let mut expected = crate::Encoder::new(Vec::new());
980            expected
981                .begin_map()
982                .str("config")
983                .begin_map()
984                .str("name")
985                .str("eth0")
986                .end()
987                .end();
988            assert_eq!(bytes, expected.into_writer());
989        }
990
991        #[test]
992        fn two_union_members_in_one_input_wire_bytes() {
993            let input = CreateConnectionInput {
994                attach_point: Endpoint::Arn("ap".to_string()),
995                remote_account: Endpoint::Config(EndpointConfig {
996                    name: "ra".to_string(),
997                }),
998            };
999            let bytes = serialize(|s| s.write_struct(&CREATE_CONNECTION_SCHEMA, &input).unwrap());
1000            let mut expected = crate::Encoder::new(Vec::new());
1001            expected
1002                .begin_map()
1003                .str("attachPoint")
1004                .begin_map()
1005                .str("arn")
1006                .str("ap")
1007                .end()
1008                .str("remoteAccount")
1009                .begin_map()
1010                .str("config")
1011                .begin_map()
1012                .str("name")
1013                .str("ra")
1014                .end()
1015                .end()
1016                .end();
1017            assert_eq!(bytes, expected.into_writer());
1018        }
1019
1020        #[test]
1021        fn tagged_unions_round_trip() {
1022            let input = CreateConnectionInput {
1023                attach_point: Endpoint::Arn("dxcon-1".to_string()),
1024                remote_account: Endpoint::Config(EndpointConfig {
1025                    name: "primary".to_string(),
1026                }),
1027            };
1028            let bytes = serialize(|s| s.write_struct(&CREATE_CONNECTION_SCHEMA, &input).unwrap());
1029            // 128 = codec default max_depth.
1030            let mut de = CborDeserializer::new(&bytes, 128);
1031            let decoded = deser_create_connection(&mut de).unwrap();
1032            assert_eq!(
1033                decoded, input,
1034                "serialize -> deserialize must round-trip the tagged unions"
1035            );
1036        }
1037    }
1038}