#[derive(Clone, PartialEq, Eq, Hash)]
pub struct Aad(String);
impl Aad {
#[must_use]
pub fn as_bytes(&self) -> &[u8] {
self.0.as_bytes()
}
pub(crate) fn field(literal: &str) -> Self {
Self(literal.to_string())
}
}
impl std::fmt::Debug for Aad {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Aad({:?})", self.0)
}
}
#[derive(Debug, Default, Clone, PartialEq, Eq)]
pub struct AadPath {
components: Vec<String>,
}
impl AadPath {
#[must_use]
pub fn root() -> Self {
Self::default()
}
pub fn push_key(&mut self, key: impl Into<String>) {
self.components.push(key.into());
}
pub fn pop(&mut self) {
self.components.pop();
}
pub fn within<T>(&mut self, key: impl Into<String>, f: impl FnOnce(&mut Self) -> T) -> T {
self.push_key(key);
let out = f(self);
self.pop();
out
}
#[must_use]
pub fn depth(&self) -> usize {
self.components.len()
}
#[must_use]
pub fn components(&self) -> &[String] {
&self.components
}
#[must_use]
pub fn aad(&self) -> Aad {
let mut s = self.components.join(":");
s.push(':');
Aad(s)
}
#[must_use]
pub fn has_ambiguous_component(&self) -> bool {
self.components.iter().any(|c| c.contains(':'))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn trailing_colon_is_part_of_the_aad() {
let mut p = AadPath::root();
p.push_key("a");
p.push_key("b");
assert_eq!(p.aad().as_bytes(), b"a:b:");
}
#[test]
fn root_aad_is_a_bare_colon() {
assert_eq!(AadPath::root().aad().as_bytes(), b":");
}
#[test]
fn within_restores_the_path() {
let mut p = AadPath::root();
p.push_key("outer");
let inner = p.within("inner", |p| p.aad());
assert_eq!(inner.as_bytes(), b"outer:inner:");
assert_eq!(p.aad().as_bytes(), b"outer:");
}
#[test]
fn sequence_descent_adds_nothing() {
let mut p = AadPath::root();
p.push_key("age");
let first = p.aad();
let second = p.aad();
assert_eq!(first.as_bytes(), second.as_bytes());
assert_eq!(first.as_bytes(), b"age:");
}
#[test]
fn ambiguity_is_reported_not_escaped() {
let mut p = AadPath::root();
p.push_key("a:b");
p.push_key("c");
assert!(p.has_ambiguous_component());
assert_eq!(p.aad().as_bytes(), b"a:b:c:");
let mut q = AadPath::root();
q.push_key("a");
q.push_key("b:c");
assert_eq!(
q.aad().as_bytes(),
p.aad().as_bytes(),
"the upstream collision, reproduced"
);
}
#[test]
fn debug_shows_the_trailing_colon() {
let mut p = AadPath::root();
p.push_key("k");
assert_eq!(format!("{:?}", p.aad()), r#"Aad("k:")"#);
}
}