1use std::str::FromStr;
39use std::sync::Arc;
40
41use aws_smithy_types::date_time::Format;
42use aws_smithy_types::{
43 BigDecimal, BigInteger, Blob, DateTime, Document, DocumentSettings, Number,
44};
45
46use crate::serde::{capped_container_size, SerdeError, ShapeDeserializer};
47use crate::Schema;
48
49#[derive(Debug)]
76pub struct DocumentShapeDeserializer<'a> {
77 cursor: &'a Document,
79 settings: Option<Arc<dyn DocumentSettings>>,
93}
94
95impl<'a> DocumentShapeDeserializer<'a> {
96 pub fn new(document: &'a Document) -> Self {
108 Self {
109 cursor: document,
110 settings: None,
111 }
112 }
113
114 pub fn new_with_settings(
125 document: &'a Document,
126 settings: Option<Arc<dyn DocumentSettings>>,
127 ) -> Self {
128 Self {
129 cursor: document,
130 settings,
131 }
132 }
133}
134
135fn number_to_timestamp(n: &Number) -> Result<DateTime, SerdeError> {
143 match n {
144 Number::PosInt(v) => i64::try_from(*v)
145 .map(DateTime::from_secs)
146 .map_err(|_| SerdeError::custom(format!("epoch seconds {v} out of range"))),
147 Number::NegInt(v) => Ok(DateTime::from_secs(*v)),
148 Number::Float(v) => {
149 if !v.is_finite() {
150 return Err(SerdeError::custom(format!(
151 "epoch seconds {v} is not finite"
152 )));
153 }
154 Ok(DateTime::from_secs_f64(*v))
155 }
156 }
157}
158
159fn type_mismatch(expected: &str, found: &Document) -> SerdeError {
162 SerdeError::type_mismatch(format!(
163 "expected {expected} document, got {}",
164 kind_name(found)
165 ))
166}
167
168fn kind_name(d: &Document) -> &'static str {
171 match d {
172 Document::Null => "null",
173 Document::Bool(_) => "boolean",
174 Document::Number(_) => "number",
175 Document::String(_) => "string",
176 Document::Array(_) => "list",
177 Document::Object(_) => "map",
178 }
179}
180
181fn resolve_member<'s>(schema: &'s Schema<'s>, wire_name: &str) -> Option<&'s Schema<'s>> {
190 let idx = schema
191 .members()
192 .iter()
193 .position(|m| m.member_name() == Some(wire_name))?;
194 schema.member_schema_by_index(idx)
195}
196
197fn coerce_signed<T>(doc: &Document, name: &str) -> Result<T, SerdeError>
238where
239 T: TryFrom<i64> + TryFrom<u64>,
240{
241 match doc {
242 Document::Number(Number::PosInt(v)) => {
243 T::try_from(*v).map_err(|_| SerdeError::numeric_coercion_overflow(name, v.to_string()))
244 }
245 Document::Number(Number::NegInt(v)) => {
246 T::try_from(*v).map_err(|_| SerdeError::numeric_coercion_overflow(name, v.to_string()))
247 }
248 Document::Number(Number::Float(v)) => {
249 if !is_integral(*v) {
250 return Err(SerdeError::numeric_coercion_lossy(name, v.to_string()));
251 }
252 float_as_i64(*v)
253 .and_then(|i| T::try_from(i).ok())
254 .ok_or_else(|| SerdeError::numeric_coercion_overflow(name, v.to_string()))
255 }
256 other => Err(type_mismatch(name, other)),
257 }
258}
259
260fn is_integral(v: f64) -> bool {
262 v.is_finite() && v.fract() == 0.0
263}
264
265pub(crate) fn float_as_i64(v: f64) -> Option<i64> {
272 const TWO_POW_63: f64 = 9_223_372_036_854_775_808.0;
273 (is_integral(v) && (-TWO_POW_63..TWO_POW_63).contains(&v)).then_some(v as i64)
274}
275
276fn coerce_double(doc: &Document) -> Result<f64, SerdeError> {
280 match doc {
281 Document::Number(Number::PosInt(v)) => Ok(*v as f64),
282 Document::Number(Number::NegInt(v)) => Ok(*v as f64),
283 Document::Number(Number::Float(f)) => Ok(*f),
284 other => Err(type_mismatch("double", other)),
285 }
286}
287
288fn coerce_float(doc: &Document) -> Result<f32, SerdeError> {
293 Ok(coerce_double(doc)? as f32)
294}
295
296fn coerce_big_integer(doc: &Document) -> Result<BigInteger, SerdeError> {
310 match doc {
311 Document::Number(Number::PosInt(v)) => parse_big_integer(&v.to_string()),
312 Document::Number(Number::NegInt(v)) => parse_big_integer(&v.to_string()),
313 Document::Number(Number::Float(v)) => {
314 if !is_integral(*v) {
315 return Err(SerdeError::numeric_coercion_lossy(
316 "bigInteger",
317 v.to_string(),
318 ));
319 }
320 parse_big_integer(&format!("{:.0}", v + 0.0))
323 }
324 Document::String(s) => {
325 if let Ok(bi) = BigInteger::from_str(s) {
326 return Ok(bi);
327 }
328 let bd = BigDecimal::from_str(s).map_err(|e| invalid_input("bigInteger", s, &e))?;
335 if !is_unambiguous_decimal(bd.as_ref()) {
342 return Err(SerdeError::invalid_input(format!(
343 "cannot coerce {:?} to bigInteger: not a decimal number",
344 bd.as_ref()
345 )));
346 }
347 let int_part = big_decimal_to_integer_string(&bd).ok_or_else(|| {
348 SerdeError::custom(format!(
349 "cannot coerce bigDecimal {} to bigInteger: integer magnitude too large",
350 bd.as_ref()
351 ))
352 })?;
353 parse_big_integer(&int_part)
354 }
355 other => Err(type_mismatch("bigInteger", other)),
356 }
357}
358
359fn coerce_big_decimal(doc: &Document) -> Result<BigDecimal, SerdeError> {
366 match doc {
367 Document::String(s) => parse_big_decimal(s),
368 Document::Number(Number::PosInt(v)) => parse_big_decimal(&v.to_string()),
369 Document::Number(Number::NegInt(v)) => parse_big_decimal(&v.to_string()),
370 Document::Number(Number::Float(f)) => {
371 if !f.is_finite() {
372 return Err(SerdeError::custom(format!(
373 "cannot coerce non-finite float {f} to bigDecimal"
374 )));
375 }
376 parse_big_decimal(&f.to_string())
377 }
378 other => Err(type_mismatch("bigDecimal", other)),
379 }
380}
381
382fn parse_big_integer(s: &str) -> Result<BigInteger, SerdeError> {
385 BigInteger::from_str(s).map_err(|e| invalid_input("bigInteger", s, &e))
386}
387
388fn parse_big_decimal(s: &str) -> Result<BigDecimal, SerdeError> {
391 BigDecimal::from_str(s).map_err(|e| invalid_input("bigDecimal", s, &e))
392}
393
394fn invalid_input(target: &str, value: &str, err: &dyn std::fmt::Display) -> SerdeError {
397 SerdeError::invalid_input(format!("cannot parse {value:?} as {target}: {err}"))
398}
399
400fn is_ascii_digits(s: &str) -> bool {
402 !s.is_empty() && s.bytes().all(|b| b.is_ascii_digit())
403}
404
405fn is_unambiguous_decimal(text: &str) -> bool {
423 let rest = text.strip_prefix(['-', '+']).unwrap_or(text);
424
425 let (mantissa, exponent) = match rest.split_once(['e', 'E']) {
426 Some((mantissa, exponent)) => (mantissa, Some(exponent)),
427 None => (rest, None),
428 };
429
430 let mantissa_ok = match mantissa.split_once('.') {
431 Some((int_part, frac_part)) => {
432 (!int_part.is_empty() || !frac_part.is_empty())
433 && int_part.bytes().all(|b| b.is_ascii_digit())
434 && frac_part.bytes().all(|b| b.is_ascii_digit())
435 }
436 None => is_ascii_digits(mantissa),
437 };
438
439 let exponent_ok = match exponent {
440 None => true,
441 Some(exponent) => is_ascii_digits(exponent.strip_prefix(['+', '-']).unwrap_or(exponent)),
442 };
443
444 mantissa_ok && exponent_ok
445}
446
447const MAX_INTEGER_DIGITS: usize = 1 << 20;
454
455fn big_decimal_to_integer_string(bd: &BigDecimal) -> Option<String> {
472 debug_assert!(
473 is_unambiguous_decimal(bd.as_ref()),
474 "big_decimal_to_integer_string requires decimal text, got {:?}",
475 bd.as_ref()
476 );
477 let text = bd.as_ref();
478 let (negative, rest) = match text.strip_prefix('-') {
479 Some(rest) => (true, rest),
480 None => (false, text.strip_prefix('+').unwrap_or(text)),
481 };
482
483 let (mantissa, exp) = match rest.split_once(['e', 'E']) {
484 Some((mantissa, exp_str)) => match exp_str.parse::<i64>() {
485 Ok(exp) => (mantissa, exp),
486 Err(_) => {
491 return if exp_str.starts_with('-') {
492 Some("0".to_string())
493 } else {
494 None
495 }
496 }
497 },
498 None => (rest, 0),
499 };
500
501 let (int_digits, frac_digits) = match mantissa.split_once('.') {
502 Some((int_digits, frac_digits)) => (int_digits, frac_digits),
503 None => (mantissa, ""),
504 };
505
506 let point = (int_digits.len() as i64).saturating_add(exp);
511
512 let int_part = if point <= 0 {
513 "0".to_string()
515 } else {
516 let point = point as usize;
517 let mut digits = String::with_capacity(int_digits.len() + frac_digits.len());
518 digits.push_str(int_digits);
519 digits.push_str(frac_digits);
520
521 if point >= digits.len() {
522 if point > MAX_INTEGER_DIGITS {
524 return None;
525 }
526 digits.push_str(&"0".repeat(point - digits.len()));
527 digits
528 } else {
529 digits.truncate(point);
531 digits
532 }
533 };
534
535 let normalized = match int_part.trim_start_matches('0') {
537 "" => "0",
538 trimmed => trimmed,
539 };
540
541 Some(if negative && normalized != "0" {
542 format!("-{normalized}")
543 } else {
544 normalized.to_string()
545 })
546}
547
548impl<'a> ShapeDeserializer for DocumentShapeDeserializer<'a> {
549 fn read_struct(
550 &mut self,
551 schema: &Schema<'_>,
552 consumer: &mut dyn FnMut(&Schema<'_>, &mut dyn ShapeDeserializer) -> Result<(), SerdeError>,
553 ) -> Result<(), SerdeError> {
554 let map = self
555 .cursor
556 .as_object()
557 .ok_or_else(|| type_mismatch("struct (map)", self.cursor))?;
558 for (key, value) in map {
559 let Some(member_schema) = resolve_member(schema, key) else {
560 continue;
564 };
565 let mut sub = Self::new_with_settings(value, self.settings.clone());
566 consumer(member_schema, &mut sub)?;
567 }
568 Ok(())
569 }
570
571 fn read_list(
572 &mut self,
573 _schema: &Schema<'_>,
574 consumer: &mut dyn FnMut(&mut dyn ShapeDeserializer) -> Result<(), SerdeError>,
575 ) -> Result<(), SerdeError> {
576 let items = self
577 .cursor
578 .as_array()
579 .ok_or_else(|| type_mismatch("list", self.cursor))?;
580 for item in items {
581 let mut sub = Self::new_with_settings(item, self.settings.clone());
582 consumer(&mut sub)?;
583 }
584 Ok(())
585 }
586
587 fn read_map(
588 &mut self,
589 _schema: &Schema<'_>,
590 consumer: &mut dyn FnMut(String, &mut dyn ShapeDeserializer) -> Result<(), SerdeError>,
591 ) -> Result<(), SerdeError> {
592 let entries = self
593 .cursor
594 .as_object()
595 .ok_or_else(|| type_mismatch("map", self.cursor))?;
596 for (key, value) in entries {
597 let mut sub = Self::new_with_settings(value, self.settings.clone());
598 consumer(key.clone(), &mut sub)?;
599 }
600 Ok(())
601 }
602
603 fn read_boolean(&mut self, _schema: &Schema<'_>) -> Result<bool, SerdeError> {
604 self.cursor
605 .as_bool()
606 .ok_or_else(|| type_mismatch("boolean", self.cursor))
607 }
608
609 fn read_byte(&mut self, _schema: &Schema<'_>) -> Result<i8, SerdeError> {
610 coerce_signed::<i8>(self.cursor, "byte")
611 }
612
613 fn read_short(&mut self, _schema: &Schema<'_>) -> Result<i16, SerdeError> {
614 coerce_signed::<i16>(self.cursor, "short")
615 }
616
617 fn read_integer(&mut self, _schema: &Schema<'_>) -> Result<i32, SerdeError> {
618 coerce_signed::<i32>(self.cursor, "integer")
619 }
620
621 fn read_long(&mut self, _schema: &Schema<'_>) -> Result<i64, SerdeError> {
622 coerce_signed::<i64>(self.cursor, "long")
623 }
624
625 fn read_float(&mut self, _schema: &Schema<'_>) -> Result<f32, SerdeError> {
626 coerce_float(self.cursor)
627 }
628
629 fn read_double(&mut self, _schema: &Schema<'_>) -> Result<f64, SerdeError> {
630 coerce_double(self.cursor)
631 }
632
633 fn read_big_integer(&mut self, _schema: &Schema<'_>) -> Result<BigInteger, SerdeError> {
634 coerce_big_integer(self.cursor)
638 }
639
640 fn read_big_decimal(&mut self, _schema: &Schema<'_>) -> Result<BigDecimal, SerdeError> {
641 coerce_big_decimal(self.cursor)
642 }
643
644 fn read_string(&mut self, _schema: &Schema<'_>) -> Result<String, SerdeError> {
645 match self.cursor {
646 Document::String(s) => Ok(s.clone()),
647 other => Err(type_mismatch("string", other)),
648 }
649 }
650
651 fn read_blob(&mut self, _schema: &Schema<'_>) -> Result<Blob, SerdeError> {
652 match (self.cursor, &self.settings) {
661 (Document::String(s), Some(settings)) => settings
662 .coerce_string_to_blob(s)
663 .map(Blob::new)
664 .map_err(SerdeError::from),
665 (Document::String(s), None) => aws_smithy_types::base64::decode(s)
666 .map(Blob::new)
667 .map_err(|e| {
668 SerdeError::custom(format!("cannot base64-decode string as blob: {e}"))
669 }),
670 (other, _) => Err(type_mismatch("blob", other)),
671 }
672 }
673
674 fn read_timestamp(&mut self, _schema: &Schema<'_>) -> Result<DateTime, SerdeError> {
675 match (self.cursor, &self.settings) {
686 (Document::Number(n), Some(settings)) => settings
687 .coerce_number_to_timestamp(n)
688 .map_err(SerdeError::from),
689 (Document::Number(n), None) => number_to_timestamp(n),
690 (Document::String(s), Some(settings)) => settings
691 .coerce_string_to_timestamp(s)
692 .map_err(SerdeError::from),
693 (Document::String(s), None) => DateTime::from_str(s, Format::DateTime).map_err(|e| {
694 SerdeError::custom(format!("cannot parse string as a date-time timestamp: {e}"))
695 }),
696 (other, _) => Err(type_mismatch("timestamp", other)),
697 }
698 }
699
700 fn read_document(&mut self, _schema: &Schema<'_>) -> Result<Document, SerdeError> {
701 Ok(self.cursor.clone())
702 }
703
704 fn is_null(&self) -> bool {
705 matches!(self.cursor, Document::Null)
706 }
707
708 fn skip_value(&mut self) -> Result<(), SerdeError> {
712 Ok(())
713 }
714
715 fn container_size(&self) -> Option<usize> {
716 let raw = match self.cursor {
717 Document::Array(items) => items.len(),
718 Document::Object(entries) => entries.len(),
719 _ => return None,
720 };
721 Some(capped_container_size(raw))
722 }
723}
724
725#[cfg(test)]
726mod tests {
727 use std::collections::HashMap;
728
729 use super::*;
730 use crate::document::DocumentShapeSerializer;
731 use crate::serde::{SerdeError, SerializableStruct, ShapeDeserializer, ShapeSerializer};
732 use crate::{prelude, shape_id, Schema, ShapeId, ShapeType};
733
734 use aws_smithy_types::Number;
735
736 const PERSON_ID: ShapeId<'static> = shape_id!("smithy.example", "Person");
739 const PERSON_NAME_ID: ShapeId<'static> = shape_id!("smithy.example", "Person", "name");
740 const PERSON_AGE_ID: ShapeId<'static> = shape_id!("smithy.example", "Person", "age");
741
742 static PERSON_NAME_MEMBER: Schema<'static> =
743 Schema::new_member(PERSON_NAME_ID, ShapeType::String, "name", 0);
744 static PERSON_AGE_MEMBER: Schema<'static> =
745 Schema::new_member(PERSON_AGE_ID, ShapeType::Integer, "age", 1);
746 static PERSON_SCHEMA: Schema<'static> = Schema::new_struct(
747 PERSON_ID,
748 ShapeType::Structure,
749 &[&PERSON_NAME_MEMBER, &PERSON_AGE_MEMBER],
750 );
751
752 #[derive(Debug, Default, PartialEq)]
755 struct Person {
756 name: Option<String>,
757 age: Option<i32>,
758 }
759
760 impl SerializableStruct for Person {
761 fn schema(&self) -> &Schema<'_> {
762 &PERSON_SCHEMA
763 }
764
765 fn serialize_members(&self, ser: &mut dyn ShapeSerializer) -> Result<(), SerdeError> {
766 if let Some(n) = &self.name {
767 ser.write_string(&PERSON_NAME_MEMBER, n)?;
768 }
769 if let Some(a) = self.age {
770 ser.write_integer(&PERSON_AGE_MEMBER, a)?;
771 }
772 Ok(())
773 }
774 }
775
776 fn deserialize_person(deser: &mut dyn ShapeDeserializer) -> Result<Person, SerdeError> {
777 let mut out = Person::default();
778 deser.read_struct(&PERSON_SCHEMA, &mut |member, sub| {
779 match member.member_index() {
780 Some(0) => out.name = Some(sub.read_string(member)?),
781 Some(1) => out.age = Some(sub.read_integer(member)?),
782 _ => {}
783 }
784 Ok(())
785 })?;
786 Ok(out)
787 }
788
789 #[test]
792 fn read_string_returns_value() {
793 let doc = Document::String("hello".to_string());
794 let mut deser = DocumentShapeDeserializer::new(&doc);
795 assert_eq!(deser.read_string(&prelude::STRING).unwrap(), "hello");
796 }
797
798 #[test]
799 fn read_string_on_non_string_errors() {
800 let doc = Document::Number(Number::PosInt(1));
801 let mut deser = DocumentShapeDeserializer::new(&doc);
802 let err = deser.read_string(&prelude::STRING).unwrap_err();
803 assert!(matches!(err, SerdeError::TypeMismatch { .. }));
804 }
805
806 #[test]
807 fn read_integer_with_coercion() {
808 let doc = Document::Number(Number::PosInt(42));
810 let mut deser = DocumentShapeDeserializer::new(&doc);
811 assert_eq!(deser.read_integer(&prelude::INTEGER).unwrap(), 42);
812 }
813
814 #[test]
815 fn read_integer_overflow_errors() {
816 let doc = Document::Number(Number::PosInt(u64::MAX));
817 let mut deser = DocumentShapeDeserializer::new(&doc);
818 let err = deser.read_integer(&prelude::INTEGER).unwrap_err();
819 assert!(matches!(err, SerdeError::NumericCoercionOverflow { .. }));
820 }
821
822 #[test]
823 fn read_boolean_returns_value() {
824 let doc = Document::Bool(true);
825 let mut deser = DocumentShapeDeserializer::new(&doc);
826 assert!(deser.read_boolean(&prelude::BOOLEAN).unwrap());
827 }
828
829 #[test]
830 fn read_blob_decodes_base64_string_without_settings() {
831 let doc = Document::String("YWJjZA==".to_string());
836 let mut deser = DocumentShapeDeserializer::new(&doc);
837 let blob = deser.read_blob(&prelude::BLOB).unwrap();
838 assert_eq!(blob.as_ref(), b"abcd");
839 }
840
841 #[test]
842 fn read_blob_on_malformed_base64_errors() {
843 let doc = Document::String("not base64!!!".to_string());
844 let mut deser = DocumentShapeDeserializer::new(&doc);
845 let err = deser.read_blob(&prelude::BLOB).unwrap_err();
846 let msg = format!("{err}");
847 assert!(msg.contains("base64"), "unexpected error message: {msg}");
848 }
849
850 #[test]
851 fn read_blob_on_non_string_errors() {
852 let doc = Document::Number(Number::PosInt(1));
853 let mut deser = DocumentShapeDeserializer::new(&doc);
854 let err = deser.read_blob(&prelude::BLOB).unwrap_err();
855 assert!(matches!(err, SerdeError::TypeMismatch { .. }));
856 }
857
858 #[test]
859 fn read_timestamp_defaults_to_epoch_seconds_without_settings() {
860 let doc = Document::Number(Number::PosInt(1234));
861 let mut deser = DocumentShapeDeserializer::new(&doc);
862 assert_eq!(
863 deser.read_timestamp(&prelude::TIMESTAMP).unwrap(),
864 DateTime::from_secs(1234)
865 );
866
867 let doc = Document::Number(Number::NegInt(-9));
868 let mut deser = DocumentShapeDeserializer::new(&doc);
869 assert_eq!(
870 deser.read_timestamp(&prelude::TIMESTAMP).unwrap(),
871 DateTime::from_secs(-9)
872 );
873 }
874
875 #[test]
876 fn read_timestamp_retains_fractional_seconds() {
877 let doc = Document::Number(Number::Float(1234.5));
878 let mut deser = DocumentShapeDeserializer::new(&doc);
879 assert_eq!(
880 deser.read_timestamp(&prelude::TIMESTAMP).unwrap(),
881 DateTime::from_secs_f64(1234.5)
882 );
883 }
884
885 #[test]
886 fn read_timestamp_parses_date_time_string_without_settings() {
887 let doc = Document::String("1970-01-01T00:00:00Z".to_string());
888 let mut deser = DocumentShapeDeserializer::new(&doc);
889 assert_eq!(
890 deser.read_timestamp(&prelude::TIMESTAMP).unwrap(),
891 DateTime::from_secs(0)
892 );
893 }
894
895 #[test]
896 fn read_timestamp_on_non_coercible_variant_errors() {
897 let doc = Document::Bool(true);
898 let mut deser = DocumentShapeDeserializer::new(&doc);
899 let err = deser.read_timestamp(&prelude::TIMESTAMP).unwrap_err();
900 assert!(matches!(err, SerdeError::TypeMismatch { .. }));
901 }
902
903 #[test]
904 fn read_big_integer_and_decimal_reverse_the_string_representation() {
905 let big = "12345678901234567890123456789";
908 let doc = Document::String(big.to_string());
909 let mut deser = DocumentShapeDeserializer::new(&doc);
910 assert_eq!(
911 deser
912 .read_big_integer(&prelude::BIG_INTEGER)
913 .unwrap()
914 .as_ref(),
915 big
916 );
917
918 let dec = "12345678901234567890123456789.25";
919 let doc = Document::String(dec.to_string());
920 let mut deser = DocumentShapeDeserializer::new(&doc);
921 assert_eq!(
922 deser
923 .read_big_decimal(&prelude::BIG_DECIMAL)
924 .unwrap()
925 .as_ref(),
926 dec
927 );
928 }
929
930 #[test]
931 fn is_null_for_null_document() {
932 let doc = Document::Null;
933 let deser = DocumentShapeDeserializer::new(&doc);
934 assert!(deser.is_null());
935 }
936
937 #[test]
938 fn is_null_false_for_non_null() {
939 let doc = Document::String("not-null".to_string());
940 let deser = DocumentShapeDeserializer::new(&doc);
941 assert!(!deser.is_null());
942 }
943
944 #[test]
947 fn read_list_iterates_elements() {
948 let doc = Document::Array(vec![
949 Document::String("a".to_string()),
950 Document::String("b".to_string()),
951 Document::String("c".to_string()),
952 ]);
953 let mut deser = DocumentShapeDeserializer::new(&doc);
954 let mut collected = Vec::new();
955 deser
956 .read_list(&prelude::DOCUMENT, &mut |sub| {
957 collected.push(sub.read_string(&prelude::STRING)?);
958 Ok(())
959 })
960 .unwrap();
961 assert_eq!(collected, ["a", "b", "c"]);
962 }
963
964 #[test]
965 fn read_map_iterates_entries() {
966 let doc = Document::Object(HashMap::from([
967 ("k1".to_string(), Document::String("v1".to_string())),
968 ("k2".to_string(), Document::String("v2".to_string())),
969 ]));
970 let mut deser = DocumentShapeDeserializer::new(&doc);
971 let mut collected = HashMap::new();
972 deser
973 .read_map(&prelude::DOCUMENT, &mut |key, sub| {
974 collected.insert(key, sub.read_string(&prelude::STRING)?);
975 Ok(())
976 })
977 .unwrap();
978 assert_eq!(collected.get("k1").map(String::as_str), Some("v1"));
979 assert_eq!(collected.get("k2").map(String::as_str), Some("v2"));
980 }
981
982 #[test]
983 fn container_size_on_list() {
984 let doc = Document::Array(vec![Document::Null; 5]);
985 let deser = DocumentShapeDeserializer::new(&doc);
986 assert_eq!(deser.container_size(), Some(5));
987 }
988
989 #[test]
990 fn container_size_on_scalar_is_none() {
991 let doc = Document::String("foo".to_string());
992 let deser = DocumentShapeDeserializer::new(&doc);
993 assert!(deser.container_size().is_none());
994 }
995
996 #[test]
999 fn read_struct_with_consumer_dispatch() {
1000 let doc = Document::Object(HashMap::from([
1001 ("name".to_string(), Document::String("Alex".to_string())),
1002 ("age".to_string(), Document::Number(Number::PosInt(30))),
1003 ]));
1004 let mut deser = DocumentShapeDeserializer::new(&doc);
1005 let person = deserialize_person(&mut deser).unwrap();
1006 assert_eq!(
1007 person,
1008 Person {
1009 name: Some("Alex".into()),
1010 age: Some(30),
1011 }
1012 );
1013 }
1014
1015 #[test]
1016 fn read_struct_with_missing_optional_member() {
1017 let doc = Document::Object(HashMap::from([(
1019 "name".to_string(),
1020 Document::String("Sam".to_string()),
1021 )]));
1022 let mut deser = DocumentShapeDeserializer::new(&doc);
1023 let person = deserialize_person(&mut deser).unwrap();
1024 assert_eq!(
1025 person,
1026 Person {
1027 name: Some("Sam".into()),
1028 age: None,
1029 }
1030 );
1031 }
1032
1033 #[test]
1034 fn read_struct_ignores_unknown_members() {
1035 let doc = Document::Object(HashMap::from([
1036 ("name".to_string(), Document::String("Joe".to_string())),
1037 (
1038 "unknown_field".to_string(),
1039 Document::String("ignored".to_string()),
1040 ),
1041 ]));
1042 let mut deser = DocumentShapeDeserializer::new(&doc);
1043 let person = deserialize_person(&mut deser).unwrap();
1044 assert_eq!(person.name.as_deref(), Some("Joe"));
1045 }
1046
1047 #[test]
1048 fn read_struct_on_non_map_errors() {
1049 let doc = Document::String("not-a-struct".to_string());
1050 let mut deser = DocumentShapeDeserializer::new(&doc);
1051 let err = deserialize_person(&mut deser).unwrap_err();
1052 assert!(matches!(err, SerdeError::TypeMismatch { .. }));
1053 }
1054
1055 #[test]
1058 fn round_trip_through_document() {
1059 let original = Person {
1060 name: Some("Iago".into()),
1061 age: Some(7),
1062 };
1063 let mut ser = DocumentShapeSerializer::new();
1065 ser.write_struct(&PERSON_SCHEMA, &original).unwrap();
1066 let doc = ser.finish().unwrap();
1067 let mut deser = DocumentShapeDeserializer::new(doc.document());
1069 let restored = deserialize_person(&mut deser).unwrap();
1070 assert_eq!(restored, original);
1071 assert_eq!(doc.discriminator(), Some("smithy.example#Person"));
1073 }
1074
1075 fn num(n: Number) -> Document {
1085 Document::Number(n)
1086 }
1087
1088 #[test]
1089 fn read_signed_integers_coerce_across_bounded_integer_sources() {
1090 let doc = num(Number::PosInt(42));
1091 assert_eq!(
1092 DocumentShapeDeserializer::new(&doc)
1093 .read_byte(&prelude::BYTE)
1094 .unwrap(),
1095 42
1096 );
1097 let doc = num(Number::NegInt(-42));
1098 assert_eq!(
1099 DocumentShapeDeserializer::new(&doc)
1100 .read_byte(&prelude::BYTE)
1101 .unwrap(),
1102 -42
1103 );
1104
1105 let doc = num(Number::PosInt(1234));
1106 assert_eq!(
1107 DocumentShapeDeserializer::new(&doc)
1108 .read_short(&prelude::SHORT)
1109 .unwrap(),
1110 1234
1111 );
1112
1113 let doc = num(Number::PosInt(i32::MAX as u64));
1114 assert_eq!(
1115 DocumentShapeDeserializer::new(&doc)
1116 .read_integer(&prelude::INTEGER)
1117 .unwrap(),
1118 i32::MAX
1119 );
1120 let doc = num(Number::NegInt(i32::MIN as i64));
1121 assert_eq!(
1122 DocumentShapeDeserializer::new(&doc)
1123 .read_integer(&prelude::INTEGER)
1124 .unwrap(),
1125 i32::MIN
1126 );
1127
1128 let doc = num(Number::PosInt(i64::MAX as u64));
1129 assert_eq!(
1130 DocumentShapeDeserializer::new(&doc)
1131 .read_long(&prelude::LONG)
1132 .unwrap(),
1133 i64::MAX
1134 );
1135 }
1136
1137 #[test]
1138 fn read_byte_overflow_populates_target_and_value() {
1139 let doc = num(Number::PosInt(200));
1140 let err = DocumentShapeDeserializer::new(&doc)
1141 .read_byte(&prelude::BYTE)
1142 .unwrap_err();
1143 match err {
1144 SerdeError::NumericCoercionOverflow { target, value } => {
1145 assert_eq!(target, "byte");
1146 assert_eq!(value, "200");
1147 }
1148 other => panic!("expected NumericCoercionOverflow, got {other:?}"),
1149 }
1150 }
1151
1152 #[test]
1153 fn read_signed_integers_overflow_at_the_edges() {
1154 let doc = num(Number::NegInt(i64::from(i32::MIN) - 1));
1157 assert!(matches!(
1158 DocumentShapeDeserializer::new(&doc)
1159 .read_integer(&prelude::INTEGER)
1160 .unwrap_err(),
1161 SerdeError::NumericCoercionOverflow { .. }
1162 ));
1163
1164 let doc = num(Number::PosInt(u64::MAX));
1165 assert!(matches!(
1166 DocumentShapeDeserializer::new(&doc)
1167 .read_long(&prelude::LONG)
1168 .unwrap_err(),
1169 SerdeError::NumericCoercionOverflow { .. }
1170 ));
1171 }
1172
1173 #[test]
1174 fn read_byte_on_non_numeric_is_type_mismatch() {
1175 let doc = Document::Bool(true);
1176 assert!(matches!(
1177 DocumentShapeDeserializer::new(&doc)
1178 .read_byte(&prelude::BYTE)
1179 .unwrap_err(),
1180 SerdeError::TypeMismatch { .. }
1181 ));
1182 }
1183
1184 #[test]
1185 fn integer_reads_accept_integral_floats_and_reject_fractional_ones() {
1186 let read_all = |value: f64| {
1187 let doc = num(Number::Float(value));
1188 let de = || DocumentShapeDeserializer::new(&doc);
1189 (
1190 de().read_byte(&prelude::BYTE),
1191 de().read_short(&prelude::SHORT),
1192 de().read_integer(&prelude::INTEGER),
1193 de().read_long(&prelude::LONG),
1194 de().read_big_integer(&prelude::BIG_INTEGER),
1195 )
1196 };
1197 let (b, sh, i, l, bi) = read_all(42.0);
1198 assert_eq!(
1199 (b.unwrap(), sh.unwrap(), i.unwrap(), l.unwrap()),
1200 (42, 42, 42, 42)
1201 );
1202 assert_eq!(bi.unwrap().as_ref(), "42");
1203 assert_eq!(read_all(-0.0).2.unwrap(), 0);
1204 assert_eq!(read_all(-0.0).4.unwrap().as_ref(), "0");
1205
1206 for value in [42.7, -0.5, f64::NAN, f64::INFINITY, f64::NEG_INFINITY] {
1208 let (b, sh, i, l, bi) = read_all(value);
1209 for err in [b.err(), sh.err(), i.err(), l.err(), bi.err()] {
1210 assert!(
1211 matches!(err, Some(SerdeError::NumericCoercionLossy { .. })),
1212 "{value}: {err:?}"
1213 );
1214 }
1215 }
1216 }
1217
1218 #[test]
1219 fn integral_float_range_checks_are_exact() {
1220 let long = |v: f64| {
1221 DocumentShapeDeserializer::new(&num(Number::Float(v))).read_long(&prelude::LONG)
1222 };
1223 let int = |v: f64| {
1224 DocumentShapeDeserializer::new(&num(Number::Float(v))).read_integer(&prelude::INTEGER)
1225 };
1226 const TWO_POW_63: f64 = 9_223_372_036_854_775_808.0;
1227 assert_eq!(long(-TWO_POW_63).unwrap(), i64::MIN);
1229 assert!(matches!(
1230 long(TWO_POW_63),
1231 Err(SerdeError::NumericCoercionOverflow { .. })
1232 ));
1233 assert_eq!(int(2_147_483_647.0).unwrap(), i32::MAX);
1234 assert_eq!(int(-2_147_483_648.0).unwrap(), i32::MIN);
1235 assert!(matches!(
1236 int(2_147_483_648.0),
1237 Err(SerdeError::NumericCoercionOverflow { .. })
1238 ));
1239 let big = DocumentShapeDeserializer::new(&num(Number::Float(1e20)))
1241 .read_big_integer(&prelude::BIG_INTEGER)
1242 .unwrap();
1243 assert_eq!(big.as_ref(), "100000000000000000000");
1244 let big = DocumentShapeDeserializer::new(&num(Number::Float(TWO_POW_63)))
1245 .read_big_integer(&prelude::BIG_INTEGER)
1246 .unwrap();
1247 assert_eq!(big.as_ref(), "9223372036854775808");
1248 }
1249
1250 #[test]
1251 fn read_double_and_float_widen_from_integer_sources() {
1252 let doc = num(Number::PosInt(42));
1255 assert_eq!(
1256 DocumentShapeDeserializer::new(&doc)
1257 .read_double(&prelude::DOUBLE)
1258 .unwrap(),
1259 42.0
1260 );
1261 assert_eq!(
1262 DocumentShapeDeserializer::new(&doc)
1263 .read_float(&prelude::FLOAT)
1264 .unwrap(),
1265 42.0_f32
1266 );
1267
1268 let doc = num(Number::NegInt(-42));
1269 assert_eq!(
1270 DocumentShapeDeserializer::new(&doc)
1271 .read_double(&prelude::DOUBLE)
1272 .unwrap(),
1273 -42.0
1274 );
1275
1276 let doc = num(Number::Float(1.5));
1277 assert_eq!(
1278 DocumentShapeDeserializer::new(&doc)
1279 .read_float(&prelude::FLOAT)
1280 .unwrap(),
1281 1.5_f32
1282 );
1283 }
1284
1285 #[test]
1286 fn read_double_carries_non_finite_floats_intact() {
1287 let doc = num(Number::Float(f64::NAN));
1290 assert!(DocumentShapeDeserializer::new(&doc)
1291 .read_double(&prelude::DOUBLE)
1292 .unwrap()
1293 .is_nan());
1294
1295 let doc = num(Number::Float(f64::INFINITY));
1296 assert_eq!(
1297 DocumentShapeDeserializer::new(&doc)
1298 .read_double(&prelude::DOUBLE)
1299 .unwrap(),
1300 f64::INFINITY
1301 );
1302
1303 let doc = num(Number::Float(f64::NEG_INFINITY));
1304 assert_eq!(
1305 DocumentShapeDeserializer::new(&doc)
1306 .read_double(&prelude::DOUBLE)
1307 .unwrap(),
1308 f64::NEG_INFINITY
1309 );
1310 }
1311
1312 #[test]
1313 fn read_big_integer_truncates_a_decimal_string_as_text() {
1314 let doc = Document::String("12345678901234567890.123".to_owned());
1317 assert_eq!(
1318 DocumentShapeDeserializer::new(&doc)
1319 .read_big_integer(&prelude::BIG_INTEGER)
1320 .unwrap()
1321 .as_ref(),
1322 "12345678901234567890"
1323 );
1324 }
1325
1326 #[test]
1327 fn read_big_integer_expands_scientific_notation() {
1328 let doc = Document::String("1.23e10".to_owned());
1331 assert_eq!(
1332 DocumentShapeDeserializer::new(&doc)
1333 .read_big_integer(&prelude::BIG_INTEGER)
1334 .unwrap()
1335 .as_ref(),
1336 "12300000000"
1337 );
1338
1339 let doc = Document::String("1e30".to_owned());
1340 assert_eq!(
1341 DocumentShapeDeserializer::new(&doc)
1342 .read_big_integer(&prelude::BIG_INTEGER)
1343 .unwrap()
1344 .as_ref(),
1345 "1000000000000000000000000000000"
1346 );
1347 }
1348
1349 #[test]
1350 fn read_big_integer_errors_on_unmaterializable_exponent() {
1351 let doc = Document::String("1e1000000000".to_owned());
1354 let err = DocumentShapeDeserializer::new(&doc)
1355 .read_big_integer(&prelude::BIG_INTEGER)
1356 .unwrap_err();
1357 assert!(
1358 err.to_string().contains("too large"),
1359 "expected a 'too large' error, got: {err}"
1360 );
1361 }
1362
1363 #[test]
1364 fn read_big_integer_rejects_non_numeric_text() {
1365 let doc = Document::String("not a number".to_owned());
1366 assert!(matches!(
1367 DocumentShapeDeserializer::new(&doc)
1368 .read_big_integer(&prelude::BIG_INTEGER)
1369 .unwrap_err(),
1370 SerdeError::InvalidInput { .. }
1371 ));
1372 }
1373
1374 #[test]
1375 fn read_big_numbers_coerce_from_integer_sources() {
1376 let doc = num(Number::PosInt(7));
1377 assert_eq!(
1378 DocumentShapeDeserializer::new(&doc)
1379 .read_big_integer(&prelude::BIG_INTEGER)
1380 .unwrap()
1381 .as_ref(),
1382 "7"
1383 );
1384 assert_eq!(
1385 DocumentShapeDeserializer::new(&doc)
1386 .read_big_decimal(&prelude::BIG_DECIMAL)
1387 .unwrap()
1388 .as_ref(),
1389 "7"
1390 );
1391
1392 let doc = num(Number::NegInt(-7));
1393 assert_eq!(
1394 DocumentShapeDeserializer::new(&doc)
1395 .read_big_decimal(&prelude::BIG_DECIMAL)
1396 .unwrap()
1397 .as_ref(),
1398 "-7"
1399 );
1400 }
1401
1402 #[test]
1403 fn read_big_decimal_rejects_non_finite_floats() {
1404 let doc = num(Number::Float(f64::INFINITY));
1408 let err = DocumentShapeDeserializer::new(&doc)
1409 .read_big_decimal(&prelude::BIG_DECIMAL)
1410 .unwrap_err();
1411 match err {
1412 SerdeError::Custom { message } => assert!(message.contains("non-finite")),
1413 other => panic!("expected Custom non-finite error, got {other:?}"),
1414 }
1415 }
1416
1417 #[test]
1418 fn big_number_reads_reject_structural_variants() {
1419 let doc = Document::Array(vec![]);
1420 assert!(matches!(
1421 DocumentShapeDeserializer::new(&doc)
1422 .read_big_integer(&prelude::BIG_INTEGER)
1423 .unwrap_err(),
1424 SerdeError::TypeMismatch { .. }
1425 ));
1426 assert!(matches!(
1427 DocumentShapeDeserializer::new(&doc)
1428 .read_big_decimal(&prelude::BIG_DECIMAL)
1429 .unwrap_err(),
1430 SerdeError::TypeMismatch { .. }
1431 ));
1432 assert!(matches!(
1433 DocumentShapeDeserializer::new(&doc)
1434 .read_double(&prelude::DOUBLE)
1435 .unwrap_err(),
1436 SerdeError::TypeMismatch { .. }
1437 ));
1438 }
1439
1440 #[test]
1441 fn big_integer_legacy_string_round_trips() {
1442 let bi = BigInteger::from_str("98765432109876543210").unwrap();
1445 let doc = Document::String(bi.as_ref().to_owned());
1446 assert_eq!(
1447 DocumentShapeDeserializer::new(&doc)
1448 .read_big_integer(&prelude::BIG_INTEGER)
1449 .unwrap(),
1450 bi
1451 );
1452 }
1453
1454 #[test]
1457 fn big_decimal_to_integer_string_truncates_and_expands() {
1458 let cases = [
1460 ("0", "0"),
1461 ("123", "123"),
1462 ("123.99", "123"), ("-123.99", "-123"),
1464 ("0.5", "0"),
1465 ("-0.5", "0"), ("1.23e10", "12300000000"), ("1.23e1", "12"), ("1.5e1", "15"),
1469 ("1e3", "1000"),
1470 ("5e-3", "0"), ("-1.23e10", "-12300000000"),
1472 ("10.0", "10"),
1473 ("+123", "123"), (".5", "0"), ("-.5", "0"), ("1.", "1"), ("+1.23e1", "12"), ("00123", "123"), ("1.23E10", "12300000000"), ];
1481 for (input, expected) in cases {
1482 let bd = BigDecimal::from_str(input).unwrap();
1483 assert_eq!(
1484 big_decimal_to_integer_string(&bd).as_deref(),
1485 Some(expected),
1486 "big_decimal_to_integer_string({input:?})"
1487 );
1488 }
1489 }
1490
1491 #[test]
1492 fn big_decimal_to_integer_string_guards_pathological_exponent() {
1493 assert_eq!(
1496 big_decimal_to_integer_string(&BigDecimal::from_str("1e1000000000").unwrap()),
1497 None
1498 );
1499 assert_eq!(
1502 big_decimal_to_integer_string(
1503 &BigDecimal::from_str("1.0e9223372036854775808").unwrap()
1504 ),
1505 None
1506 );
1507 }
1510}
1511
1512#[cfg(test)]
1517mod skip_value_contract {
1518 use super::*;
1519 use crate::serde::ShapeDeserializer;
1520 use crate::{shape_id, Schema, ShapeType};
1521 use std::collections::HashMap;
1522
1523 static NAME: Schema<'static> = Schema::new_member(
1524 shape_id!("smithy.example", "Person", "name"),
1525 ShapeType::String,
1526 "name",
1527 0,
1528 );
1529 static AGE: Schema<'static> = Schema::new_member(
1530 shape_id!("smithy.example", "Person", "age"),
1531 ShapeType::Integer,
1532 "age",
1533 1,
1534 );
1535 static PERSON: Schema<'static> = Schema::new_struct(
1536 shape_id!("smithy.example", "Person"),
1537 ShapeType::Structure,
1538 &[&NAME, &AGE],
1539 );
1540
1541 #[test]
1542 fn skipping_one_member_leaves_the_others_readable() {
1543 let doc = Document::Object(HashMap::from([
1544 ("name".to_string(), Document::String("Alex".into())),
1545 (
1546 "age".to_string(),
1547 Document::Number(aws_smithy_types::Number::PosInt(30)),
1548 ),
1549 ]));
1550 let mut deser = DocumentShapeDeserializer::new(&doc);
1551 let mut age = None;
1552 deser
1553 .read_struct(&PERSON, &mut |member, d| {
1554 match member.member_index() {
1555 Some(0) => d.skip_value()?,
1556 Some(1) => age = Some(d.read_integer(member)?),
1557 _ => {}
1558 }
1559 Ok(())
1560 })
1561 .unwrap();
1562 assert_eq!(age, Some(30));
1563 }
1564
1565 #[test]
1566 fn skipping_an_aggregate_member_is_a_no_op() {
1567 let doc = Document::Object(HashMap::from([
1570 (
1571 "name".to_string(),
1572 Document::Array(vec![
1573 Document::String("nested".into()),
1574 Document::Object(HashMap::from([(
1575 "age".to_string(),
1576 Document::Number(aws_smithy_types::Number::PosInt(999)),
1577 )])),
1578 ]),
1579 ),
1580 (
1581 "age".to_string(),
1582 Document::Number(aws_smithy_types::Number::PosInt(30)),
1583 ),
1584 ]));
1585 let mut deser = DocumentShapeDeserializer::new(&doc);
1586 let mut age = None;
1587 deser
1588 .read_struct(&PERSON, &mut |member, d| {
1589 match member.member_index() {
1590 Some(0) => d.skip_value()?,
1591 Some(1) => age = Some(d.read_integer(member)?),
1592 _ => {}
1593 }
1594 Ok(())
1595 })
1596 .unwrap();
1597 assert_eq!(age, Some(30), "the decoy nested `age` must not be read");
1598 }
1599
1600 #[test]
1601 fn skip_value_does_not_consume_the_cursor() {
1602 let doc = Document::String("still here".into());
1605 let mut deser = DocumentShapeDeserializer::new(&doc);
1606 let dynamic: &mut dyn ShapeDeserializer = &mut deser;
1607 dynamic.skip_value().unwrap();
1608 assert_eq!(deser.read_string(&NAME).unwrap(), "still here");
1609 }
1610}