Skip to main content

vitaminc_aead/plaintext/
path.rs

1//! Where a node sits in a value: the steps from the root to it.
2
3use vitaminc_protected::OpaqueDebug;
4
5/// One step from a container to one of its children.
6///
7/// A map key can be user data, so `Debug` masks it; an index is structure
8/// and is shown. Read a key by matching on the step.
9///
10/// Steps compare by value, keys included. A key is not secret from the
11/// ciphertext, which carries every map key in the clear; only logs are
12/// kept free of it.
13#[derive(Clone, PartialEq, Eq, OpaqueDebug)]
14pub enum Step {
15    /// The element at this index of a sequence.
16    Index(#[non_sensitive] usize),
17    /// The entry under this key of a map.
18    Key(String),
19}
20
21/// The steps from the root of a value to one node in it, root first. A
22/// node at the root has no steps.
23///
24/// Refusals carry one to say where the value was refused, and their
25/// `Display` never shows it, because map keys can be user data. `Debug`
26/// shows its shape, with every key masked (see [`Step`]). Read the steps
27/// with [`steps`](Path::steps). Paths compare step by step.
28#[derive(Clone, Default, PartialEq, Eq, OpaqueDebug)]
29pub struct Path(#[non_sensitive] Vec<Step>);
30
31impl Path {
32    /// A path of these steps, root first.
33    pub fn new(steps: Vec<Step>) -> Self {
34        Self(steps)
35    }
36
37    /// The steps, root first.
38    pub fn steps(&self) -> &[Step] {
39        &self.0
40    }
41}
42
43/// A path written out for a test's assertion: `.key` for an entry and
44/// `[i]` for an element, so `.m[0].k` is the entry `k` of the first
45/// element of the entry `m`.
46///
47/// Not an API: it shows every key, which is what a test wants and a log
48/// must not have. This crate's tests and, through the `test-support`
49/// feature, its sibling crates' tests share this one copy.
50#[cfg(any(test, feature = "test-support"))]
51#[doc(hidden)]
52pub fn render(path: &Path) -> String {
53    path.steps()
54        .iter()
55        .map(|step| match step {
56            Step::Index(i) => format!("[{i}]"),
57            Step::Key(k) => format!(".{k}"),
58        })
59        .collect()
60}
61
62#[cfg(test)]
63mod tests {
64    use super::*;
65
66    #[test]
67    fn render_writes_keys_and_indexes() {
68        let path = Path::new(vec![
69            Step::Key("m".into()),
70            Step::Index(0),
71            Step::Key("k".into()),
72        ]);
73        assert_eq!(render(&path), ".m[0].k");
74        assert_eq!(render(&Path::default()), "");
75    }
76
77    #[test]
78    fn steps_are_kept_root_first() {
79        let path = Path::new(vec![Step::Key("a".into()), Step::Index(2)]);
80        assert_eq!(path.steps(), [Step::Key("a".into()), Step::Index(2)]);
81        assert!(Path::default().steps().is_empty());
82    }
83
84    #[test]
85    fn paths_compare_step_by_step() {
86        let path = |steps: Vec<Step>| Path::new(steps);
87        let a = path(vec![Step::Key("a".into()), Step::Index(2)]);
88        assert_eq!(a, a.clone());
89        assert_ne!(a, path(vec![Step::Key("b".into()), Step::Index(2)]));
90        assert_ne!(a, path(vec![Step::Key("a".into()), Step::Index(3)]));
91        assert_ne!(a, path(vec![Step::Key("a".into())]));
92        assert_ne!(Step::Index(0), Step::Key("0".into()));
93        assert_eq!(Path::default(), path(Vec::new()));
94    }
95
96    #[test]
97    fn debug_shows_indexes_and_masks_keys() {
98        let path = Path::new(vec![Step::Key("ada@example.com".into()), Step::Index(2)]);
99        let shown = format!("{path:?}");
100        assert!(!shown.contains("ada"), "{shown}");
101        assert!(shown.contains("Key(\"***\")"), "{shown}");
102        assert!(shown.contains("Index(2)"), "{shown}");
103    }
104}