1use std::collections::BTreeMap;
4
5#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
10pub enum Value {
11 Null,
13 Boolean(bool),
15 Integer(i64),
17 String(String),
19 Bytes(Vec<u8>),
21 Array(Vec<Self>),
23 Object(BTreeMap<String, Self>),
25}
26
27#[derive(Clone, Debug, Default, Eq, Ord, PartialEq, PartialOrd)]
29pub struct FieldPath {
30 segments: Vec<String>,
31}
32
33impl FieldPath {
34 pub fn new<I, S>(segments: I) -> Self
36 where
37 I: IntoIterator<Item = S>,
38 S: Into<String>,
39 {
40 Self {
41 segments: segments.into_iter().map(Into::into).collect(),
42 }
43 }
44
45 pub fn field(name: impl Into<String>) -> Self {
47 Self::new([name.into()])
48 }
49
50 pub fn segments(&self) -> &[String] {
52 &self.segments
53 }
54
55 pub fn resolve<'value>(&self, root: &'value Value) -> Option<&'value Value> {
58 let mut current = root;
59 for segment in &self.segments {
60 let Value::Object(object) = current else {
61 return None;
62 };
63 current = object.get(segment)?;
64 }
65 Some(current)
66 }
67}
68
69#[cfg(test)]
70mod tests {
71 use std::collections::BTreeMap;
72
73 use super::{FieldPath, Value};
74
75 #[test]
76 fn field_path_distinguishes_missing_from_explicit_null() {
77 let root = Value::Object(BTreeMap::from([
78 (
79 "nested".to_owned(),
80 Value::Object(BTreeMap::from([("value".to_owned(), Value::Null)])),
81 ),
82 ("scalar".to_owned(), Value::Integer(7)),
83 ]));
84
85 assert_eq!(
86 FieldPath::new(["nested", "value"]).resolve(&root),
87 Some(&Value::Null)
88 );
89 assert_eq!(FieldPath::new(["nested", "missing"]).resolve(&root), None);
90 assert_eq!(FieldPath::new(["scalar", "child"]).resolve(&root), None);
91 assert_eq!(FieldPath::default().resolve(&root), Some(&root));
92 }
93
94 #[test]
95 fn variant_order_is_the_normative_total_order() {
96 let values = [
97 Value::Null,
98 Value::Boolean(false),
99 Value::Integer(-1),
100 Value::String(String::new()),
101 Value::Bytes(Vec::new()),
102 Value::Array(Vec::new()),
103 Value::Object(BTreeMap::new()),
104 ];
105 assert!(values.windows(2).all(|pair| pair[0] < pair[1]));
106 }
107}