use vitaminc_protected::OpaqueDebug;
#[derive(Clone, PartialEq, Eq, OpaqueDebug)]
pub enum Step {
Index(#[non_sensitive] usize),
Key(String),
}
#[derive(Clone, Default, PartialEq, Eq, OpaqueDebug)]
pub struct Path(#[non_sensitive] Vec<Step>);
impl Path {
pub fn new(steps: Vec<Step>) -> Self {
Self(steps)
}
pub fn steps(&self) -> &[Step] {
&self.0
}
}
#[cfg(any(test, feature = "test-support"))]
#[doc(hidden)]
pub fn render(path: &Path) -> String {
path.steps()
.iter()
.map(|step| match step {
Step::Index(i) => format!("[{i}]"),
Step::Key(k) => format!(".{k}"),
})
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn render_writes_keys_and_indexes() {
let path = Path::new(vec![
Step::Key("m".into()),
Step::Index(0),
Step::Key("k".into()),
]);
assert_eq!(render(&path), ".m[0].k");
assert_eq!(render(&Path::default()), "");
}
#[test]
fn steps_are_kept_root_first() {
let path = Path::new(vec![Step::Key("a".into()), Step::Index(2)]);
assert_eq!(path.steps(), [Step::Key("a".into()), Step::Index(2)]);
assert!(Path::default().steps().is_empty());
}
#[test]
fn paths_compare_step_by_step() {
let path = |steps: Vec<Step>| Path::new(steps);
let a = path(vec![Step::Key("a".into()), Step::Index(2)]);
assert_eq!(a, a.clone());
assert_ne!(a, path(vec![Step::Key("b".into()), Step::Index(2)]));
assert_ne!(a, path(vec![Step::Key("a".into()), Step::Index(3)]));
assert_ne!(a, path(vec![Step::Key("a".into())]));
assert_ne!(Step::Index(0), Step::Key("0".into()));
assert_eq!(Path::default(), path(Vec::new()));
}
#[test]
fn debug_shows_indexes_and_masks_keys() {
let path = Path::new(vec![Step::Key("ada@example.com".into()), Step::Index(2)]);
let shown = format!("{path:?}");
assert!(!shown.contains("ada"), "{shown}");
assert!(shown.contains("Key(\"***\")"), "{shown}");
assert!(shown.contains("Index(2)"), "{shown}");
}
}