Skip to main content

aws_smithy_cbor/codec/
deserializer.rs

1/*
2 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 * SPDX-License-Identifier: Apache-2.0
4 */
5
6//! CBOR deserializer implementation.
7
8use aws_smithy_schema::serde::{capped_container_size, SerdeError, ShapeDeserializer};
9use aws_smithy_schema::Schema;
10use aws_smithy_types::{BigDecimal, BigInteger, Blob, DateTime, Document};
11
12use crate::data::Type;
13
14/// CBOR deserializer that implements the ShapeDeserializer trait.
15///
16/// Wraps the existing `Decoder` which handles both definite and
17/// indefinite-length strings/blobs and includes millisecond-precision
18/// timestamp truncation.
19pub struct CborDeserializer<'a> {
20    decoder: crate::Decoder<'a>,
21    input_len: usize,
22    depth: u32,
23    max_depth: u32,
24}
25
26impl<'a> CborDeserializer<'a> {
27    pub(crate) fn new(input: &'a [u8], max_depth: u32) -> Self {
28        Self {
29            decoder: crate::Decoder::new(input),
30            input_len: input.len(),
31            depth: 0,
32            max_depth,
33        }
34    }
35
36    fn check_depth(&mut self) -> Result<(), SerdeError> {
37        self.depth += 1;
38        if self.depth > self.max_depth {
39            return Err(SerdeError::custom("maximum nesting depth exceeded"));
40        }
41        Ok(())
42    }
43
44    /// Returns true if the current CBOR item is a break code (end of indefinite container).
45    fn is_break(&self) -> bool {
46        matches!(self.decoder.datatype(), Ok(Type::Break))
47    }
48
49    /// Skips the break code at the end of an indefinite-length container.
50    fn consume_break(&mut self) -> Result<(), SerdeError> {
51        self.decoder.skip().map_err(deser_err)
52    }
53
54    /// Reads a list of items using the provided element reader, handling
55    /// both definite and indefinite-length arrays.
56    fn read_list_items<T>(
57        &mut self,
58        mut read_element: impl FnMut(&mut crate::Decoder<'_>) -> Result<T, SerdeError>,
59    ) -> Result<Vec<T>, SerdeError> {
60        self.check_depth()?;
61        let len = self.decoder.list().map_err(deser_err)?;
62        let is_indefinite = len.is_none();
63        let count = len.unwrap_or(0) as usize;
64        let mut out = Vec::with_capacity(capped_container_size(count));
65        let mut i = 0;
66        loop {
67            if !is_indefinite && i >= count {
68                break;
69            }
70            if is_indefinite && self.is_break() {
71                self.consume_break()?;
72                break;
73            }
74            out.push(read_element(&mut self.decoder)?);
75            i += 1;
76        }
77        self.depth -= 1;
78        Ok(out)
79    }
80}
81
82impl ShapeDeserializer for CborDeserializer<'_> {
83    fn read_struct(
84        &mut self,
85        schema: &Schema,
86        consumer: &mut dyn FnMut(&Schema, &mut dyn ShapeDeserializer) -> Result<(), SerdeError>,
87    ) -> Result<(), SerdeError> {
88        // Empty input (e.g., empty HTTP response body) is treated as an empty struct
89        if self.decoder.position() >= self.input_len {
90            return Ok(());
91        }
92        self.check_depth()?;
93        let len = self.decoder.map().map_err(deser_err)?;
94        let is_indefinite = len.is_none();
95        let count = len.unwrap_or(0) as usize;
96
97        let mut i = 0;
98        loop {
99            if !is_indefinite && i >= count {
100                break;
101            }
102            if is_indefinite && self.is_break() {
103                self.consume_break()?;
104                break;
105            }
106            let key = self.decoder.str().map_err(deser_err)?;
107            if let Some(member_schema) = schema.member_schema(&key) {
108                consumer(member_schema, self)?;
109            } else {
110                self.decoder.skip().map_err(deser_err)?;
111            }
112            i += 1;
113        }
114        self.depth -= 1;
115        Ok(())
116    }
117
118    fn read_list(
119        &mut self,
120        _schema: &Schema,
121        consumer: &mut dyn FnMut(&mut dyn ShapeDeserializer) -> Result<(), SerdeError>,
122    ) -> Result<(), SerdeError> {
123        self.check_depth()?;
124        let len = self.decoder.list().map_err(deser_err)?;
125        let is_indefinite = len.is_none();
126        let count = len.unwrap_or(0) as usize;
127
128        let mut i = 0;
129        loop {
130            if !is_indefinite && i >= count {
131                break;
132            }
133            if is_indefinite && self.is_break() {
134                self.consume_break()?;
135                break;
136            }
137            consumer(self)?;
138            i += 1;
139        }
140        self.depth -= 1;
141        Ok(())
142    }
143
144    fn read_map(
145        &mut self,
146        _schema: &Schema,
147        consumer: &mut dyn FnMut(String, &mut dyn ShapeDeserializer) -> Result<(), SerdeError>,
148    ) -> Result<(), SerdeError> {
149        self.check_depth()?;
150        let len = self.decoder.map().map_err(deser_err)?;
151        let is_indefinite = len.is_none();
152        let count = len.unwrap_or(0) as usize;
153
154        let mut i = 0;
155        loop {
156            if !is_indefinite && i >= count {
157                break;
158            }
159            if is_indefinite && self.is_break() {
160                self.consume_break()?;
161                break;
162            }
163            let key = self.decoder.str().map_err(deser_err)?.into_owned();
164            consumer(key, self)?;
165            i += 1;
166        }
167        self.depth -= 1;
168        Ok(())
169    }
170
171    fn read_boolean(&mut self, _schema: &Schema) -> Result<bool, SerdeError> {
172        self.decoder.boolean().map_err(deser_err)
173    }
174
175    fn read_byte(&mut self, _schema: &Schema) -> Result<i8, SerdeError> {
176        self.decoder.byte().map_err(deser_err)
177    }
178
179    fn read_short(&mut self, _schema: &Schema) -> Result<i16, SerdeError> {
180        self.decoder.short().map_err(deser_err)
181    }
182
183    fn read_integer(&mut self, _schema: &Schema) -> Result<i32, SerdeError> {
184        self.decoder.integer().map_err(deser_err)
185    }
186
187    fn read_long(&mut self, _schema: &Schema) -> Result<i64, SerdeError> {
188        self.decoder.long().map_err(deser_err)
189    }
190
191    fn read_float(&mut self, _schema: &Schema) -> Result<f32, SerdeError> {
192        self.decoder.float().map_err(deser_err)
193    }
194
195    fn read_double(&mut self, _schema: &Schema) -> Result<f64, SerdeError> {
196        self.decoder.double().map_err(deser_err)
197    }
198
199    fn read_big_integer(&mut self, _schema: &Schema) -> Result<BigInteger, SerdeError> {
200        Err(SerdeError::UnsupportedOperation {
201            message: "CBOR big integer not yet supported (smithy-rs#4611)".into(),
202        })
203    }
204
205    fn read_big_decimal(&mut self, _schema: &Schema) -> Result<BigDecimal, SerdeError> {
206        Err(SerdeError::UnsupportedOperation {
207            message: "CBOR big decimal not yet supported (smithy-rs#4611)".into(),
208        })
209    }
210
211    fn read_string(&mut self, _schema: &Schema) -> Result<String, SerdeError> {
212        self.decoder
213            .str()
214            .map(|cow| cow.into_owned())
215            .map_err(deser_err)
216    }
217
218    fn read_blob(&mut self, _schema: &Schema) -> Result<Blob, SerdeError> {
219        self.decoder.blob().map_err(deser_err)
220    }
221
222    fn read_timestamp(&mut self, _schema: &Schema) -> Result<DateTime, SerdeError> {
223        self.decoder.timestamp().map_err(deser_err)
224    }
225
226    fn read_document(&mut self, _schema: &Schema) -> Result<Document, SerdeError> {
227        Err(SerdeError::UnsupportedOperation {
228            message: "document types are not supported by rpcv2Cbor protocol".into(),
229        })
230    }
231
232    fn is_null(&self) -> bool {
233        matches!(self.decoder.datatype(), Ok(Type::Null))
234    }
235
236    fn read_null(&mut self) -> Result<(), SerdeError> {
237        self.decoder.null().map_err(deser_err)
238    }
239
240    fn container_size(&self) -> Option<usize> {
241        let mut peek = self.decoder.clone();
242        match peek.datatype().ok()? {
243            Type::Array | Type::ArrayIndef => {
244                peek.list().ok()?.map(|n| capped_container_size(n as usize))
245            }
246            Type::Map | Type::MapIndef => {
247                peek.map().ok()?.map(|n| capped_container_size(n as usize))
248            }
249            _ => None,
250        }
251    }
252
253    fn read_string_list(&mut self, _schema: &Schema) -> Result<Vec<String>, SerdeError> {
254        self.read_list_items(|dec| dec.str().map(|c| c.into_owned()).map_err(deser_err))
255    }
256
257    fn read_blob_list(&mut self, _schema: &Schema) -> Result<Vec<Blob>, SerdeError> {
258        self.read_list_items(|dec| dec.blob().map_err(deser_err))
259    }
260
261    fn read_integer_list(&mut self, _schema: &Schema) -> Result<Vec<i32>, SerdeError> {
262        self.read_list_items(|dec| dec.integer().map_err(deser_err))
263    }
264
265    fn read_long_list(&mut self, _schema: &Schema) -> Result<Vec<i64>, SerdeError> {
266        self.read_list_items(|dec| dec.long().map_err(deser_err))
267    }
268
269    fn read_string_string_map(
270        &mut self,
271        _schema: &Schema,
272    ) -> Result<std::collections::HashMap<String, String>, SerdeError> {
273        self.check_depth()?;
274        let len = self.decoder.map().map_err(deser_err)?;
275        let is_indefinite = len.is_none();
276        let count = len.unwrap_or(0) as usize;
277        let mut out = std::collections::HashMap::with_capacity(capped_container_size(count));
278        let mut i = 0;
279        loop {
280            if !is_indefinite && i >= count {
281                break;
282            }
283            if is_indefinite && self.is_break() {
284                self.consume_break()?;
285                break;
286            }
287            let key = self
288                .decoder
289                .str()
290                .map(|c| c.into_owned())
291                .map_err(deser_err)?;
292            let val = self
293                .decoder
294                .str()
295                .map(|c| c.into_owned())
296                .map_err(deser_err)?;
297            out.insert(key, val);
298            i += 1;
299        }
300        self.depth -= 1;
301        Ok(out)
302    }
303}
304
305fn deser_err(e: crate::decode::DeserializeError) -> SerdeError {
306    SerdeError::InvalidInput {
307        message: e.to_string(),
308    }
309}
310
311#[cfg(test)]
312mod tests {
313    use super::*;
314    use aws_smithy_schema::codec::Codec;
315    use aws_smithy_schema::prelude::*;
316    use aws_smithy_schema::serde::{SerializableStruct, ShapeSerializer};
317    use aws_smithy_schema::{shape_id, ShapeType};
318
319    use crate::codec::CborCodec;
320
321    /// Helper: serialize with CborSerializer, then deserialize with CborDeserializer.
322    fn make_deser(f: impl FnOnce(&mut crate::codec::CborSerializer)) -> Vec<u8> {
323        use aws_smithy_schema::codec::FinishSerializer;
324        let codec = CborCodec::default();
325        let mut ser = codec.create_serializer();
326        f(&mut ser);
327        ser.finish()
328    }
329
330    #[test]
331    fn test_read_boolean() {
332        let bytes = make_deser(|s| s.write_boolean(&BOOLEAN, true).unwrap());
333        let mut de = CborDeserializer::new(&bytes, 128);
334        assert_eq!(de.read_boolean(&BOOLEAN).unwrap(), true);
335    }
336
337    #[test]
338    fn test_read_byte() {
339        let bytes = make_deser(|s| s.write_byte(&BYTE, -42).unwrap());
340        let mut de = CborDeserializer::new(&bytes, 128);
341        assert_eq!(de.read_byte(&BYTE).unwrap(), -42);
342    }
343
344    #[test]
345    fn test_read_short() {
346        let bytes = make_deser(|s| s.write_short(&SHORT, 1234).unwrap());
347        let mut de = CborDeserializer::new(&bytes, 128);
348        assert_eq!(de.read_short(&SHORT).unwrap(), 1234);
349    }
350
351    #[test]
352    fn test_read_integer() {
353        let bytes = make_deser(|s| s.write_integer(&INTEGER, -99999).unwrap());
354        let mut de = CborDeserializer::new(&bytes, 128);
355        assert_eq!(de.read_integer(&INTEGER).unwrap(), -99999);
356    }
357
358    #[test]
359    fn test_read_long() {
360        let bytes = make_deser(|s| s.write_long(&LONG, i64::MIN).unwrap());
361        let mut de = CborDeserializer::new(&bytes, 128);
362        assert_eq!(de.read_long(&LONG).unwrap(), i64::MIN);
363    }
364
365    #[test]
366    fn test_read_float_nan() {
367        let bytes = make_deser(|s| s.write_float(&FLOAT, f32::NAN).unwrap());
368        let mut de = CborDeserializer::new(&bytes, 128);
369        assert!(de.read_float(&FLOAT).unwrap().is_nan());
370    }
371
372    #[test]
373    fn test_read_double_neg_infinity() {
374        let bytes = make_deser(|s| s.write_double(&DOUBLE, f64::NEG_INFINITY).unwrap());
375        let mut de = CborDeserializer::new(&bytes, 128);
376        assert_eq!(de.read_double(&DOUBLE).unwrap(), f64::NEG_INFINITY);
377    }
378
379    #[test]
380    fn test_read_string() {
381        let bytes = make_deser(|s| s.write_string(&STRING, "hello world").unwrap());
382        let mut de = CborDeserializer::new(&bytes, 128);
383        assert_eq!(de.read_string(&STRING).unwrap(), "hello world");
384    }
385
386    #[test]
387    fn test_read_blob() {
388        let blob = Blob::new(b"binary");
389        let bytes = make_deser(|s| s.write_blob(&BLOB, blob.as_ref()).unwrap());
390        let mut de = CborDeserializer::new(&bytes, 128);
391        assert_eq!(de.read_blob(&BLOB).unwrap(), blob);
392    }
393
394    #[test]
395    fn test_read_timestamp() {
396        let ts = DateTime::from_secs_f64(1700000000.123);
397        let bytes = make_deser(|s| s.write_timestamp(&TIMESTAMP, &ts).unwrap());
398        let mut de = CborDeserializer::new(&bytes, 128);
399        let decoded = de.read_timestamp(&TIMESTAMP).unwrap();
400        // Millisecond truncation
401        assert_eq!(decoded.subsec_nanos() % 1_000_000, 0);
402    }
403
404    #[test]
405    fn test_is_null() {
406        let bytes = make_deser(|s| s.write_null(&STRING).unwrap());
407        let de = CborDeserializer::new(&bytes, 128);
408        assert!(de.is_null());
409    }
410
411    #[test]
412    fn test_read_null() {
413        let bytes = make_deser(|s| s.write_null(&STRING).unwrap());
414        let mut de = CborDeserializer::new(&bytes, 128);
415        de.read_null().unwrap();
416    }
417
418    #[test]
419    fn test_read_struct() {
420        static NAME: Schema =
421            Schema::new_member(shape_id!("test", "S"), ShapeType::String, "name", 0);
422        static AGE: Schema =
423            Schema::new_member(shape_id!("test", "S"), ShapeType::Integer, "age", 1);
424        static SCHEMA: Schema =
425            Schema::new_struct(shape_id!("test", "S"), ShapeType::Structure, &[&NAME, &AGE]);
426
427        struct TestStruct;
428        impl SerializableStruct for TestStruct {
429            fn serialize_members(&self, s: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
430                s.write_string(&NAME, "Bob")?;
431                s.write_integer(&AGE, 25)?;
432                Ok(())
433            }
434        }
435
436        let bytes = make_deser(|s| s.write_struct(&SCHEMA, &TestStruct).unwrap());
437        let mut de = CborDeserializer::new(&bytes, 128);
438        let mut name = String::new();
439        let mut age = 0i32;
440        de.read_struct(&SCHEMA, &mut |member, d| {
441            match member.member_name() {
442                Some("name") => name = d.read_string(member)?,
443                Some("age") => age = d.read_integer(member)?,
444                _ => {}
445            }
446            Ok(())
447        })
448        .unwrap();
449        assert_eq!(name, "Bob");
450        assert_eq!(age, 25);
451    }
452
453    #[test]
454    fn test_read_struct_unknown_members_skipped() {
455        // Build CBOR with an extra field not in the schema
456        let mut enc = crate::Encoder::new(Vec::new());
457        enc.begin_map()
458            .str("name")
459            .str("Alice")
460            .str("unknown_field")
461            .integer(999)
462            .str("age")
463            .integer(30)
464            .end();
465        let bytes = enc.into_writer();
466
467        static NAME: Schema =
468            Schema::new_member(shape_id!("test", "S"), ShapeType::String, "name", 0);
469        static AGE: Schema =
470            Schema::new_member(shape_id!("test", "S"), ShapeType::Integer, "age", 1);
471        static SCHEMA: Schema =
472            Schema::new_struct(shape_id!("test", "S"), ShapeType::Structure, &[&NAME, &AGE]);
473
474        let mut de = CborDeserializer::new(&bytes, 128);
475        let mut name = String::new();
476        let mut age = 0i32;
477        de.read_struct(&SCHEMA, &mut |member, d| {
478            match member.member_name() {
479                Some("name") => name = d.read_string(member)?,
480                Some("age") => age = d.read_integer(member)?,
481                _ => {}
482            }
483            Ok(())
484        })
485        .unwrap();
486        assert_eq!(name, "Alice");
487        assert_eq!(age, 30);
488    }
489
490    #[test]
491    fn test_read_struct_definite_length_map() {
492        // Services may send definite-length maps
493        let mut enc = crate::Encoder::new(Vec::new());
494        enc.map(1).str("name").str("Charlie");
495        let bytes = enc.into_writer();
496
497        static NAME: Schema =
498            Schema::new_member(shape_id!("test", "S"), ShapeType::String, "name", 0);
499        static SCHEMA: Schema =
500            Schema::new_struct(shape_id!("test", "S"), ShapeType::Structure, &[&NAME]);
501
502        let mut de = CborDeserializer::new(&bytes, 128);
503        let mut name = String::new();
504        de.read_struct(&SCHEMA, &mut |member, d| {
505            if member.member_name() == Some("name") {
506                name = d.read_string(member)?;
507            }
508            Ok(())
509        })
510        .unwrap();
511        assert_eq!(name, "Charlie");
512    }
513
514    #[test]
515    fn test_read_list() {
516        let bytes = make_deser(|s| {
517            let schema = Schema::new(shape_id!("test", "L"), ShapeType::List);
518            s.write_list(&schema, &|s| {
519                s.write_integer(&INTEGER, 10)?;
520                s.write_integer(&INTEGER, 20)?;
521                Ok(())
522            })
523            .unwrap()
524        });
525        let list_schema = Schema::new(shape_id!("test", "L"), ShapeType::List);
526        let mut de = CborDeserializer::new(&bytes, 128);
527        let mut items = Vec::new();
528        de.read_list(&list_schema, &mut |d| {
529            items.push(d.read_integer(&INTEGER)?);
530            Ok(())
531        })
532        .unwrap();
533        assert_eq!(items, vec![10, 20]);
534    }
535
536    #[test]
537    fn test_read_map() {
538        let bytes = make_deser(|s| {
539            let schema = Schema::new(shape_id!("test", "M"), ShapeType::Map);
540            s.write_map(&schema, &|s| {
541                s.write_string(&STRING, "k1")?;
542                s.write_string(&STRING, "v1")?;
543                s.write_string(&STRING, "k2")?;
544                s.write_string(&STRING, "v2")?;
545                Ok(())
546            })
547            .unwrap()
548        });
549        let map_schema = Schema::new(shape_id!("test", "M"), ShapeType::Map);
550        let mut de = CborDeserializer::new(&bytes, 128);
551        let mut map = std::collections::HashMap::new();
552        de.read_map(&map_schema, &mut |key, d| {
553            map.insert(key, d.read_string(&STRING)?);
554            Ok(())
555        })
556        .unwrap();
557        assert_eq!(map.get("k1").unwrap(), "v1");
558        assert_eq!(map.get("k2").unwrap(), "v2");
559    }
560
561    #[test]
562    fn test_empty_input_treated_as_empty_struct() {
563        static SCHEMA: Schema =
564            Schema::new_struct(shape_id!("test", "S"), ShapeType::Structure, &[]);
565        let mut de = CborDeserializer::new(&[], 128);
566        de.read_struct(&SCHEMA, &mut |_, _| Ok(())).unwrap();
567    }
568
569    #[test]
570    fn test_container_size_definite() {
571        let mut enc = crate::Encoder::new(Vec::new());
572        enc.array(5);
573        let bytes = enc.into_writer();
574        let de = CborDeserializer::new(&bytes, 128);
575        assert_eq!(de.container_size(), Some(5));
576    }
577
578    #[test]
579    fn test_container_size_indefinite_returns_none() {
580        let mut enc = crate::Encoder::new(Vec::new());
581        enc.begin_array();
582        let bytes = enc.into_writer();
583        let de = CborDeserializer::new(&bytes, 128);
584        assert_eq!(de.container_size(), None);
585    }
586
587    #[test]
588    fn test_depth_limit_rejects_deeply_nested() {
589        // Build deeply nested maps: {"a": {"a": {"a": ...}}}
590        let mut enc = crate::Encoder::new(Vec::new());
591        for _ in 0..200 {
592            enc.begin_map().str("a");
593        }
594        enc.begin_map().end(); // innermost empty map
595        for _ in 0..200 {
596            enc.end();
597        }
598        let bytes = enc.into_writer();
599
600        static MEMBER: Schema =
601            Schema::new_member(shape_id!("test", "R"), ShapeType::Structure, "a", 0);
602        static SCHEMA: Schema =
603            Schema::new_struct(shape_id!("test", "R"), ShapeType::Structure, &[&MEMBER]);
604
605        let mut de = CborDeserializer::new(&bytes, 128);
606        fn recursive_consumer(
607            _member: &Schema,
608            deser: &mut dyn ShapeDeserializer,
609        ) -> Result<(), SerdeError> {
610            static MEMBER: Schema =
611                Schema::new_member(shape_id!("test", "R"), ShapeType::Structure, "a", 0);
612            static SCHEMA: Schema =
613                Schema::new_struct(shape_id!("test", "R"), ShapeType::Structure, &[&MEMBER]);
614            deser.read_struct(&SCHEMA, &mut recursive_consumer)
615        }
616        let result = de.read_struct(&SCHEMA, &mut recursive_consumer);
617        assert!(result.is_err());
618        assert!(result.unwrap_err().to_string().contains("depth"));
619    }
620
621    #[test]
622    fn test_depth_limit_accepts_under_limit() {
623        // 10 levels of nesting — well under 128
624        let mut enc = crate::Encoder::new(Vec::new());
625        for _ in 0..10 {
626            enc.begin_map().str("a");
627        }
628        enc.begin_map().end();
629        for _ in 0..10 {
630            enc.end();
631        }
632        let bytes = enc.into_writer();
633
634        static MEMBER: Schema =
635            Schema::new_member(shape_id!("test", "R"), ShapeType::Structure, "a", 0);
636        static SCHEMA: Schema =
637            Schema::new_struct(shape_id!("test", "R"), ShapeType::Structure, &[&MEMBER]);
638
639        let mut de = CborDeserializer::new(&bytes, 128);
640        fn recursive_consumer(
641            _member: &Schema,
642            deser: &mut dyn ShapeDeserializer,
643        ) -> Result<(), SerdeError> {
644            static MEMBER: Schema =
645                Schema::new_member(shape_id!("test", "R"), ShapeType::Structure, "a", 0);
646            static SCHEMA: Schema =
647                Schema::new_struct(shape_id!("test", "R"), ShapeType::Structure, &[&MEMBER]);
648            deser.read_struct(&SCHEMA, &mut recursive_consumer)
649        }
650        de.read_struct(&SCHEMA, &mut recursive_consumer).unwrap();
651    }
652
653    #[test]
654    fn test_read_struct_with_null_optional_members() {
655        // Struct with null value for an optional member — null should be skipped
656        let mut enc = crate::Encoder::new(Vec::new());
657        enc.begin_map()
658            .str("name")
659            .str("Alice")
660            .str("age")
661            .null()
662            .end();
663        let bytes = enc.into_writer();
664
665        static NAME: Schema =
666            Schema::new_member(shape_id!("test", "S"), ShapeType::String, "name", 0);
667        static AGE: Schema =
668            Schema::new_member(shape_id!("test", "S"), ShapeType::Integer, "age", 1);
669        static SCHEMA: Schema =
670            Schema::new_struct(shape_id!("test", "S"), ShapeType::Structure, &[&NAME, &AGE]);
671
672        let mut de = CborDeserializer::new(&bytes, 128);
673        let mut name: Option<String> = None;
674        let mut age: Option<i32> = None;
675        de.read_struct(&SCHEMA, &mut |member, d| {
676            match member.member_name() {
677                Some("name") => {
678                    if !d.is_null() {
679                        name = Some(d.read_string(member)?);
680                    } else {
681                        d.read_null()?;
682                    }
683                }
684                Some("age") => {
685                    if !d.is_null() {
686                        age = Some(d.read_integer(member)?);
687                    } else {
688                        d.read_null()?;
689                    }
690                }
691                _ => {}
692            }
693            Ok(())
694        })
695        .unwrap();
696        assert_eq!(name, Some("Alice".to_string()));
697        assert_eq!(age, None);
698    }
699
700    #[test]
701    fn test_read_sparse_list_with_nulls() {
702        // @sparse list: [1, null, 3] — null should not be skipped
703        let mut enc = crate::Encoder::new(Vec::new());
704        enc.begin_array().integer(1).null().integer(3).end();
705        let bytes = enc.into_writer();
706
707        let list_schema = Schema::new(shape_id!("test", "L"), ShapeType::List);
708        let mut de = CborDeserializer::new(&bytes, 128);
709        let mut items: Vec<Option<i32>> = Vec::new();
710        de.read_list(&list_schema, &mut |d| {
711            if d.is_null() {
712                d.read_null()?;
713                items.push(None);
714            } else {
715                items.push(Some(d.read_integer(&INTEGER)?));
716            }
717            Ok(())
718        })
719        .unwrap();
720        assert_eq!(items, vec![Some(1), None, Some(3)]);
721    }
722
723    #[test]
724    fn test_read_sparse_map_with_nulls() {
725        // @sparse map: {"a": "hello", "b": null} — null should not be skipped
726        let mut enc = crate::Encoder::new(Vec::new());
727        enc.begin_map().str("a").str("hello").str("b").null().end();
728        let bytes = enc.into_writer();
729
730        let map_schema = Schema::new(shape_id!("test", "M"), ShapeType::Map);
731        let mut de = CborDeserializer::new(&bytes, 128);
732        let mut map: std::collections::HashMap<String, Option<String>> =
733            std::collections::HashMap::new();
734        de.read_map(&map_schema, &mut |key, d| {
735            if d.is_null() {
736                d.read_null()?;
737                map.insert(key, None);
738            } else {
739                map.insert(key, Some(d.read_string(&STRING)?));
740            }
741            Ok(())
742        })
743        .unwrap();
744        assert_eq!(map.get("a"), Some(&Some("hello".to_string())));
745        assert_eq!(map.get("b"), Some(&None));
746    }
747}