Skip to main content

tabnas_alchemy/shared/
selector.rs

1//! Selectors: reusable descriptions of where in a document to look.
2//!
3//! A selector is data, never code: it is built from constructors or from
4//! validated path segments (`as-path`), and a matcher interprets it. A
5//! concrete [`Path`] names one location; a [`Selector`] may name many
6//! (`EachIndex`, `EachMember`). Both print in jq syntax, which is what the
7//! rest of the fleet (aless included) prints and accepts.
8
9use std::fmt;
10
11/// One step of a concrete path.
12#[derive(Clone, Debug, PartialEq, Eq, Hash)]
13pub enum Segment {
14    Key(Box<str>),
15    Index(usize),
16}
17
18impl Segment {
19    pub fn key(k: impl Into<Box<str>>) -> Segment {
20        Segment::Key(k.into())
21    }
22}
23
24/// A concrete location in a document.
25#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
26pub struct Path(pub Vec<Segment>);
27
28impl Path {
29    pub fn root() -> Path {
30        Path(Vec::new())
31    }
32
33    pub fn push(&mut self, seg: Segment) {
34        self.0.push(seg);
35    }
36
37    pub fn pop(&mut self) -> Option<Segment> {
38        self.0.pop()
39    }
40
41    pub fn depth(&self) -> usize {
42        self.0.len()
43    }
44
45    pub fn segments(&self) -> &[Segment] {
46        &self.0
47    }
48}
49
50/// Write one key as jq does: bare when it is an identifier, quoted otherwise.
51pub fn write_key(f: &mut fmt::Formatter<'_>, key: &str) -> fmt::Result {
52    let bare = !key.is_empty()
53        && key
54            .chars()
55            .next()
56            .is_some_and(|c| c.is_ascii_alphabetic() || c == '_')
57        && key.chars().all(|c| c.is_ascii_alphanumeric() || c == '_');
58    if bare {
59        write!(f, ".{key}")
60    } else {
61        write!(f, ".{}", serde_json::Value::String(key.to_string()))
62    }
63}
64
65impl fmt::Display for Path {
66    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
67        if self.0.is_empty() {
68            return f.write_str(".");
69        }
70        for seg in &self.0 {
71            match seg {
72                Segment::Key(k) => write_key(f, k)?,
73                Segment::Index(i) => write!(f, "[{i}]")?,
74            }
75        }
76        Ok(())
77    }
78}
79
80/// One step of a selector.
81#[derive(Clone, Debug, PartialEq, Eq, Hash)]
82pub enum Step {
83    /// The member with this name, inside an object.
84    Property(Box<str>),
85    /// The element at this position, inside an array.
86    Index(usize),
87    /// Every element of an array.
88    EachIndex,
89    /// Every member value of an object.
90    EachMember,
91}
92
93/// A description of locations: the root, narrowed step by step.
94#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
95pub struct Selector(pub Vec<Step>);
96
97impl Selector {
98    /// The document itself.
99    pub fn root() -> Selector {
100        Selector(Vec::new())
101    }
102
103    pub fn property(mut self, name: impl Into<Box<str>>) -> Selector {
104        self.0.push(Step::Property(name.into()));
105        self
106    }
107
108    pub fn index(mut self, i: usize) -> Selector {
109        self.0.push(Step::Index(i));
110        self
111    }
112
113    pub fn each_index(mut self) -> Selector {
114        self.0.push(Step::EachIndex);
115        self
116    }
117
118    pub fn each_member(mut self) -> Selector {
119        self.0.push(Step::EachMember);
120        self
121    }
122
123    /// `self`, then `other` below every location `self` names.
124    pub fn compose(mut self, other: &Selector) -> Selector {
125        self.0.extend(other.0.iter().cloned());
126        self
127    }
128
129    /// A selector naming exactly one location: `as-path` over data.
130    pub fn from_segments(segments: &[Segment]) -> Selector {
131        Selector(
132            segments
133                .iter()
134                .map(|s| match s {
135                    Segment::Key(k) => Step::Property(k.clone()),
136                    Segment::Index(i) => Step::Index(*i),
137                })
138                .collect(),
139        )
140    }
141
142    pub fn steps(&self) -> &[Step] {
143        &self.0
144    }
145
146    pub fn is_root(&self) -> bool {
147        self.0.is_empty()
148    }
149
150    /// Whether the selector can name more than one location.
151    pub fn is_multi(&self) -> bool {
152        self.0
153            .iter()
154            .any(|s| matches!(s, Step::EachIndex | Step::EachMember))
155    }
156
157    /// Whether this selector names a location strictly inside a location
158    /// `other` names, or the same one: the test a router uses to refuse
159    /// overlapping captures.
160    pub fn may_overlap(&self, other: &Selector) -> bool {
161        let (short, long) = if self.0.len() <= other.0.len() {
162            (self, other)
163        } else {
164            (other, self)
165        };
166        short
167            .0
168            .iter()
169            .zip(long.0.iter())
170            .all(|(a, b)| step_may_match_same(a, b))
171    }
172}
173
174fn step_may_match_same(a: &Step, b: &Step) -> bool {
175    match (a, b) {
176        (Step::Property(x), Step::Property(y)) => x == y,
177        (Step::Property(_), Step::EachMember) | (Step::EachMember, Step::Property(_)) => true,
178        (Step::EachMember, Step::EachMember) => true,
179        (Step::Index(x), Step::Index(y)) => x == y,
180        (Step::Index(_), Step::EachIndex) | (Step::EachIndex, Step::Index(_)) => true,
181        (Step::EachIndex, Step::EachIndex) => true,
182        _ => false,
183    }
184}
185
186impl fmt::Display for Selector {
187    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
188        if self.0.is_empty() {
189            return f.write_str(".");
190        }
191        for step in &self.0 {
192            match step {
193                Step::Property(k) => write_key(f, k)?,
194                Step::Index(i) => write!(f, "[{i}]")?,
195                Step::EachIndex => f.write_str("[*]")?,
196                Step::EachMember => f.write_str("[]")?,
197            }
198        }
199        Ok(())
200    }
201}
202
203#[cfg(test)]
204mod tests {
205    use super::*;
206
207    #[test]
208    fn display_is_jq() {
209        let s = Selector::root()
210            .property("response")
211            .property("odd key")
212            .index(3)
213            .each_index()
214            .each_member();
215        assert_eq!(s.to_string(), ".response.\"odd key\"[3][*][]");
216        assert_eq!(Selector::root().to_string(), ".");
217        let p = Path(vec![
218            Segment::key("a"),
219            Segment::Index(0),
220            Segment::key("b-c"),
221        ]);
222        assert_eq!(p.to_string(), ".a[0].\"b-c\"");
223        assert_eq!(Path::root().to_string(), ".");
224    }
225
226    #[test]
227    fn from_segments_is_single() {
228        let s = Selector::from_segments(&[Segment::key("account"), Segment::key("balance")]);
229        assert_eq!(s.to_string(), ".account.balance");
230        assert!(!s.is_multi());
231        assert!(Selector::root().each_index().is_multi());
232    }
233
234    #[test]
235    fn overlap() {
236        let rows = Selector::root().property("records").each_index();
237        let meta = Selector::root().property("metadata");
238        let inner = Selector::root().property("records").index(2).property("x");
239        assert!(!rows.may_overlap(&meta));
240        assert!(rows.may_overlap(&inner));
241        assert!(inner.may_overlap(&rows));
242        assert!(rows.may_overlap(&rows));
243        assert!(Selector::root().may_overlap(&meta));
244        assert!(Selector::root().each_member().may_overlap(&meta));
245        assert!(!Selector::root().each_index().may_overlap(&meta));
246    }
247
248    #[test]
249    fn compose_appends() {
250        let a = Selector::root().property("a");
251        let b = Selector::root().each_index();
252        assert_eq!(a.compose(&b).to_string(), ".a[*]");
253    }
254}