Skip to main content

aws_smithy_types/document/
mod.rs

1/*
2 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 * SPDX-License-Identifier: Apache-2.0
4 */
5
6#[cfg(all(aws_sdk_unstable, feature = "serde-deserialize"))]
7mod de;
8mod discriminated;
9#[cfg(any(
10    all(aws_sdk_unstable, feature = "serde-deserialize"),
11    all(aws_sdk_unstable, feature = "serde-serialize")
12))]
13mod doc_error;
14mod error;
15#[cfg(all(aws_sdk_unstable, feature = "serde-serialize"))]
16mod ser;
17mod settings;
18
19#[cfg(all(aws_sdk_unstable, feature = "serde-deserialize"))]
20pub use de::from_document;
21pub use discriminated::DiscriminatedDocument;
22#[cfg(any(
23    all(aws_sdk_unstable, feature = "serde-deserialize"),
24    all(aws_sdk_unstable, feature = "serde-serialize")
25))]
26pub use doc_error::DocError;
27pub use error::DocumentError;
28#[cfg(all(aws_sdk_unstable, feature = "serde-serialize"))]
29pub use ser::to_document;
30pub use settings::DocumentSettings;
31
32use crate::Number;
33use std::borrow::Cow;
34use std::collections::HashMap;
35
36#[cfg(any(
37    all(aws_sdk_unstable, feature = "serde-deserialize"),
38    all(aws_sdk_unstable, feature = "serde-serialize")
39))]
40use serde;
41
42/// Document Type
43///
44/// Document types represents protocol-agnostic open content that is accessed like JSON data.
45/// Open content is useful for modeling unstructured data that has no schema, data that can't be
46/// modeled using rigid types, or data that has a schema that evolves outside of the purview of a model.
47/// The serialization format of a document is an implementation detail of a protocol.
48#[derive(Clone, Debug, PartialEq)]
49#[cfg_attr(
50    all(aws_sdk_unstable, feature = "serde-serialize"),
51    derive(serde::Serialize)
52)]
53#[cfg_attr(
54    all(aws_sdk_unstable, feature = "serde-deserialize"),
55    derive(serde::Deserialize)
56)]
57#[cfg_attr(
58    any(
59        all(aws_sdk_unstable, feature = "serde-deserialize"),
60        all(aws_sdk_unstable, feature = "serde-serialize")
61    ),
62    serde(untagged)
63)]
64pub enum Document {
65    /// JSON object
66    Object(HashMap<String, Document>),
67    /// JSON array
68    Array(Vec<Document>),
69    /// JSON number
70    Number(Number),
71    /// JSON string
72    String(String),
73    /// JSON boolean
74    Bool(bool),
75    /// JSON null
76    Null,
77}
78
79impl Document {
80    /// Returns the inner map value if this `Document` is an object.
81    pub fn as_object(&self) -> Option<&HashMap<String, Document>> {
82        if let Self::Object(object) = self {
83            Some(object)
84        } else {
85            None
86        }
87    }
88
89    /// Returns the mutable inner map value if this `Document` is an object.
90    pub fn as_object_mut(&mut self) -> Option<&mut HashMap<String, Document>> {
91        if let Self::Object(object) = self {
92            Some(object)
93        } else {
94            None
95        }
96    }
97
98    /// Returns the inner array value if this `Document` is an array.
99    pub fn as_array(&self) -> Option<&Vec<Document>> {
100        if let Self::Array(array) = self {
101            Some(array)
102        } else {
103            None
104        }
105    }
106
107    /// Returns the mutable inner array value if this `Document` is an array.
108    pub fn as_array_mut(&mut self) -> Option<&mut Vec<Document>> {
109        if let Self::Array(array) = self {
110            Some(array)
111        } else {
112            None
113        }
114    }
115
116    /// Returns the inner number value if this `Document` is a number.
117    pub fn as_number(&self) -> Option<&Number> {
118        if let Self::Number(number) = self {
119            Some(number)
120        } else {
121            None
122        }
123    }
124
125    /// Returns the inner string value if this `Document` is a string.
126    pub fn as_string(&self) -> Option<&str> {
127        if let Self::String(string) = self {
128            Some(string)
129        } else {
130            None
131        }
132    }
133
134    /// Returns the inner boolean value if this `Document` is a boolean.
135    pub fn as_bool(&self) -> Option<bool> {
136        if let Self::Bool(boolean) = self {
137            Some(*boolean)
138        } else {
139            None
140        }
141    }
142
143    /// Returns `Some(())` if this `Document` is a null.
144    pub fn as_null(&self) -> Option<()> {
145        if let Self::Null = self {
146            Some(())
147        } else {
148            None
149        }
150    }
151
152    /// Returns `true` if this `Document` is an object.
153    pub fn is_object(&self) -> bool {
154        matches!(self, Self::Object(_))
155    }
156
157    /// Returns `true` if this `Document` is an array.
158    pub fn is_array(&self) -> bool {
159        matches!(self, Self::Array(_))
160    }
161
162    /// Returns `true` if this `Document` is a number.
163    pub fn is_number(&self) -> bool {
164        matches!(self, Self::Number(_))
165    }
166
167    /// Returns `true` if this `Document` is a string.
168    pub fn is_string(&self) -> bool {
169        matches!(self, Self::String(_))
170    }
171
172    /// Returns `true` if this `Document` is a bool.
173    pub fn is_bool(&self) -> bool {
174        matches!(self, Self::Bool(_))
175    }
176
177    /// Returns `true` if this `Document` is a boolean.
178    pub fn is_null(&self) -> bool {
179        matches!(self, Self::Null)
180    }
181}
182
183/// The default value is `Document::Null`.
184impl Default for Document {
185    fn default() -> Self {
186        Self::Null
187    }
188}
189
190impl From<bool> for Document {
191    fn from(value: bool) -> Self {
192        Document::Bool(value)
193    }
194}
195
196impl<'a> From<&'a str> for Document {
197    fn from(value: &'a str) -> Self {
198        Document::String(value.to_string())
199    }
200}
201
202impl<'a> From<Cow<'a, str>> for Document {
203    fn from(value: Cow<'a, str>) -> Self {
204        Document::String(value.into_owned())
205    }
206}
207
208impl From<String> for Document {
209    fn from(value: String) -> Self {
210        Document::String(value)
211    }
212}
213
214impl From<Vec<Document>> for Document {
215    fn from(values: Vec<Document>) -> Self {
216        Document::Array(values)
217    }
218}
219
220impl From<HashMap<String, Document>> for Document {
221    fn from(values: HashMap<String, Document>) -> Self {
222        Document::Object(values)
223    }
224}
225
226impl From<u64> for Document {
227    fn from(value: u64) -> Self {
228        Document::Number(Number::PosInt(value))
229    }
230}
231
232impl From<i64> for Document {
233    fn from(value: i64) -> Self {
234        Document::Number(Number::NegInt(value))
235    }
236}
237
238impl From<i32> for Document {
239    fn from(value: i32) -> Self {
240        Document::Number(Number::NegInt(value as i64))
241    }
242}
243
244impl From<f64> for Document {
245    fn from(value: f64) -> Self {
246        Document::Number(Number::Float(value))
247    }
248}
249
250impl From<Number> for Document {
251    fn from(value: Number) -> Self {
252        Document::Number(value)
253    }
254}
255
256impl<T> From<Option<T>> for Document
257where
258    Document: From<T>,
259{
260    fn from(value: Option<T>) -> Self {
261        match value {
262            Some(inner) => inner.into(),
263            None => Document::Null,
264        }
265    }
266}
267
268/* ANCHOR END: document */
269
270/// Returns the human-readable name of a [`Document`] variant for use
271/// in error messages.
272///
273/// Deliberately placed **outside** the released `Document` contract
274/// above (the enum, its inherent `impl`, `Default`, and the `From`
275/// conversions): this is a private helper used by
276/// [`DiscriminatedDocument`]'s coercion error messages, not part of
277/// the `Document` API.
278pub(crate) fn document_variant_name(d: &Document) -> &'static str {
279    match d {
280        Document::Null => "null",
281        Document::Bool(_) => "boolean",
282        Document::Number(_) => "number",
283        Document::String(_) => "string",
284        Document::Array(_) => "array",
285        Document::Object(_) => "object",
286    }
287}
288
289#[cfg(test)]
290#[cfg(all(
291    aws_sdk_unstable,
292    feature = "serde-serialize",
293    feature = "serde-deserialize"
294))]
295mod test {
296    use super::{from_document, to_document, Document};
297    use crate::Number;
298    use serde::{Deserialize, Serialize};
299    use std::collections::HashMap;
300
301    /// Helper: serialize a value to Document and verify the result.
302    fn test_to_document_ok<T>(cases: &[(T, Document)])
303    where
304        T: Serialize + std::fmt::Debug,
305    {
306        for (value, expected) in cases {
307            let doc = to_document(value).unwrap();
308            assert_eq!(&doc, expected, "to_document({:?})", value);
309        }
310    }
311
312    /// Helper: round-trip T → Document → T.
313    fn test_roundtrip<T>(cases: &[T])
314    where
315        T: Serialize + for<'de> Deserialize<'de> + PartialEq + std::fmt::Debug + Clone,
316    {
317        for value in cases {
318            let doc = to_document(value).unwrap();
319            let roundtripped: T = from_document(doc).unwrap();
320            assert_eq!(&roundtripped, value, "roundtrip failed for {:?}", value);
321        }
322    }
323
324    // ========================================================================
325    // Null / Unit
326    // ========================================================================
327
328    #[test]
329    fn test_null() {
330        test_to_document_ok(&[((), Document::Null)]);
331
332        let v: () = from_document(Document::Null).unwrap();
333        assert_eq!(v, ());
334    }
335
336    // ========================================================================
337    // Booleans
338    // ========================================================================
339
340    #[test]
341    fn test_bool() {
342        test_to_document_ok(&[(true, Document::Bool(true)), (false, Document::Bool(false))]);
343        test_roundtrip(&[true, false]);
344    }
345
346    // ========================================================================
347    // Unsigned integers
348    // ========================================================================
349
350    #[test]
351    fn test_u8() {
352        test_to_document_ok(&[
353            (0u8, Document::Number(Number::PosInt(0))),
354            (u8::MAX, Document::Number(Number::PosInt(u8::MAX as u64))),
355        ]);
356        test_roundtrip(&[0u8, 1, 127, u8::MAX]);
357    }
358
359    #[test]
360    fn test_u16() {
361        test_to_document_ok(&[
362            (0u16, Document::Number(Number::PosInt(0))),
363            (u16::MAX, Document::Number(Number::PosInt(u16::MAX as u64))),
364        ]);
365        test_roundtrip(&[0u16, 1, u16::MAX]);
366    }
367
368    #[test]
369    fn test_u32() {
370        test_to_document_ok(&[
371            (0u32, Document::Number(Number::PosInt(0))),
372            (u32::MAX, Document::Number(Number::PosInt(u32::MAX as u64))),
373        ]);
374        test_roundtrip(&[0u32, 1, u32::MAX]);
375    }
376
377    #[test]
378    fn test_u64() {
379        test_to_document_ok(&[
380            (0u64, Document::Number(Number::PosInt(0))),
381            (u64::MAX, Document::Number(Number::PosInt(u64::MAX))),
382        ]);
383        test_roundtrip(&[0u64, 1, u64::MAX]);
384    }
385
386    // ========================================================================
387    // Signed integers
388    // ========================================================================
389
390    #[test]
391    fn test_i8() {
392        test_to_document_ok(&[
393            (0i8, Document::Number(Number::PosInt(0))),
394            (-1i8, Document::Number(Number::NegInt(-1))),
395            (i8::MIN, Document::Number(Number::NegInt(i8::MIN as i64))),
396            (i8::MAX, Document::Number(Number::PosInt(i8::MAX as u64))),
397        ]);
398        test_roundtrip(&[0i8, -1, 1, i8::MIN, i8::MAX]);
399    }
400
401    #[test]
402    fn test_i16() {
403        test_to_document_ok(&[
404            (0i16, Document::Number(Number::PosInt(0))),
405            (i16::MIN, Document::Number(Number::NegInt(i16::MIN as i64))),
406            (i16::MAX, Document::Number(Number::PosInt(i16::MAX as u64))),
407        ]);
408        test_roundtrip(&[0i16, -1, i16::MIN, i16::MAX]);
409    }
410
411    #[test]
412    fn test_i32() {
413        test_to_document_ok(&[
414            (0i32, Document::Number(Number::PosInt(0))),
415            (i32::MIN, Document::Number(Number::NegInt(i32::MIN as i64))),
416            (i32::MAX, Document::Number(Number::PosInt(i32::MAX as u64))),
417        ]);
418        test_roundtrip(&[0i32, -1, i32::MIN, i32::MAX]);
419    }
420
421    #[test]
422    fn test_i64() {
423        test_to_document_ok(&[
424            (0i64, Document::Number(Number::PosInt(0))),
425            (-1i64, Document::Number(Number::NegInt(-1))),
426            (i64::MIN, Document::Number(Number::NegInt(i64::MIN))),
427            (i64::MAX, Document::Number(Number::PosInt(i64::MAX as u64))),
428        ]);
429        test_roundtrip(&[0i64, -1, i64::MIN, i64::MAX]);
430    }
431
432    // ========================================================================
433    // Floats
434    // ========================================================================
435
436    #[test]
437    fn test_f32() {
438        test_to_document_ok(&[
439            (0.0f32, Document::Number(Number::Float(0.0))),
440            (3.5f32, Document::Number(Number::Float(3.5))),
441            (-1.5f32, Document::Number(Number::Float(-1.5))),
442        ]);
443        test_roundtrip(&[0.0f32, 3.5, -1.5, f32::MIN, f32::MAX]);
444    }
445
446    #[test]
447    fn test_f64() {
448        test_to_document_ok(&[
449            (0.0f64, Document::Number(Number::Float(0.0))),
450            (3.1f64, Document::Number(Number::Float(3.1))),
451            (-1.5f64, Document::Number(Number::Float(-1.5))),
452            (f64::MIN, Document::Number(Number::Float(f64::MIN))),
453            (f64::MAX, Document::Number(Number::Float(f64::MAX))),
454            (f64::EPSILON, Document::Number(Number::Float(f64::EPSILON))),
455        ]);
456        test_roundtrip(&[0.0f64, 3.1, -1.5, 0.5, f64::MIN, f64::MAX]);
457    }
458
459    #[test]
460    fn test_nonfinite_floats() {
461        // NaN, +Inf, -Inf all serialize to the float Document representation
462        let doc = to_document(&f64::NAN).unwrap();
463        match doc {
464            Document::Number(Number::Float(v)) => assert!(v.is_nan()),
465            other => panic!("expected NaN float, got {:?}", other),
466        }
467
468        let doc = to_document(&f64::INFINITY).unwrap();
469        assert_eq!(doc, Document::Number(Number::Float(f64::INFINITY)));
470
471        let doc = to_document(&f64::NEG_INFINITY).unwrap();
472        assert_eq!(doc, Document::Number(Number::Float(f64::NEG_INFINITY)));
473    }
474
475    // ========================================================================
476    // Strings
477    // ========================================================================
478
479    #[test]
480    fn test_string() {
481        test_to_document_ok(&[
482            (String::new(), Document::String(String::new())),
483            ("hello".to_owned(), Document::String("hello".to_owned())),
484            (
485                "with\nnewline".to_owned(),
486                Document::String("with\nnewline".to_owned()),
487            ),
488            (
489                "unicode: \u{1F600}".to_owned(),
490                Document::String("unicode: \u{1F600}".to_owned()),
491            ),
492        ]);
493        test_roundtrip(&[
494            String::new(),
495            "foo".to_owned(),
496            "bar\tbaz".to_owned(),
497            "\u{3A3}".to_owned(),
498        ]);
499    }
500
501    #[test]
502    fn test_str_ref() {
503        let doc = to_document(&"borrowed str").unwrap();
504        assert_eq!(doc, Document::String("borrowed str".to_owned()));
505    }
506
507    #[test]
508    fn test_char() {
509        let doc = to_document(&'a').unwrap();
510        assert_eq!(doc, Document::String("a".to_owned()));
511
512        let doc = to_document(&'\u{1F600}').unwrap();
513        assert_eq!(doc, Document::String("\u{1F600}".to_owned()));
514    }
515
516    // ========================================================================
517    // Option
518    // ========================================================================
519
520    #[test]
521    fn test_option() {
522        test_to_document_ok(&[
523            (None::<String>, Document::Null),
524            (
525                Some("jodhpurs".to_owned()),
526                Document::String("jodhpurs".to_owned()),
527            ),
528        ]);
529        test_to_document_ok(&[
530            (None::<u32>, Document::Null),
531            (Some(42u32), Document::Number(Number::PosInt(42))),
532        ]);
533        test_roundtrip(&[None::<u32>, Some(5), Some(0)]);
534        test_roundtrip(&[None::<String>, Some("x".to_owned())]);
535    }
536
537    // ========================================================================
538    // Sequences / Arrays
539    // ========================================================================
540
541    #[test]
542    fn test_vec_empty() {
543        let doc = to_document(&Vec::<i32>::new()).unwrap();
544        assert_eq!(doc, Document::Array(vec![]));
545
546        let v: Vec<i32> = from_document(Document::Array(vec![])).unwrap();
547        assert_eq!(v, Vec::<i32>::new());
548    }
549
550    #[test]
551    fn test_vec_integers() {
552        test_to_document_ok(&[(
553            vec![1u64, 2, 3],
554            Document::Array(vec![
555                Document::Number(Number::PosInt(1)),
556                Document::Number(Number::PosInt(2)),
557                Document::Number(Number::PosInt(3)),
558            ]),
559        )]);
560        test_roundtrip(&[vec![1i32, -2, 3], vec![], vec![0]]);
561    }
562
563    #[test]
564    fn test_vec_mixed_via_document() {
565        // Vec<Document> allows mixed types
566        let mixed = vec![
567            Document::Bool(true),
568            Document::Null,
569            Document::String("foo".to_owned()),
570            Document::Number(Number::PosInt(42)),
571        ];
572        let doc = Document::Array(mixed.clone());
573        let roundtripped: Vec<Document> = from_document(doc.clone()).unwrap();
574        assert_eq!(roundtripped, mixed);
575    }
576
577    #[test]
578    fn test_nested_vec() {
579        test_roundtrip(&[
580            vec![vec![1u32, 2], vec![], vec![3]],
581            vec![vec![], vec![], vec![]],
582        ]);
583    }
584
585    #[test]
586    fn test_tuple() {
587        let doc = to_document(&(5u32,)).unwrap();
588        assert_eq!(
589            doc,
590            Document::Array(vec![Document::Number(Number::PosInt(5))])
591        );
592
593        let doc = to_document(&(1u32, "abc", true)).unwrap();
594        assert_eq!(
595            doc,
596            Document::Array(vec![
597                Document::Number(Number::PosInt(1)),
598                Document::String("abc".to_owned()),
599                Document::Bool(true),
600            ])
601        );
602
603        test_roundtrip(&[(1u32, 2u32), (0, u32::MAX)]);
604        test_roundtrip(&[(1i32, "hello".to_owned(), true)]);
605    }
606
607    // ========================================================================
608    // Maps / Objects
609    // ========================================================================
610
611    #[test]
612    fn test_map_empty() {
613        let map: HashMap<String, u32> = HashMap::new();
614        let doc = to_document(&map).unwrap();
615        assert_eq!(doc, Document::Object(HashMap::new()));
616        test_roundtrip(&[HashMap::<String, u32>::new()]);
617    }
618
619    #[test]
620    fn test_map_string_keys() {
621        let mut map = HashMap::new();
622        map.insert("a".to_owned(), 1u32);
623        map.insert("b".to_owned(), 2u32);
624        test_roundtrip(&[map]);
625    }
626
627    #[test]
628    fn test_map_integer_keys() {
629        // Integer keys get serialized as their string representation
630        let mut map = HashMap::new();
631        map.insert(1u32, "one".to_owned());
632        map.insert(2u32, "two".to_owned());
633
634        let doc = to_document(&map).unwrap();
635        assert!(doc.is_object());
636        let obj = doc.as_object().unwrap();
637        assert!(obj.contains_key("1") || obj.contains_key("2"));
638    }
639
640    #[test]
641    fn test_nested_map() {
642        let mut inner = HashMap::new();
643        inner.insert("x".to_owned(), 10u32);
644
645        let mut outer = HashMap::new();
646        outer.insert("inner".to_owned(), inner.clone());
647
648        test_roundtrip(&[outer]);
649    }
650
651    // ========================================================================
652    // Structs
653    // ========================================================================
654
655    #[test]
656    fn test_struct() {
657        #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
658        struct Inner {
659            a: (),
660            b: usize,
661            c: Vec<String>,
662        }
663
664        #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
665        struct Outer {
666            inner: Vec<Inner>,
667        }
668
669        let outer = Outer {
670            inner: vec![Inner {
671                a: (),
672                b: 2,
673                c: vec!["abc".to_owned(), "xyz".to_owned()],
674            }],
675        };
676
677        let doc = to_document(&outer).unwrap();
678        assert!(doc.is_object());
679        let roundtripped: Outer = from_document(doc).unwrap();
680        assert_eq!(outer, roundtripped);
681
682        // Empty inner
683        test_roundtrip(&[Outer { inner: vec![] }]);
684    }
685
686    #[test]
687    fn test_newtype_struct() {
688        #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
689        struct Wrapper(u32);
690
691        test_to_document_ok(&[(Wrapper(123), Document::Number(Number::PosInt(123)))]);
692        test_roundtrip(&[Wrapper(0), Wrapper(u32::MAX)]);
693    }
694
695    #[test]
696    fn test_unit_struct() {
697        #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
698        struct Unit;
699
700        test_to_document_ok(&[(Unit, Document::Null)]);
701        let v: Unit = from_document(Document::Null).unwrap();
702        assert_eq!(v, Unit);
703    }
704
705    // ========================================================================
706    // Enums
707    // ========================================================================
708
709    #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
710    enum Animal {
711        Dog,
712        Frog(String, Vec<isize>),
713        Cat { age: usize, name: String },
714        AntHive(Vec<String>),
715    }
716
717    #[test]
718    fn test_enum_unit_variant() {
719        let doc = to_document(&Animal::Dog).unwrap();
720        assert_eq!(doc, Document::String("Dog".to_owned()));
721        test_roundtrip(&[Animal::Dog]);
722    }
723
724    #[test]
725    fn test_enum_tuple_variant() {
726        let frog = Animal::Frog("Henry".to_owned(), vec![349, 102]);
727        let doc = to_document(&frog).unwrap();
728        assert!(doc.is_object());
729        let obj = doc.as_object().unwrap();
730        assert!(obj.contains_key("Frog"));
731        test_roundtrip(&[
732            Animal::Frog("Henry".to_owned(), vec![]),
733            Animal::Frog("Henry".to_owned(), vec![349, 102]),
734        ]);
735    }
736
737    #[test]
738    fn test_enum_struct_variant() {
739        let cat = Animal::Cat {
740            age: 5,
741            name: "Kate".to_owned(),
742        };
743        let doc = to_document(&cat).unwrap();
744        assert!(doc.is_object());
745        let obj = doc.as_object().unwrap();
746        assert!(obj.contains_key("Cat"));
747        test_roundtrip(&[cat]);
748    }
749
750    #[test]
751    fn test_enum_newtype_variant() {
752        let hive = Animal::AntHive(vec!["Bob".to_owned(), "Stuart".to_owned()]);
753        test_roundtrip(&[hive]);
754    }
755
756    // ========================================================================
757    // Bytes
758    // ========================================================================
759
760    #[test]
761    fn test_bytes() {
762        // Bytes serialize as an array of numbers
763        let data: &[u8] = &[1, 2, 3];
764        let doc = to_document(&data).unwrap();
765        assert_eq!(
766            doc,
767            Document::Array(vec![
768                Document::Number(Number::PosInt(1)),
769                Document::Number(Number::PosInt(2)),
770                Document::Number(Number::PosInt(3)),
771            ])
772        );
773
774        let empty: &[u8] = &[];
775        let doc = to_document(&empty).unwrap();
776        assert_eq!(doc, Document::Array(vec![]));
777    }
778
779    // ========================================================================
780    // Error cases
781    // ========================================================================
782
783    #[test]
784    fn test_deserialize_wrong_type() {
785        let result = from_document::<bool>(Document::String("not a bool".to_owned()));
786        assert!(result.is_err());
787        let err = result.unwrap_err();
788        assert!(
789            err.to_string().contains("invalid type"),
790            "unexpected error message: {}",
791            err
792        );
793    }
794
795    #[test]
796    fn test_deserialize_missing_field() {
797        #[derive(Debug, Deserialize)]
798        struct Required {
799            #[allow(dead_code)]
800            x: u32,
801        }
802
803        let doc = Document::Object(HashMap::new());
804        let result = from_document::<Required>(doc);
805        assert!(result.is_err());
806        let err = result.unwrap_err();
807        assert!(err.to_string().contains("missing field"));
808    }
809
810    #[test]
811    fn test_serialize_non_string_map_key_rejected() {
812        // Map keys that cannot be coerced to strings should fail
813        use std::collections::HashMap;
814        let mut map: HashMap<Option<u32>, u32> = HashMap::new();
815        map.insert(None, 1);
816
817        let result = to_document(&map);
818        assert!(result.is_err());
819    }
820
821    // ========================================================================
822    // serde_json compatibility (existing test)
823    // ========================================================================
824
825    #[test]
826    fn test_serde_json_compatibility() {
827        let mut map: HashMap<String, Document> = HashMap::new();
828        map.insert("hello".into(), "world".to_string().into());
829        map.insert("pos_int".into(), Document::Number(Number::PosInt(1).into()));
830        map.insert(
831            "neg_int".into(),
832            Document::Number(Number::NegInt(-1).into()),
833        );
834        map.insert(
835            "float".into(),
836            Document::Number(Number::Float(0.1 + 0.2).into()),
837        );
838        map.insert("true".into(), true.into());
839        map.insert("false".into(), false.into());
840        map.insert(
841            "array".into(),
842            vec![
843                map.clone().into(),
844                "hello-world".to_string().into(),
845                true.into(),
846                false.into(),
847            ]
848            .into(),
849        );
850        map.insert("map".into(), map.clone().into());
851        map.insert("null".into(), Document::Null);
852        let obj = Document::Object(map);
853
854        let target_file = include_str!("../../test_data/serialize_document.json");
855        let json: Result<serde_json::Value, _> = serde_json::from_str(target_file);
856        assert_eq!(serde_json::to_value(&obj).unwrap(), json.unwrap());
857        let doc: Result<Document, _> = serde_json::from_str(target_file);
858        assert_eq!(obj, doc.unwrap());
859    }
860}