1#![forbid(unsafe_code)]
67
68use std::collections::BTreeMap;
69
70use serde::de::DeserializeOwned;
71use serde::Serialize;
72
73use crate::codec::schema::{DynamicSchema, EnumVariantSchema, MAX_SCHEMA_DEPTH};
74use crate::error::{Error, Result};
75
76pub const MAX_DYNAMIC_DEPTH: usize = 32;
79
80pub const MAX_DYNAMIC_NODES: usize = 65_536;
84
85const TAG_NULL: u8 = 0x00;
88const TAG_BOOL: u8 = 0x01;
89const TAG_U64: u8 = 0x02;
90const TAG_I64: u8 = 0x03;
91const TAG_F64: u8 = 0x04;
92const TAG_STRING: u8 = 0x05;
93const TAG_BYTES: u8 = 0x06;
94const TAG_SEQ: u8 = 0x07;
95const TAG_MAP: u8 = 0x08;
96const TAG_ENUM: u8 = 0x09;
97
98#[derive(Debug, Clone, PartialEq)]
105#[non_exhaustive]
106pub enum Dynamic {
107 Null,
109 Bool(bool),
111 U64(u64),
113 I64(i64),
115 F64(f64),
119 String(String),
121 Bytes(Vec<u8>),
123 Seq(Vec<Dynamic>),
125 Map(BTreeMap<String, Dynamic>),
128 Enum {
137 variant: String,
139 payload: Box<Dynamic>,
141 },
142}
143
144impl Dynamic {
145 pub fn from_tagged_bytes(bytes: &[u8]) -> Result<Self> {
160 let (value, _rest) = Self::decode_value(bytes, 0, &mut 0)?;
161 Ok(value)
162 }
163
164 pub fn from_postcard_bytes(bytes: &[u8], schema: &DynamicSchema) -> Result<Self> {
184 let (value, rest) = walk_schema(bytes, schema)?;
185 debug_assert!(rest.len() <= bytes.len(), "walker consumed more than input");
186 if !rest.is_empty() {
187 return Err(Error::SchemaTypeMismatch {
190 expected: "exact",
191 found: "trailing-bytes",
192 path: String::new(),
193 });
194 }
195 Ok(value)
196 }
197
198 pub fn to_postcard_bytes(&self) -> Result<Vec<u8>> {
204 let mut buf = Vec::new();
205 self.encode_into(&mut buf)?;
206 Ok(buf)
207 }
208
209 fn deserialize_postcard<T: DeserializeOwned>(payload: &[u8]) -> Result<T> {
216 postcard::from_bytes::<T>(payload).map_err(Error::from)
217 }
218
219 #[must_use]
222 pub fn get(&self, field: &str) -> Option<&Dynamic> {
223 if let Dynamic::Map(m) = self {
224 m.get(field)
225 } else {
226 None
227 }
228 }
229
230 pub fn get_str(&self, field: &str) -> Result<&str> {
241 match self.get(field) {
242 Some(Dynamic::String(s)) => Ok(s.as_str()),
243 Some(other) => Err(Error::SchemaTypeMismatch {
244 expected: "String",
245 found: variant_name(other),
246 path: field.to_owned(),
247 }),
248 None => Err(Error::SchemaTypeMismatch {
249 expected: "String",
250 found: "absent",
251 path: field.to_owned(),
252 }),
253 }
254 }
255
256 pub fn set<S, V>(&mut self, field: S, value: V)
267 where
268 S: Into<String>,
269 V: Into<Dynamic>,
270 {
271 let key = field.into();
272 let val = value.into();
273 if let Dynamic::Map(m) = self {
274 m.insert(key, val);
275 } else {
276 let mut m = BTreeMap::new();
277 m.insert(key, val);
278 *self = Dynamic::Map(m);
279 }
280 }
281
282 pub fn remove(&mut self, field: &str) -> Result<Option<Dynamic>> {
305 match self {
306 Dynamic::Map(m) => Ok(m.remove(field)),
307 _ => Err(Error::DynamicPathNotMap {
308 path: field.to_owned(),
309 }),
310 }
311 }
312
313 #[must_use]
318 pub fn enum_variant(&self) -> Option<&str> {
319 match self {
320 Dynamic::Enum { variant, .. } => Some(variant.as_str()),
321 _ => None,
322 }
323 }
324
325 #[must_use]
330 pub fn enum_payload(&self) -> Option<&Dynamic> {
331 match self {
332 Dynamic::Enum { payload, .. } => Some(payload.as_ref()),
333 _ => None,
334 }
335 }
336
337 pub fn deserialize<T: DeserializeOwned>(&self) -> Result<T> {
355 let bytes = postcard::to_allocvec(self)?;
356 Self::deserialize_postcard::<T>(&bytes)
357 }
358
359 fn decode_value<'a>(
364 bytes: &'a [u8],
365 depth: usize,
366 nodes: &mut usize,
367 ) -> Result<(Self, &'a [u8])> {
368 if depth >= MAX_DYNAMIC_DEPTH {
369 return Err(Error::Corruption { page_id: 0 });
370 }
371 *nodes = nodes
372 .checked_add(1)
373 .ok_or(Error::Corruption { page_id: 0 })?;
374 if *nodes > MAX_DYNAMIC_NODES {
375 return Err(Error::Corruption { page_id: 0 });
376 }
377 let (tag, rest) = split_first(bytes)?;
378 Self::decode_body(tag, rest, depth, nodes)
379 }
380
381 fn decode_body<'a>(
382 tag: u8,
383 rest: &'a [u8],
384 depth: usize,
385 nodes: &mut usize,
386 ) -> Result<(Self, &'a [u8])> {
387 match tag {
388 TAG_NULL => Ok((Dynamic::Null, rest)),
389 TAG_BOOL => decode_bool(rest),
390 TAG_U64 => decode_u64(rest),
391 TAG_I64 => decode_i64(rest),
392 TAG_F64 => decode_f64(rest),
393 TAG_STRING => decode_string(rest),
394 TAG_BYTES => decode_bytes(rest),
395 TAG_SEQ => Self::decode_seq(rest, depth + 1, nodes),
396 TAG_MAP => Self::decode_map(rest, depth + 1, nodes),
397 TAG_ENUM => Self::decode_enum(rest, depth + 1, nodes),
398 _ => Err(Error::Corruption { page_id: 0 }),
399 }
400 }
401
402 fn decode_enum<'a>(
407 bytes: &'a [u8],
408 depth: usize,
409 nodes: &mut usize,
410 ) -> Result<(Self, &'a [u8])> {
411 let (name_bytes, after_name) = take_len_prefixed(bytes)?;
412 let name = std::str::from_utf8(name_bytes)
413 .map_err(|_| Error::Corruption { page_id: 0 })?
414 .to_owned();
415 let (payload, after_payload) = Self::decode_value(after_name, depth, nodes)?;
416 Ok((
417 Dynamic::Enum {
418 variant: name,
419 payload: Box::new(payload),
420 },
421 after_payload,
422 ))
423 }
424
425 fn decode_seq<'a>(
426 bytes: &'a [u8],
427 depth: usize,
428 nodes: &mut usize,
429 ) -> Result<(Self, &'a [u8])> {
430 let (len, mut rest) = take_varint_usize(bytes)?;
431 if len > MAX_DYNAMIC_NODES {
432 return Err(Error::Corruption { page_id: 0 });
433 }
434 let mut items = Vec::with_capacity(len);
435 for _ in 0..len {
436 let (item, next) = Self::decode_value(rest, depth, nodes)?;
437 items.push(item);
438 rest = next;
439 }
440 Ok((Dynamic::Seq(items), rest))
441 }
442
443 fn decode_map<'a>(
444 bytes: &'a [u8],
445 depth: usize,
446 nodes: &mut usize,
447 ) -> Result<(Self, &'a [u8])> {
448 let (len, mut rest) = take_varint_usize(bytes)?;
449 if len > MAX_DYNAMIC_NODES {
450 return Err(Error::Corruption { page_id: 0 });
451 }
452 let mut map = BTreeMap::new();
453 for _ in 0..len {
454 let (key_bytes, after_key) = take_len_prefixed(rest)?;
455 let key = std::str::from_utf8(key_bytes)
456 .map_err(|_| Error::Corruption { page_id: 0 })?
457 .to_owned();
458 let (value, after_value) = Self::decode_value(after_key, depth, nodes)?;
459 if map.insert(key, value).is_some() {
460 return Err(Error::Corruption { page_id: 0 });
464 }
465 rest = after_value;
466 }
467 Ok((Dynamic::Map(map), rest))
468 }
469
470 fn encode_into(&self, dst: &mut Vec<u8>) -> Result<()> {
471 encode_one_bounded(self, 0, dst)
472 }
473}
474
475fn encode_one_bounded(value: &Dynamic, depth: usize, dst: &mut Vec<u8>) -> Result<()> {
480 if depth >= MAX_DYNAMIC_DEPTH {
481 return Err(Error::Corruption { page_id: 0 });
482 }
483 match value {
484 Dynamic::Null => dst.push(TAG_NULL),
485 Dynamic::Bool(b) => {
486 dst.push(TAG_BOOL);
487 dst.push(u8::from(*b));
488 }
489 Dynamic::U64(n) => {
490 dst.push(TAG_U64);
491 write_varint_u64(*n, dst);
492 }
493 Dynamic::I64(n) => {
494 dst.push(TAG_I64);
495 write_varint_i64(*n, dst);
496 }
497 Dynamic::F64(f) => {
498 dst.push(TAG_F64);
499 dst.extend_from_slice(&f.to_le_bytes());
500 }
501 Dynamic::String(s) => {
502 dst.push(TAG_STRING);
503 write_varint_u64(s.len() as u64, dst);
504 dst.extend_from_slice(s.as_bytes());
505 }
506 Dynamic::Bytes(b) => {
507 dst.push(TAG_BYTES);
508 write_varint_u64(b.len() as u64, dst);
509 dst.extend_from_slice(b);
510 }
511 Dynamic::Seq(items) => {
512 dst.push(TAG_SEQ);
513 write_varint_u64(items.len() as u64, dst);
514 for item in items {
515 encode_one_bounded(item, depth + 1, dst)?;
516 }
517 }
518 Dynamic::Map(map) => {
519 dst.push(TAG_MAP);
520 write_varint_u64(map.len() as u64, dst);
521 for (k, v) in map {
522 write_varint_u64(k.len() as u64, dst);
523 dst.extend_from_slice(k.as_bytes());
524 encode_one_bounded(v, depth + 1, dst)?;
525 }
526 }
527 Dynamic::Enum { variant, payload } => {
528 dst.push(TAG_ENUM);
529 write_varint_u64(variant.len() as u64, dst);
530 dst.extend_from_slice(variant.as_bytes());
531 encode_one_bounded(payload, depth + 1, dst)?;
532 }
533 }
534 Ok(())
535}
536
537fn split_first(bytes: &[u8]) -> Result<(u8, &[u8])> {
540 let (first, rest) = bytes
541 .split_first()
542 .ok_or(Error::Corruption { page_id: 0 })?;
543 Ok((*first, rest))
544}
545
546fn take_n(bytes: &[u8], n: usize) -> Result<(&[u8], &[u8])> {
547 if bytes.len() < n {
548 return Err(Error::Corruption { page_id: 0 });
549 }
550 Ok(bytes.split_at(n))
551}
552
553fn take_len_prefixed(bytes: &[u8]) -> Result<(&[u8], &[u8])> {
554 let (len, rest) = take_varint_usize(bytes)?;
555 let (data, after) = take_n(rest, len)?;
556 Ok((data, after))
557}
558
559fn take_varint_usize(bytes: &[u8]) -> Result<(usize, &[u8])> {
562 let (v, rest) = take_varint_u64(bytes)?;
563 let v = usize::try_from(v).map_err(|_| Error::Corruption { page_id: 0 })?;
564 Ok((v, rest))
565}
566
567const MAX_VARINT_BYTES: usize = 10;
569
570fn take_varint_u64(bytes: &[u8]) -> Result<(u64, &[u8])> {
571 let mut value: u64 = 0;
572 let mut shift: u32 = 0;
573 let mut i: usize = 0;
574 while i < bytes.len() && i < MAX_VARINT_BYTES {
575 let b = bytes[i];
576 let part = u64::from(b & 0x7F);
577 let shifted = part
578 .checked_shl(shift)
579 .ok_or(Error::Corruption { page_id: 0 })?;
580 value |= shifted;
581 i += 1;
582 if (b & 0x80) == 0 {
583 return Ok((value, &bytes[i..]));
584 }
585 shift = shift
586 .checked_add(7)
587 .ok_or(Error::Corruption { page_id: 0 })?;
588 }
589 Err(Error::Corruption { page_id: 0 })
590}
591
592fn write_varint_u64(mut value: u64, dst: &mut Vec<u8>) {
593 while value >= 0x80 {
594 #[allow(clippy::cast_possible_truncation)] let lo = (value & 0x7F) as u8;
598 dst.push(lo | 0x80);
599 value >>= 7;
600 }
601 #[allow(clippy::cast_possible_truncation)] let last = value as u8;
604 dst.push(last);
605}
606
607fn write_varint_i64(value: i64, dst: &mut Vec<u8>) {
608 let zz = ((value << 1) ^ (value >> 63)).cast_unsigned();
611 write_varint_u64(zz, dst);
612}
613
614fn decode_bool(bytes: &[u8]) -> Result<(Dynamic, &[u8])> {
615 let (b, rest) = split_first(bytes)?;
616 match b {
617 0 => Ok((Dynamic::Bool(false), rest)),
618 1 => Ok((Dynamic::Bool(true), rest)),
619 _ => Err(Error::Corruption { page_id: 0 }),
620 }
621}
622
623fn decode_u64(bytes: &[u8]) -> Result<(Dynamic, &[u8])> {
624 let (v, rest) = take_varint_u64(bytes)?;
625 Ok((Dynamic::U64(v), rest))
626}
627
628fn decode_i64(bytes: &[u8]) -> Result<(Dynamic, &[u8])> {
629 let (zz, rest) = take_varint_u64(bytes)?;
630 let high = (zz >> 1).cast_signed();
634 let low = (zz & 1).cast_signed();
635 let v = high ^ -low;
636 Ok((Dynamic::I64(v), rest))
637}
638
639fn decode_f64(bytes: &[u8]) -> Result<(Dynamic, &[u8])> {
640 let (data, rest) = take_n(bytes, 8)?;
641 let mut buf = [0u8; 8];
642 buf.copy_from_slice(data);
643 Ok((Dynamic::F64(f64::from_le_bytes(buf)), rest))
644}
645
646fn decode_string(bytes: &[u8]) -> Result<(Dynamic, &[u8])> {
647 let (data, rest) = take_len_prefixed(bytes)?;
648 let s = std::str::from_utf8(data)
649 .map_err(|_| Error::Corruption { page_id: 0 })?
650 .to_owned();
651 Ok((Dynamic::String(s), rest))
652}
653
654fn decode_bytes(bytes: &[u8]) -> Result<(Dynamic, &[u8])> {
655 let (data, rest) = take_len_prefixed(bytes)?;
656 Ok((Dynamic::Bytes(data.to_vec()), rest))
657}
658
659impl Serialize for Dynamic {
662 fn serialize<S: serde::Serializer>(&self, ser: S) -> std::result::Result<S::Ok, S::Error> {
663 use serde::ser::{SerializeMap, SerializeSeq};
664 match self {
665 Dynamic::Null => ser.serialize_unit(),
666 Dynamic::Bool(b) => ser.serialize_bool(*b),
667 Dynamic::U64(n) => ser.serialize_u64(*n),
668 Dynamic::I64(n) => ser.serialize_i64(*n),
669 Dynamic::F64(f) => ser.serialize_f64(*f),
670 Dynamic::String(s) => ser.serialize_str(s),
671 Dynamic::Bytes(b) => ser.serialize_bytes(b),
672 Dynamic::Seq(items) => {
673 let mut s = ser.serialize_seq(Some(items.len()))?;
674 for item in items {
675 s.serialize_element(item)?;
676 }
677 s.end()
678 }
679 Dynamic::Map(map) => {
680 let mut m = ser.serialize_map(Some(map.len()))?;
681 for (k, v) in map {
682 m.serialize_entry(k, v)?;
683 }
684 m.end()
685 }
686 Dynamic::Enum { variant, payload } => {
694 let mut m = ser.serialize_map(Some(1))?;
695 m.serialize_entry(variant, payload.as_ref())?;
696 m.end()
697 }
698 }
699 }
700}
701
702impl From<bool> for Dynamic {
711 fn from(b: bool) -> Self {
712 Dynamic::Bool(b)
713 }
714}
715
716impl From<u64> for Dynamic {
717 fn from(n: u64) -> Self {
718 Dynamic::U64(n)
719 }
720}
721
722impl From<u32> for Dynamic {
723 fn from(n: u32) -> Self {
724 Dynamic::U64(u64::from(n))
725 }
726}
727
728impl From<i64> for Dynamic {
729 fn from(n: i64) -> Self {
730 Dynamic::I64(n)
731 }
732}
733
734impl From<i32> for Dynamic {
735 fn from(n: i32) -> Self {
736 Dynamic::I64(i64::from(n))
737 }
738}
739
740impl From<f64> for Dynamic {
741 fn from(f: f64) -> Self {
742 Dynamic::F64(f)
743 }
744}
745
746impl From<String> for Dynamic {
747 fn from(s: String) -> Self {
748 Dynamic::String(s)
749 }
750}
751
752impl From<&str> for Dynamic {
753 fn from(s: &str) -> Self {
754 Dynamic::String(s.to_owned())
755 }
756}
757
758impl From<Vec<u8>> for Dynamic {
759 fn from(b: Vec<u8>) -> Self {
760 Dynamic::Bytes(b)
761 }
762}
763
764impl From<&[u8]> for Dynamic {
765 fn from(b: &[u8]) -> Self {
766 Dynamic::Bytes(b.to_vec())
767 }
768}
769
770enum Frame {
788 Seq {
792 elem: DynamicSchema,
793 remaining: usize,
794 acc: Vec<Dynamic>,
795 },
796 Map {
807 pending: std::collections::VecDeque<(String, DynamicSchema)>,
808 current_name: Option<String>,
809 acc: BTreeMap<String, Dynamic>,
810 path_prefix: String,
811 },
812 Enum {
821 variant: String,
824 pending_payload: Option<DynamicSchema>,
827 acc: Option<Dynamic>,
830 #[allow(dead_code)] path_prefix: String,
834 },
835}
836
837fn walk_schema<'a>(bytes: &'a [u8], schema: &DynamicSchema) -> Result<(Dynamic, &'a [u8])> {
841 let mut stack: Vec<Frame> = Vec::new();
842 let mut rest = bytes;
843 let mut next_schema: DynamicSchema = schema.clone();
847 let mut next_path: String = String::new();
848 let mut iters: usize = 0;
849 let iter_cap = (1 + MAX_SCHEMA_DEPTH) * (1 + MAX_DYNAMIC_NODES);
850 loop {
851 iters = iters.checked_add(1).ok_or(Error::SchemaDepthExceeded {
855 depth: MAX_SCHEMA_DEPTH,
856 })?;
857 check_walk_schema_bounds(iters, iter_cap, stack.len())?;
858 let outcome = decode_slot(rest, &next_schema, &next_path, &mut stack)?;
859 rest = outcome.rest;
860 match advance_after_slot(outcome.value, &mut stack, next_path)? {
861 WalkStep::Complete(root) => return Ok((root, rest)),
862 WalkStep::Continue {
863 next_schema: ns,
864 next_path: np,
865 } => {
866 next_schema = ns;
867 next_path = np;
868 }
869 }
870 }
871}
872
873fn check_walk_schema_bounds(iters: usize, iter_cap: usize, stack_len: usize) -> Result<()> {
877 if iters > iter_cap {
878 return Err(Error::SchemaDepthExceeded {
879 depth: MAX_SCHEMA_DEPTH,
880 });
881 }
882 if stack_len >= MAX_SCHEMA_DEPTH {
883 return Err(Error::SchemaDepthExceeded {
884 depth: MAX_SCHEMA_DEPTH,
885 });
886 }
887 Ok(())
888}
889
890enum WalkStep {
894 Complete(Dynamic),
897 Continue {
900 next_schema: DynamicSchema,
901 next_path: String,
902 },
903}
904
905fn advance_after_slot(
911 slot_value: Option<Dynamic>,
912 stack: &mut Vec<Frame>,
913 current_path: String,
914) -> Result<WalkStep> {
915 match slot_value {
916 None => {
917 if let Some((next_schema, next_path)) = request_next_child(stack) {
920 Ok(WalkStep::Continue {
921 next_schema,
922 next_path,
923 })
924 } else {
925 Err(Error::SchemaTypeMismatch {
931 expected: "non-empty",
932 found: "empty-frame",
933 path: current_path,
934 })
935 }
936 }
937 Some(value) => {
938 if let Some(root) = fold_value(value, stack) {
939 return Ok(WalkStep::Complete(root));
940 }
941 let Some((next_schema, next_path)) = request_next_child(stack) else {
942 return Err(Error::SchemaTypeMismatch {
946 expected: "next-child",
947 found: "exhausted-frame",
948 path: current_path,
949 });
950 };
951 Ok(WalkStep::Continue {
952 next_schema,
953 next_path,
954 })
955 }
956 }
957}
958
959struct SlotOutcome<'a> {
961 value: Option<Dynamic>,
966 rest: &'a [u8],
968}
969
970fn decode_slot<'a>(
972 bytes: &'a [u8],
973 schema: &DynamicSchema,
974 path: &str,
975 stack: &mut Vec<Frame>,
976) -> Result<SlotOutcome<'a>> {
977 match schema {
978 DynamicSchema::Null => Ok(SlotOutcome {
979 value: Some(Dynamic::Null),
980 rest: bytes,
981 }),
982 DynamicSchema::Bool => {
983 let (v, rest) = decode_bool_schema(bytes, path)?;
984 Ok(SlotOutcome {
985 value: Some(v),
986 rest,
987 })
988 }
989 DynamicSchema::U64 => {
990 let (n, rest) = take_varint_u64(bytes)?;
991 Ok(SlotOutcome {
992 value: Some(Dynamic::U64(n)),
993 rest,
994 })
995 }
996 DynamicSchema::I64 => {
997 let (zz, rest) = take_varint_u64(bytes)?;
998 Ok(SlotOutcome {
999 value: Some(Dynamic::I64(zigzag_decode_i64(zz))),
1000 rest,
1001 })
1002 }
1003 DynamicSchema::F64 => {
1004 let (v, rest) = decode_f64_schema(bytes, path)?;
1005 Ok(SlotOutcome {
1006 value: Some(v),
1007 rest,
1008 })
1009 }
1010 DynamicSchema::String => {
1011 let (v, rest) = decode_string_schema(bytes, path)?;
1012 Ok(SlotOutcome {
1013 value: Some(v),
1014 rest,
1015 })
1016 }
1017 DynamicSchema::Bytes => {
1018 let (v, rest) = decode_bytes_schema(bytes, path)?;
1019 Ok(SlotOutcome {
1020 value: Some(v),
1021 rest,
1022 })
1023 }
1024 DynamicSchema::Seq(elem) => decode_seq_slot(bytes, elem, stack),
1025 DynamicSchema::Map(fields) => Ok(decode_map_slot(bytes, fields, path, stack)),
1026 DynamicSchema::Enum(variants) => decode_enum_slot(bytes, variants, path, stack),
1027 }
1028}
1029
1030fn decode_bool_schema<'a>(bytes: &'a [u8], path: &str) -> Result<(Dynamic, &'a [u8])> {
1032 let (b, rest) = bytes
1033 .split_first()
1034 .ok_or_else(|| Error::SchemaTypeMismatch {
1035 expected: "Bool",
1036 found: "truncated",
1037 path: path.to_owned(),
1038 })?;
1039 match *b {
1040 0 => Ok((Dynamic::Bool(false), rest)),
1041 1 => Ok((Dynamic::Bool(true), rest)),
1042 _ => Err(Error::SchemaTypeMismatch {
1043 expected: "Bool",
1044 found: "non-bool-byte",
1045 path: path.to_owned(),
1046 }),
1047 }
1048}
1049
1050fn decode_f64_schema<'a>(bytes: &'a [u8], path: &str) -> Result<(Dynamic, &'a [u8])> {
1052 if bytes.len() < 8 {
1053 return Err(Error::SchemaTypeMismatch {
1054 expected: "F64",
1055 found: "truncated",
1056 path: path.to_owned(),
1057 });
1058 }
1059 let (data, rest) = bytes.split_at(8);
1060 let mut buf = [0u8; 8];
1061 buf.copy_from_slice(data);
1062 Ok((Dynamic::F64(f64::from_le_bytes(buf)), rest))
1063}
1064
1065fn decode_string_schema<'a>(bytes: &'a [u8], path: &str) -> Result<(Dynamic, &'a [u8])> {
1067 let (len, rest) = take_varint_usize(bytes)?;
1068 if rest.len() < len {
1069 return Err(Error::SchemaTypeMismatch {
1070 expected: "String",
1071 found: "truncated",
1072 path: path.to_owned(),
1073 });
1074 }
1075 let (data, after) = rest.split_at(len);
1076 let s = std::str::from_utf8(data)
1077 .map_err(|_| Error::SchemaTypeMismatch {
1078 expected: "String",
1079 found: "non-utf8",
1080 path: path.to_owned(),
1081 })?
1082 .to_owned();
1083 Ok((Dynamic::String(s), after))
1084}
1085
1086fn decode_bytes_schema<'a>(bytes: &'a [u8], path: &str) -> Result<(Dynamic, &'a [u8])> {
1088 let (len, rest) = take_varint_usize(bytes)?;
1089 if rest.len() < len {
1090 return Err(Error::SchemaTypeMismatch {
1091 expected: "Bytes",
1092 found: "truncated",
1093 path: path.to_owned(),
1094 });
1095 }
1096 let (data, after) = rest.split_at(len);
1097 Ok((Dynamic::Bytes(data.to_vec()), after))
1098}
1099
1100fn decode_seq_slot<'a>(
1105 bytes: &'a [u8],
1106 elem: &DynamicSchema,
1107 stack: &mut Vec<Frame>,
1108) -> Result<SlotOutcome<'a>> {
1109 let (len, rest) = take_varint_usize(bytes)?;
1110 if len == 0 {
1111 return Ok(SlotOutcome {
1112 value: Some(Dynamic::Seq(Vec::new())),
1113 rest,
1114 });
1115 }
1116 if len > MAX_DYNAMIC_NODES {
1117 return Err(Error::SchemaTypeMismatch {
1118 expected: "Seq",
1119 found: "length-exceeds-bound",
1120 path: String::new(),
1121 });
1122 }
1123 stack.push(Frame::Seq {
1124 elem: elem.clone(),
1125 remaining: len,
1126 acc: Vec::with_capacity(len),
1127 });
1128 Ok(SlotOutcome { value: None, rest })
1129}
1130
1131fn decode_map_slot<'a>(
1136 bytes: &'a [u8],
1137 fields: &[(String, DynamicSchema)],
1138 path: &str,
1139 stack: &mut Vec<Frame>,
1140) -> SlotOutcome<'a> {
1141 if fields.is_empty() {
1142 return SlotOutcome {
1143 value: Some(Dynamic::Map(BTreeMap::new())),
1144 rest: bytes,
1145 };
1146 }
1147 let pending: std::collections::VecDeque<(String, DynamicSchema)> =
1148 fields.iter().cloned().collect();
1149 stack.push(Frame::Map {
1150 pending,
1151 current_name: None,
1152 acc: BTreeMap::new(),
1153 path_prefix: path.to_owned(),
1154 });
1155 SlotOutcome {
1156 value: None,
1157 rest: bytes,
1158 }
1159}
1160
1161fn decode_enum_slot<'a>(
1168 bytes: &'a [u8],
1169 variants: &[EnumVariantSchema],
1170 path: &str,
1171 stack: &mut Vec<Frame>,
1172) -> Result<SlotOutcome<'a>> {
1173 debug_assert!(
1174 variants
1175 .windows(2)
1176 .all(|w| w[0].discriminant < w[1].discriminant),
1177 "Enum schema variants must be strictly ascending by discriminant",
1178 );
1179 debug_assert!(
1180 !variants.is_empty(),
1181 "Enum schema must declare at least one variant",
1182 );
1183 let (disc_u64, rest) = take_varint_u64(bytes)?;
1184 let disc = u32::try_from(disc_u64).map_err(|_| Error::SchemaTypeMismatch {
1185 expected: "u32-discriminant",
1186 found: "varint-overflow",
1187 path: path.to_owned(),
1188 })?;
1189 let idx = variants
1190 .binary_search_by(|v| v.discriminant.cmp(&disc))
1191 .map_err(|_| Error::SchemaTypeMismatch {
1192 expected: "known variant",
1193 found: "unknown-discriminant",
1194 path: path.to_owned(),
1195 })?;
1196 let variant = &variants[idx];
1197 stack.push(Frame::Enum {
1198 variant: variant.name.clone(),
1199 pending_payload: Some(variant.payload.as_ref().clone()),
1200 acc: None,
1201 path_prefix: path.to_owned(),
1202 });
1203 Ok(SlotOutcome { value: None, rest })
1204}
1205
1206fn fold_value(mut value: Dynamic, stack: &mut Vec<Frame>) -> Option<Dynamic> {
1211 let mut iters: usize = 0;
1212 loop {
1213 iters = iters.saturating_add(1);
1214 debug_assert!(
1215 iters <= MAX_SCHEMA_DEPTH + 1,
1216 "fold cascade exceeded MAX_SCHEMA_DEPTH",
1217 );
1218 let Some(top) = stack.last_mut() else {
1219 return Some(value);
1220 };
1221 if !fold_into(top, &mut value) {
1222 return None;
1223 }
1224 let completed = match stack.pop() {
1227 Some(Frame::Seq { acc, .. }) => Dynamic::Seq(acc),
1228 Some(Frame::Map { acc, .. }) => Dynamic::Map(acc),
1229 Some(Frame::Enum {
1230 variant,
1231 acc: payload,
1232 ..
1233 }) => {
1234 debug_assert!(
1240 payload.is_some(),
1241 "Enum frame popped before fold_into populated acc",
1242 );
1243 Dynamic::Enum {
1244 variant,
1245 payload: Box::new(payload.unwrap_or(Dynamic::Null)),
1246 }
1247 }
1248 None => return Some(value),
1249 };
1250 value = completed;
1251 }
1252}
1253
1254fn fold_into(top: &mut Frame, value: &mut Dynamic) -> bool {
1258 match top {
1259 Frame::Seq { remaining, acc, .. } => {
1260 acc.push(std::mem::replace(value, Dynamic::Null));
1261 *remaining = remaining.saturating_sub(1);
1262 *remaining == 0
1263 }
1264 Frame::Map {
1265 pending,
1266 current_name,
1267 acc,
1268 ..
1269 } => {
1270 let name = current_name
1271 .take()
1272 .unwrap_or_else(|| String::from("<bug:missing-current-name>"));
1273 debug_assert!(
1274 !name.starts_with("<bug:"),
1275 "Map frame missing current_name in fold",
1276 );
1277 acc.insert(name, std::mem::replace(value, Dynamic::Null));
1278 pending.is_empty()
1279 }
1280 Frame::Enum { acc, .. } => {
1281 debug_assert!(acc.is_none(), "Enum frame folded twice");
1285 *acc = Some(std::mem::replace(value, Dynamic::Null));
1286 true
1287 }
1288 }
1289}
1290
1291fn request_next_child(stack: &mut [Frame]) -> Option<(DynamicSchema, String)> {
1296 let top = stack.last_mut()?;
1297 match top {
1298 Frame::Seq { elem, .. } => Some((elem.clone(), String::new())),
1299 Frame::Map {
1300 pending,
1301 current_name,
1302 path_prefix,
1303 ..
1304 } => {
1305 let (name, schema) = pending.pop_front()?;
1306 let path = if path_prefix.is_empty() {
1307 name.clone()
1308 } else {
1309 format!("{path_prefix}.{name}")
1310 };
1311 *current_name = Some(name);
1312 Some((schema, path))
1313 }
1314 Frame::Enum {
1315 variant,
1316 pending_payload,
1317 path_prefix,
1318 ..
1319 } => {
1320 let schema = pending_payload.take()?;
1321 let path = if path_prefix.is_empty() {
1322 variant.clone()
1323 } else {
1324 format!("{path_prefix}.{variant}")
1325 };
1326 Some((schema, path))
1327 }
1328 }
1329}
1330
1331fn variant_name(d: &Dynamic) -> &'static str {
1334 match d {
1335 Dynamic::Null => "Null",
1336 Dynamic::Bool(_) => "Bool",
1337 Dynamic::U64(_) => "U64",
1338 Dynamic::I64(_) => "I64",
1339 Dynamic::F64(_) => "F64",
1340 Dynamic::String(_) => "String",
1341 Dynamic::Bytes(_) => "Bytes",
1342 Dynamic::Seq(_) => "Seq",
1343 Dynamic::Map(_) => "Map",
1344 Dynamic::Enum { .. } => "Enum",
1345 }
1346}
1347
1348fn zigzag_decode_i64(zz: u64) -> i64 {
1351 let high = (zz >> 1).cast_signed();
1352 let low = (zz & 1).cast_signed();
1353 high ^ -low
1354}
1355
1356#[cfg(test)]
1365mod tests {
1366 use super::*;
1367 use serde::{Deserialize, Serialize};
1368
1369 #[test]
1370 fn round_trip_scalar() {
1371 for value in [
1372 Dynamic::Null,
1373 Dynamic::Bool(true),
1374 Dynamic::Bool(false),
1375 Dynamic::U64(0),
1376 Dynamic::U64(u64::MAX),
1377 Dynamic::I64(0),
1378 Dynamic::I64(-1),
1379 Dynamic::I64(i64::MIN),
1380 Dynamic::I64(i64::MAX),
1381 Dynamic::F64(1.5),
1382 Dynamic::String("hello".to_owned()),
1383 Dynamic::Bytes(vec![1, 2, 3]),
1384 ] {
1385 let bytes = value.to_postcard_bytes().expect("encode");
1386 let decoded = Dynamic::from_tagged_bytes(&bytes).expect("decode");
1387 assert_eq!(decoded, value);
1388 }
1389 }
1390
1391 #[test]
1392 fn round_trip_nested_map() {
1393 let mut inner = BTreeMap::new();
1394 inner.insert("a".to_owned(), Dynamic::U64(1));
1395 inner.insert("b".to_owned(), Dynamic::String("two".to_owned()));
1396 let mut outer = BTreeMap::new();
1397 outer.insert("inner".to_owned(), Dynamic::Map(inner));
1398 outer.insert(
1399 "list".to_owned(),
1400 Dynamic::Seq(vec![
1401 Dynamic::U64(10),
1402 Dynamic::U64(20),
1403 Dynamic::Bool(false),
1404 ]),
1405 );
1406 let value = Dynamic::Map(outer);
1407 let bytes = value.to_postcard_bytes().expect("encode");
1408 let decoded = Dynamic::from_tagged_bytes(&bytes).expect("decode");
1409 assert_eq!(decoded, value);
1410 }
1411
1412 #[test]
1413 fn unknown_tag_is_corruption() {
1414 let err = Dynamic::from_tagged_bytes(&[0xFF]).expect_err("unknown tag");
1415 assert!(matches!(err, Error::Corruption { page_id: 0 }));
1416 }
1417
1418 #[test]
1419 fn truncated_tagged_string_is_corruption() {
1420 let bytes = [TAG_STRING, 100, b'x'];
1422 let err = Dynamic::from_tagged_bytes(&bytes).expect_err("truncated");
1423 assert!(matches!(err, Error::Corruption { page_id: 0 }));
1424 }
1425
1426 #[test]
1427 fn get_and_set_on_map() {
1428 let mut m = BTreeMap::new();
1429 m.insert("a".to_owned(), Dynamic::U64(1));
1430 let mut value = Dynamic::Map(m);
1431 assert_eq!(value.get("a"), Some(&Dynamic::U64(1)));
1432 assert_eq!(value.get("missing"), None);
1433 value.set("b", Dynamic::Bool(true));
1434 assert_eq!(value.get("b"), Some(&Dynamic::Bool(true)));
1435 }
1436
1437 #[test]
1438 fn remove_from_map_returns_removed() {
1439 let mut m = BTreeMap::new();
1440 m.insert("a".to_owned(), Dynamic::U64(1));
1441 m.insert("b".to_owned(), Dynamic::String("two".to_owned()));
1442 let mut value = Dynamic::Map(m);
1443 let removed = value.remove("a").expect("ok");
1444 assert_eq!(removed, Some(Dynamic::U64(1)));
1445 assert_eq!(value.get("a"), None);
1446 assert_eq!(value.get("b"), Some(&Dynamic::String("two".to_owned())));
1447 let again = value.remove("a").expect("ok");
1450 assert_eq!(again, None);
1451 }
1452
1453 #[test]
1454 fn remove_on_non_map_errors() {
1455 let mut value = Dynamic::U64(5);
1456 let err = value.remove("k").expect_err("non-map");
1457 assert!(matches!(err, Error::DynamicPathNotMap { .. }));
1458 }
1459
1460 #[test]
1461 fn set_on_non_map_replaces() {
1462 let mut value = Dynamic::U64(5);
1463 value.set("k", Dynamic::String("v".to_owned()));
1464 match value {
1465 Dynamic::Map(m) => {
1466 assert_eq!(m.len(), 1);
1467 assert_eq!(m.get("k"), Some(&Dynamic::String("v".to_owned())));
1468 }
1469 other => panic!("expected Map, got {other:?}"),
1470 }
1471 }
1472
1473 #[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
1479 struct V1 {
1480 a: u32,
1481 }
1482
1483 #[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
1484 struct V2 {
1485 a: u32,
1486 b: String,
1487 }
1488
1489 #[test]
1490 fn dynamic_migration_shape() {
1491 let v1 = V1 { a: 42 };
1493 let v1_postcard = postcard::to_allocvec(&v1).expect("encode v1");
1494
1495 let recovered_v1: V1 = postcard::from_bytes(&v1_postcard).expect("decode v1");
1500 let mut dynamic = Dynamic::Map(BTreeMap::new());
1501 dynamic.set("a", Dynamic::U64(u64::from(recovered_v1.a)));
1502
1503 dynamic.set("b", Dynamic::String("default-b".to_owned()));
1505
1506 let a = match dynamic.get("a") {
1513 Some(Dynamic::U64(n)) => u32::try_from(*n).expect("u32 range"),
1514 other => panic!("missing or wrong-type a: {other:?}"),
1515 };
1516 let b = match dynamic.get("b") {
1517 Some(Dynamic::String(s)) => s.clone(),
1518 other => panic!("missing or wrong-type b: {other:?}"),
1519 };
1520 let v2 = V2 { a, b };
1521 assert_eq!(
1522 v2,
1523 V2 {
1524 a: 42,
1525 b: "default-b".to_owned(),
1526 }
1527 );
1528 }
1529
1530 #[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
1533 struct SchemaPair {
1534 name: String,
1535 age: u32,
1536 }
1537
1538 #[test]
1539 fn schema_walker_decodes_simple_struct() {
1540 let s = SchemaPair {
1541 name: "ada".to_owned(),
1542 age: 36,
1543 };
1544 let bytes = postcard::to_allocvec(&s).expect("encode");
1545 let schema =
1546 DynamicSchema::map([("name", DynamicSchema::String), ("age", DynamicSchema::U64)]);
1547 let dyn_view = Dynamic::from_postcard_bytes(&bytes, &schema).expect("walk");
1548 assert_eq!(
1549 dyn_view.get("name"),
1550 Some(&Dynamic::String("ada".to_owned())),
1551 );
1552 assert_eq!(dyn_view.get("age"), Some(&Dynamic::U64(36)));
1553 }
1554
1555 #[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
1556 struct SchemaInner {
1557 x: u32,
1558 y: u32,
1559 }
1560
1561 #[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
1562 struct SchemaOuter {
1563 tag: String,
1564 inner: SchemaInner,
1565 tail: bool,
1566 }
1567
1568 #[test]
1569 fn schema_walker_decodes_nested_struct() {
1570 let s = SchemaOuter {
1571 tag: "hello".to_owned(),
1572 inner: SchemaInner { x: 1, y: 2 },
1573 tail: true,
1574 };
1575 let bytes = postcard::to_allocvec(&s).expect("encode");
1576 let schema = DynamicSchema::map([
1577 ("tag", DynamicSchema::String),
1578 (
1579 "inner",
1580 DynamicSchema::map([("x", DynamicSchema::U64), ("y", DynamicSchema::U64)]),
1581 ),
1582 ("tail", DynamicSchema::Bool),
1583 ]);
1584 let dyn_view = Dynamic::from_postcard_bytes(&bytes, &schema).expect("walk");
1585 assert_eq!(
1586 dyn_view.get("tag"),
1587 Some(&Dynamic::String("hello".to_owned())),
1588 );
1589 let inner = dyn_view.get("inner").expect("inner present");
1590 assert_eq!(inner.get("x"), Some(&Dynamic::U64(1)));
1591 assert_eq!(inner.get("y"), Some(&Dynamic::U64(2)));
1592 assert_eq!(dyn_view.get("tail"), Some(&Dynamic::Bool(true)));
1593 }
1594
1595 #[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
1596 struct SchemaWithSeq {
1597 items: Vec<u32>,
1598 name: String,
1599 }
1600
1601 #[test]
1602 fn schema_walker_decodes_seq() {
1603 let s = SchemaWithSeq {
1604 items: vec![10, 20, 30],
1605 name: "vec".to_owned(),
1606 };
1607 let bytes = postcard::to_allocvec(&s).expect("encode");
1608 let schema = DynamicSchema::map([
1609 ("items", DynamicSchema::seq(DynamicSchema::U64)),
1610 ("name", DynamicSchema::String),
1611 ]);
1612 let dyn_view = Dynamic::from_postcard_bytes(&bytes, &schema).expect("walk");
1613 match dyn_view.get("items") {
1614 Some(Dynamic::Seq(items)) => {
1615 assert_eq!(items.len(), 3);
1616 assert_eq!(items[0], Dynamic::U64(10));
1617 assert_eq!(items[1], Dynamic::U64(20));
1618 assert_eq!(items[2], Dynamic::U64(30));
1619 }
1620 other => panic!("expected Seq, got {other:?}"),
1621 }
1622 assert_eq!(
1623 dyn_view.get("name"),
1624 Some(&Dynamic::String("vec".to_owned())),
1625 );
1626 }
1627
1628 #[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
1629 struct SchemaSigned {
1630 neg: i32,
1631 pos: i32,
1632 }
1633
1634 #[test]
1635 fn schema_walker_decodes_signed_ints() {
1636 let s = SchemaSigned { neg: -5, pos: 7 };
1637 let bytes = postcard::to_allocvec(&s).expect("encode");
1638 let schema = DynamicSchema::map([("neg", DynamicSchema::I64), ("pos", DynamicSchema::I64)]);
1639 let dyn_view = Dynamic::from_postcard_bytes(&bytes, &schema).expect("walk");
1640 assert_eq!(dyn_view.get("neg"), Some(&Dynamic::I64(-5)));
1641 assert_eq!(dyn_view.get("pos"), Some(&Dynamic::I64(7)));
1642 }
1643
1644 #[test]
1645 fn schema_walker_decodes_bytes_and_unit() {
1646 #[derive(Serialize, Deserialize)]
1647 struct B {
1648 blob: Vec<u8>,
1649 nothing: (),
1650 present: bool,
1651 }
1652 let v = B {
1653 blob: vec![1, 2, 3, 4],
1654 nothing: (),
1655 present: false,
1656 };
1657 let bytes = postcard::to_allocvec(&v).expect("encode");
1658 let schema = DynamicSchema::map([
1659 ("blob", DynamicSchema::Bytes),
1660 ("nothing", DynamicSchema::Null),
1661 ("present", DynamicSchema::Bool),
1662 ]);
1663 let dyn_view = Dynamic::from_postcard_bytes(&bytes, &schema).expect("walk");
1664 match dyn_view.get("blob") {
1665 Some(Dynamic::Bytes(b)) => assert_eq!(b, &vec![1, 2, 3, 4]),
1670 other => panic!("expected Bytes, got {other:?}"),
1671 }
1672 assert_eq!(dyn_view.get("nothing"), Some(&Dynamic::Null));
1673 assert_eq!(dyn_view.get("present"), Some(&Dynamic::Bool(false)));
1674 }
1675
1676 #[test]
1677 fn schema_walker_round_trip_to_native_deserialize() {
1678 let s = SchemaPair {
1683 name: "ada".to_owned(),
1684 age: 36,
1685 };
1686 let bytes = postcard::to_allocvec(&s).expect("encode");
1687 let direct: SchemaPair = postcard::from_bytes(&bytes).expect("postcard");
1688 let schema =
1689 DynamicSchema::map([("name", DynamicSchema::String), ("age", DynamicSchema::U64)]);
1690 let dyn_view = Dynamic::from_postcard_bytes(&bytes, &schema).expect("walk");
1691 assert_eq!(
1694 dyn_view.get("name"),
1695 Some(&Dynamic::String(direct.name.clone())),
1696 );
1697 assert_eq!(
1698 dyn_view.get("age"),
1699 Some(&Dynamic::U64(u64::from(direct.age)))
1700 );
1701 }
1702
1703 #[test]
1704 fn schema_walker_rejects_truncated_payload() {
1705 let s = SchemaPair {
1706 name: "ada".to_owned(),
1707 age: 36,
1708 };
1709 let mut bytes = postcard::to_allocvec(&s).expect("encode");
1710 bytes.truncate(2); let schema =
1712 DynamicSchema::map([("name", DynamicSchema::String), ("age", DynamicSchema::U64)]);
1713 let err = Dynamic::from_postcard_bytes(&bytes, &schema).expect_err("truncated");
1714 assert!(matches!(err, Error::SchemaTypeMismatch { .. }));
1715 }
1716
1717 #[test]
1718 fn schema_walker_rejects_excess_depth() {
1719 let mut s = DynamicSchema::U64;
1725 for _ in 0..(MAX_SCHEMA_DEPTH + 2) {
1726 s = DynamicSchema::seq(s);
1727 }
1728 let bytes = vec![1u8; MAX_SCHEMA_DEPTH + 16];
1730 let err = Dynamic::from_postcard_bytes(&bytes, &s).expect_err("depth");
1731 assert!(matches!(err, Error::SchemaDepthExceeded { .. }));
1732 }
1733
1734 #[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
1737 enum SchemaEnumProbe {
1738 Pending,
1739 Shipped { tracking: String },
1740 Cancelled(String),
1741 }
1742
1743 fn schema_enum_probe_schema() -> DynamicSchema {
1744 DynamicSchema::enumeration([
1745 EnumVariantSchema::new(0, "Pending", DynamicSchema::Null),
1746 EnumVariantSchema::new(
1747 1,
1748 "Shipped",
1749 DynamicSchema::map([("tracking", DynamicSchema::String)]),
1750 ),
1751 EnumVariantSchema::new(2, "Cancelled", DynamicSchema::String),
1752 ])
1753 }
1754
1755 #[test]
1756 fn schema_walker_decodes_unit_variant() {
1757 let bytes = postcard::to_allocvec(&SchemaEnumProbe::Pending).expect("encode");
1758 let schema = schema_enum_probe_schema();
1759 let dyn_view = Dynamic::from_postcard_bytes(&bytes, &schema).expect("walk");
1760 assert_eq!(dyn_view.enum_variant(), Some("Pending"));
1761 assert_eq!(dyn_view.enum_payload(), Some(&Dynamic::Null));
1762 }
1763
1764 #[test]
1765 fn schema_walker_decodes_struct_variant() {
1766 let v = SchemaEnumProbe::Shipped {
1767 tracking: "ABC".to_owned(),
1768 };
1769 let bytes = postcard::to_allocvec(&v).expect("encode");
1770 let dyn_view =
1771 Dynamic::from_postcard_bytes(&bytes, &schema_enum_probe_schema()).expect("walk");
1772 assert_eq!(dyn_view.enum_variant(), Some("Shipped"));
1773 let payload = dyn_view.enum_payload().expect("payload");
1774 assert_eq!(
1775 payload.get("tracking"),
1776 Some(&Dynamic::String("ABC".to_owned())),
1777 );
1778 }
1779
1780 #[test]
1781 fn schema_walker_decodes_newtype_variant() {
1782 let v = SchemaEnumProbe::Cancelled("late".to_owned());
1783 let bytes = postcard::to_allocvec(&v).expect("encode");
1784 let dyn_view =
1785 Dynamic::from_postcard_bytes(&bytes, &schema_enum_probe_schema()).expect("walk");
1786 assert_eq!(dyn_view.enum_variant(), Some("Cancelled"));
1787 assert_eq!(
1788 dyn_view.enum_payload(),
1789 Some(&Dynamic::String("late".to_owned())),
1790 );
1791 }
1792
1793 #[test]
1794 fn schema_walker_unknown_discriminant_errors() {
1795 let bytes = [99u8];
1798 let err =
1799 Dynamic::from_postcard_bytes(&bytes, &schema_enum_probe_schema()).expect_err("unknown");
1800 assert!(matches!(
1801 err,
1802 Error::SchemaTypeMismatch {
1803 expected: "known variant",
1804 ..
1805 }
1806 ));
1807 }
1808
1809 #[test]
1810 fn schema_walker_decodes_enum_inside_map() {
1811 #[derive(Serialize, Deserialize)]
1812 struct Wrap {
1813 label: String,
1814 status: SchemaEnumProbe,
1815 }
1816 let v = Wrap {
1817 label: "order".to_owned(),
1818 status: SchemaEnumProbe::Shipped {
1819 tracking: "XYZ".to_owned(),
1820 },
1821 };
1822 let bytes = postcard::to_allocvec(&v).expect("encode");
1823 let schema = DynamicSchema::map([
1824 ("label", DynamicSchema::String),
1825 ("status", schema_enum_probe_schema()),
1826 ]);
1827 let dyn_view = Dynamic::from_postcard_bytes(&bytes, &schema).expect("walk");
1828 assert_eq!(
1829 dyn_view.get("label"),
1830 Some(&Dynamic::String("order".to_owned())),
1831 );
1832 let status = dyn_view.get("status").expect("status");
1833 assert_eq!(status.enum_variant(), Some("Shipped"));
1834 let payload = status.enum_payload().expect("payload");
1835 assert_eq!(
1836 payload.get("tracking"),
1837 Some(&Dynamic::String("XYZ".to_owned())),
1838 );
1839 }
1840
1841 #[test]
1842 fn enum_tagged_round_trip() {
1843 let value = Dynamic::Enum {
1847 variant: "Shipped".to_owned(),
1848 payload: Box::new(Dynamic::Map({
1849 let mut m = BTreeMap::new();
1850 m.insert("tracking".to_owned(), Dynamic::String("ABC".to_owned()));
1851 m
1852 })),
1853 };
1854 let bytes = value.to_postcard_bytes().expect("encode");
1855 let back = Dynamic::from_tagged_bytes(&bytes).expect("decode");
1856 assert_eq!(back, value);
1857 }
1858
1859 #[test]
1860 fn dynamic_tagged_round_trip_matches_intent() {
1861 let value = Dynamic::Seq(vec![
1867 Dynamic::Null,
1868 Dynamic::Bool(true),
1869 Dynamic::String("nested".to_owned()),
1870 Dynamic::Map({
1871 let mut m = BTreeMap::new();
1872 m.insert("k".to_owned(), Dynamic::I64(-7));
1873 m
1874 }),
1875 ]);
1876 let bytes = value.to_postcard_bytes().expect("encode");
1877 let back = Dynamic::from_tagged_bytes(&bytes).expect("decode");
1878 assert_eq!(back, value);
1879 }
1880}