use std::collections::BTreeMap;
#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
pub enum Value {
Null,
Boolean(bool),
Integer(i64),
String(String),
Bytes(Vec<u8>),
Array(Vec<Self>),
Object(BTreeMap<String, Self>),
}
#[derive(Clone, Debug, Default, Eq, Ord, PartialEq, PartialOrd)]
pub struct FieldPath {
segments: Vec<String>,
}
impl FieldPath {
pub fn new<I, S>(segments: I) -> Self
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
Self {
segments: segments.into_iter().map(Into::into).collect(),
}
}
pub fn field(name: impl Into<String>) -> Self {
Self::new([name.into()])
}
pub fn segments(&self) -> &[String] {
&self.segments
}
pub fn resolve<'value>(&self, root: &'value Value) -> Option<&'value Value> {
let mut current = root;
for segment in &self.segments {
let Value::Object(object) = current else {
return None;
};
current = object.get(segment)?;
}
Some(current)
}
}
#[cfg(test)]
mod tests {
use std::collections::BTreeMap;
use super::{FieldPath, Value};
#[test]
fn field_path_distinguishes_missing_from_explicit_null() {
let root = Value::Object(BTreeMap::from([
(
"nested".to_owned(),
Value::Object(BTreeMap::from([("value".to_owned(), Value::Null)])),
),
("scalar".to_owned(), Value::Integer(7)),
]));
assert_eq!(
FieldPath::new(["nested", "value"]).resolve(&root),
Some(&Value::Null)
);
assert_eq!(FieldPath::new(["nested", "missing"]).resolve(&root), None);
assert_eq!(FieldPath::new(["scalar", "child"]).resolve(&root), None);
assert_eq!(FieldPath::default().resolve(&root), Some(&root));
}
#[test]
fn variant_order_is_the_normative_total_order() {
let values = [
Value::Null,
Value::Boolean(false),
Value::Integer(-1),
Value::String(String::new()),
Value::Bytes(Vec::new()),
Value::Array(Vec::new()),
Value::Object(BTreeMap::new()),
];
assert!(values.windows(2).all(|pair| pair[0] < pair[1]));
}
}