1#[derive(Clone, Debug, PartialEq, Eq, Hash)]
12pub enum Seg {
13 Key(String),
14 Index(usize),
15}
16
17#[derive(Debug, Clone, PartialEq, Eq, Hash)]
23pub struct PathPat(pub Vec<SegPat>);
24
25#[derive(Debug, Clone, PartialEq, Eq, Hash)]
27pub enum SegPat {
28 Key(String),
30 AnyKey,
32 Index(usize),
34 EachItem,
36 AnyDepth,
40}
41
42impl PathPat {
43 pub fn key(name: impl Into<String>) -> Self {
45 PathPat(vec![SegPat::Key(name.into())])
46 }
47
48 pub fn each_item_of(name: impl Into<String>) -> Self {
51 PathPat(vec![SegPat::Key(name.into()), SegPat::EachItem])
52 }
53
54 pub fn subtree_of(name: impl Into<String>) -> Self {
57 PathPat(vec![SegPat::Key(name.into()), SegPat::AnyDepth])
58 }
59
60 pub fn matches(&self, path: &[Seg]) -> bool {
64 matches_from(&self.0, path)
65 }
66}
67
68fn matches_from(pats: &[SegPat], path: &[Seg]) -> bool {
72 let Some((pat, rest)) = pats.split_first() else {
73 return path.is_empty();
74 };
75 if let SegPat::AnyDepth = pat {
76 return (0..=path.len()).any(|taken| matches_from(rest, &path[taken..]));
78 }
79 match path.split_first() {
80 Some((seg, tail)) if seg_matches(pat, seg) => matches_from(rest, tail),
81 _ => false,
82 }
83}
84
85fn seg_matches(pat: &SegPat, seg: &Seg) -> bool {
87 match (pat, seg) {
88 (SegPat::Key(k), Seg::Key(s)) => k == s,
89 (SegPat::AnyKey, Seg::Key(_)) => true,
90 (SegPat::Index(i), Seg::Index(j)) => i == j,
91 (SegPat::EachItem, Seg::Index(_)) => true,
92 (SegPat::AnyDepth, _) => true,
94 _ => false,
95 }
96}
97
98#[cfg(test)]
99mod tests {
100 use super::*;
101
102 fn key(k: &str) -> Seg {
103 Seg::Key(k.into())
104 }
105
106 #[test]
107 fn path_pattern_matches_keys_and_each_item() {
108 let pat = PathPat::each_item_of("audience");
109 assert!(pat.matches(&[key("audience"), Seg::Index(0)]));
110 assert!(pat.matches(&[key("audience"), Seg::Index(3)]));
111 assert!(!pat.matches(&[key("audience")]));
112 assert!(!pat.matches(&[key("tags"), Seg::Index(0)]));
113 }
114
115 #[test]
116 fn subtree_matches_the_key_itself_and_everything_under_it() {
117 let pat = PathPat::subtree_of("meta");
118 assert!(pat.matches(&[key("meta")]));
119 assert!(pat.matches(&[key("meta"), key("author")]));
120 assert!(pat.matches(&[key("meta"), key("tags"), Seg::Index(2)]));
121 assert!(!pat.matches(&[key("other")]));
122 assert!(!pat.matches(&[]));
123 }
124
125 #[test]
126 fn any_depth_matches_a_key_at_an_unknown_depth() {
127 let pat = PathPat(vec![SegPat::AnyDepth, SegPat::Key("title".into())]);
129 assert!(pat.matches(&[key("title")]));
130 assert!(pat.matches(&[key("meta"), key("title")]));
131 assert!(pat.matches(&[key("a"), Seg::Index(0), key("title")]));
132 assert!(!pat.matches(&[key("title"), key("sub")]));
133 }
134
135 #[test]
136 fn any_depth_between_two_fixed_segments() {
137 let pat = PathPat(vec![
138 SegPat::Key("a".into()),
139 SegPat::AnyDepth,
140 SegPat::Key("z".into()),
141 ]);
142 assert!(pat.matches(&[key("a"), key("z")]));
143 assert!(pat.matches(&[key("a"), key("m"), key("z")]));
144 assert!(pat.matches(&[key("a"), key("m"), Seg::Index(1), key("z")]));
145 assert!(!pat.matches(&[key("a"), key("m")]));
146 }
147
148 #[test]
149 fn a_pattern_without_any_depth_still_requires_an_exact_length() {
150 let pat = PathPat::key("meta");
151 assert!(pat.matches(&[key("meta")]));
152 assert!(!pat.matches(&[key("meta"), key("author")]));
153 }
154
155 #[test]
156 fn any_key_does_not_match_an_index() {
157 let pat = PathPat(vec![SegPat::AnyKey]);
158 assert!(pat.matches(&[key("whatever")]));
159 assert!(!pat.matches(&[Seg::Index(0)]));
160 }
161}