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