#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum Seg {
Key(String),
Index(usize),
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct PathPat(pub Vec<SegPat>);
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum SegPat {
Key(String),
AnyKey,
Index(usize),
EachItem,
AnyDepth,
}
impl PathPat {
pub fn key(name: impl Into<String>) -> Self {
PathPat(vec![SegPat::Key(name.into())])
}
pub fn each_item_of(name: impl Into<String>) -> Self {
PathPat(vec![SegPat::Key(name.into()), SegPat::EachItem])
}
pub fn subtree_of(name: impl Into<String>) -> Self {
PathPat(vec![SegPat::Key(name.into()), SegPat::AnyDepth])
}
pub fn matches(&self, path: &[Seg]) -> bool {
matches_from(&self.0, path)
}
}
fn matches_from(pats: &[SegPat], path: &[Seg]) -> bool {
let Some((pat, rest)) = pats.split_first() else {
return path.is_empty();
};
if let SegPat::AnyDepth = pat {
return (0..=path.len()).any(|taken| matches_from(rest, &path[taken..]));
}
match path.split_first() {
Some((seg, tail)) if seg_matches(pat, seg) => matches_from(rest, tail),
_ => false,
}
}
fn seg_matches(pat: &SegPat, seg: &Seg) -> bool {
match (pat, seg) {
(SegPat::Key(k), Seg::Key(s)) => k == s,
(SegPat::AnyKey, Seg::Key(_)) => true,
(SegPat::Index(i), Seg::Index(j)) => i == j,
(SegPat::EachItem, Seg::Index(_)) => true,
(SegPat::AnyDepth, _) => true,
_ => false,
}
}
#[cfg(test)]
mod tests {
use super::*;
fn key(k: &str) -> Seg {
Seg::Key(k.into())
}
#[test]
fn path_pattern_matches_keys_and_each_item() {
let pat = PathPat::each_item_of("audience");
assert!(pat.matches(&[key("audience"), Seg::Index(0)]));
assert!(pat.matches(&[key("audience"), Seg::Index(3)]));
assert!(!pat.matches(&[key("audience")]));
assert!(!pat.matches(&[key("tags"), Seg::Index(0)]));
}
#[test]
fn subtree_matches_the_key_itself_and_everything_under_it() {
let pat = PathPat::subtree_of("meta");
assert!(pat.matches(&[key("meta")]));
assert!(pat.matches(&[key("meta"), key("author")]));
assert!(pat.matches(&[key("meta"), key("tags"), Seg::Index(2)]));
assert!(!pat.matches(&[key("other")]));
assert!(!pat.matches(&[]));
}
#[test]
fn any_depth_matches_a_key_at_an_unknown_depth() {
let pat = PathPat(vec![SegPat::AnyDepth, SegPat::Key("title".into())]);
assert!(pat.matches(&[key("title")]));
assert!(pat.matches(&[key("meta"), key("title")]));
assert!(pat.matches(&[key("a"), Seg::Index(0), key("title")]));
assert!(!pat.matches(&[key("title"), key("sub")]));
}
#[test]
fn any_depth_between_two_fixed_segments() {
let pat = PathPat(vec![
SegPat::Key("a".into()),
SegPat::AnyDepth,
SegPat::Key("z".into()),
]);
assert!(pat.matches(&[key("a"), key("z")]));
assert!(pat.matches(&[key("a"), key("m"), key("z")]));
assert!(pat.matches(&[key("a"), key("m"), Seg::Index(1), key("z")]));
assert!(!pat.matches(&[key("a"), key("m")]));
}
#[test]
fn a_pattern_without_any_depth_still_requires_an_exact_length() {
let pat = PathPat::key("meta");
assert!(pat.matches(&[key("meta")]));
assert!(!pat.matches(&[key("meta"), key("author")]));
}
#[test]
fn any_key_does_not_match_an_index() {
let pat = PathPat(vec![SegPat::AnyKey]);
assert!(pat.matches(&[key("whatever")]));
assert!(!pat.matches(&[Seg::Index(0)]));
}
}