interface/directory/
expression.rs

1use serde::{Deserialize, Serialize};
2
3use nom::branch::alt;
4use nom::bytes::complete::tag;
5use nom::character::complete::char;
6use nom::combinator::map;
7use nom::sequence::{preceded, separated_pair};
8use nom::IResult;
9
10use crate::FieldValue;
11use rapidquery::parse::util::{identifier, integer, string};
12
13#[derive(Clone, Debug, Deserialize, Hash, Serialize, PartialEq, Eq)]
14#[serde(untagged)]
15pub enum ExpressionField {
16    Tag { tag: String },
17    Field { key: String, value: FieldValue },
18    HasField { key: String },
19}
20
21impl ExpressionField {
22    fn tag_node(i: &str) -> IResult<&str, Self> {
23        map(alt((identifier, string)), |tag| ExpressionField::Tag {
24            tag,
25        })(i)
26    }
27
28    fn string_field_value(i: &str) -> IResult<&str, FieldValue> {
29        map(alt((identifier, string)), FieldValue::Str)(i)
30    }
31
32    fn int_field_value(i: &str) -> IResult<&str, FieldValue> {
33        map(integer, FieldValue::Numeric)(i)
34    }
35
36    fn field_value(i: &str) -> IResult<&str, FieldValue> {
37        alt((Self::int_field_value, Self::string_field_value))(i)
38    }
39
40    fn key_value_node(i: &str) -> IResult<&str, Self> {
41        map(
42            separated_pair(identifier, tag("="), Self::field_value),
43            |(key, value)| ExpressionField::Field { key, value },
44        )(i)
45    }
46
47    fn hasfield_node(i: &str) -> IResult<&str, Self> {
48        map(preceded(char('@'), identifier), |key| {
49            ExpressionField::HasField { key }
50        })(i)
51    }
52}
53
54impl rapidquery::Parse for ExpressionField {
55    fn parse(i: &str) -> IResult<&str, Self> {
56        alt((Self::hasfield_node, Self::key_value_node, Self::tag_node))(i)
57    }
58}
59
60#[cfg(test)]
61mod test {
62    use std::{collections::HashMap, convert::Infallible};
63
64    use rapidquery::Expression;
65
66    use super::*;
67
68    #[test]
69    fn parse_basic_tag() {
70        let e = Expression::parse(" bing  ").unwrap();
71        assert_eq!(
72            e,
73            Expression::Field(ExpressionField::Tag { tag: "bing".into() })
74        );
75    }
76
77    #[test]
78    fn parse_basic_hasfield() {
79        let e = Expression::parse(" @type").unwrap();
80        assert_eq!(
81            e,
82            Expression::Field(ExpressionField::HasField { key: "type".into() })
83        )
84    }
85
86    #[test]
87    fn parse_underscore_tag() {
88        let e = Expression::parse(" bing_bong ").unwrap();
89        assert_eq!(
90            e,
91            Expression::Field(ExpressionField::Tag {
92                tag: "bing_bong".into()
93            })
94        );
95    }
96
97    #[test]
98    fn parse_multiword_tag() {
99        let e = Expression::parse(" \"hello world\"   ").unwrap();
100        assert_eq!(
101            e,
102            Expression::Field(ExpressionField::Tag {
103                tag: "hello world".into()
104            })
105        );
106    }
107
108    #[test]
109    fn parse_basic_kv() {
110        let e = Expression::parse("bing=bong").unwrap();
111        assert_eq!(
112            e,
113            Expression::Field(ExpressionField::Field {
114                key: "bing".into(),
115                value: "bong".into(),
116            })
117        );
118    }
119
120    #[test]
121    fn parse_int_kv() {
122        let e = Expression::parse("bing=42").unwrap();
123        assert_eq!(
124            e,
125            Expression::Field(ExpressionField::Field {
126                key: "bing".into(),
127                value: FieldValue::Numeric(42)
128            })
129        )
130    }
131
132    #[test]
133    fn parse_multiword_kv() {
134        let e = Expression::parse("bing = \"bada boom\"").unwrap();
135        assert_eq!(
136            e,
137            Expression::Field(ExpressionField::Field {
138                key: "bing".into(),
139                value: "bada boom".into(),
140            })
141        );
142    }
143
144    #[test]
145    fn parse_basic_and() {
146        let e = Expression::parse("bing && type=image").unwrap();
147        assert_eq!(
148            e,
149            Expression::And {
150                and: (
151                    Box::new(Expression::Field(ExpressionField::Tag {
152                        tag: "bing".into()
153                    })),
154                    Box::new(Expression::Field(ExpressionField::Field {
155                        key: "type".into(),
156                        value: "image".into(),
157                    }))
158                )
159            }
160        );
161    }
162
163    #[test]
164    fn chained_and() {
165        let e = Expression::parse("hello && there && world").unwrap();
166
167        let expected_expr = Expression::And {
168            and: (
169                Box::from(Expression::And {
170                    and: (
171                        Box::from(Expression::Field(ExpressionField::Tag {
172                            tag: "hello".into(),
173                        })),
174                        Box::from(Expression::Field(ExpressionField::Tag {
175                            tag: "there".into(),
176                        })),
177                    ),
178                }),
179                Box::from(Expression::Field(ExpressionField::Tag {
180                    tag: "world".into(),
181                })),
182            ),
183        };
184        assert_eq!(e, expected_expr);
185    }
186
187    #[test]
188    fn parse_basic_or() {
189        let e = Expression::parse("type=image || bing").unwrap();
190        assert_eq!(
191            e,
192            Expression::Or {
193                or: (
194                    Box::from(Expression::Field(ExpressionField::Field {
195                        key: "type".into(),
196                        value: "image".into(),
197                    })),
198                    Box::from(Expression::Field(ExpressionField::Tag {
199                        tag: "bing".into()
200                    }))
201                )
202            }
203        )
204    }
205
206    #[test]
207    fn parse_not_after_or() {
208        let e = Expression::parse("to_b || !to_b").unwrap();
209        assert_eq!(
210            e,
211            Expression::Or {
212                or: (
213                    Box::from(Expression::Field(ExpressionField::Tag {
214                        tag: "to_b".into()
215                    })),
216                    Box::from(Expression::Not {
217                        not: Box::from(Expression::Field(ExpressionField::Tag {
218                            tag: "to_b".into()
219                        }))
220                    })
221                )
222            }
223        )
224    }
225
226    #[test]
227    fn parse_basic_not() {
228        let e = Expression::parse("!type=image").unwrap();
229        assert_eq!(
230            e,
231            Expression::Not {
232                not: Box::from(Expression::Field(ExpressionField::Field {
233                    key: "type".into(),
234                    value: "image".into(),
235                }))
236            }
237        );
238    }
239
240    #[test]
241    fn parse_basic_subexpr() {
242        let e = Expression::parse("a && (b || c)").unwrap();
243        let expected_expression = Expression::And {
244            and: (
245                Box::from(Expression::Field(ExpressionField::Tag { tag: "a".into() })),
246                Box::from(Expression::Or {
247                    or: (
248                        Box::from(Expression::Field(ExpressionField::Tag { tag: "b".into() })),
249                        Box::from(Expression::Field(ExpressionField::Tag { tag: "c".into() })),
250                    ),
251                }),
252            ),
253        };
254
255        assert_eq!(e, expected_expression);
256    }
257
258    #[derive(Default)]
259    struct MockResolver {
260        tags: Vec<String>,
261        kv: HashMap<String, FieldValue>,
262        keys: Vec<String>,
263    }
264
265    impl MockResolver {
266        pub fn with_tag<S: Into<String>>(mut self, tag: S) -> Self {
267            self.tags.push(tag.into());
268            self
269        }
270
271        pub fn with_field<K: Into<String>, V: Into<FieldValue>>(mut self, k: K, v: V) -> Self {
272            let key: String = k.into();
273            self.keys.push(key.clone());
274            self.kv.insert(key, v.into());
275            self
276        }
277    }
278
279    impl rapidquery::FieldResolver<bool> for MockResolver {
280        type FieldType = ExpressionField;
281        type Error = Infallible;
282
283        fn resolve_empty(&self) -> Result<bool, Self::Error> {
284            Ok(true)
285        }
286
287        fn resolve(&self, field: &Self::FieldType) -> Result<bool, Self::Error> {
288            let val = match field {
289                ExpressionField::HasField { key } => self.keys.contains(key),
290                ExpressionField::Field { key, value } => self.kv.get(key) == Some(value),
291                ExpressionField::Tag { tag } => self.tags.contains(tag),
292            };
293
294            Ok(val)
295        }
296    }
297
298    #[test]
299    fn eval_empty_query() {
300        assert!(Expression::Empty
301            .evaluate(&MockResolver::default())
302            .unwrap())
303    }
304
305    #[test]
306    fn eval_tag_query() {
307        assert!(Expression::Field(ExpressionField::Tag {
308            tag: "hello".into()
309        })
310        .evaluate(&MockResolver::default().with_tag("hello"))
311        .unwrap());
312    }
313
314    #[test]
315    fn eval_tag_nomatch() {
316        assert!(!Expression::Field(ExpressionField::Tag {
317            tag: "yayeet".into()
318        })
319        .evaluate(&MockResolver::default().with_tag("Hello"))
320        .unwrap())
321    }
322
323    #[test]
324    fn eval_kv_query() {
325        assert!(Expression::Field(ExpressionField::Field {
326            key: "key".into(),
327            value: "val".into(),
328        })
329        .evaluate(&MockResolver::default().with_field("key", "val"))
330        .unwrap())
331    }
332
333    #[test]
334    fn eval_kv_nomatch() {
335        assert!(!Expression::Field(ExpressionField::Field {
336            key: "key".into(),
337            value: "val".into(),
338        })
339        .evaluate(&MockResolver::default().with_field("key", "yayeet"))
340        .unwrap())
341    }
342
343    #[test]
344    fn eval_and() {
345        assert!(Expression::parse("a && b")
346            .unwrap()
347            .evaluate(&MockResolver::default().with_tag("a").with_tag("b"))
348            .unwrap())
349    }
350
351    #[test]
352    fn eval_and_nomatch() {
353        assert!(!Expression::parse("a && b")
354            .unwrap()
355            .evaluate(&MockResolver::default().with_tag("a").with_tag("c"))
356            .unwrap())
357    }
358
359    #[test]
360    fn eval_or() {
361        assert!(Expression::parse("a || b")
362            .unwrap()
363            .evaluate(&MockResolver::default().with_tag("b"))
364            .unwrap())
365    }
366
367    #[test]
368    fn eval_or_nomatch() {
369        assert!(!Expression::parse("a || b")
370            .unwrap()
371            .evaluate(&MockResolver::default().with_tag("c"))
372            .unwrap())
373    }
374
375    #[test]
376    fn eval_not() {
377        assert!(Expression::parse("!a")
378            .unwrap()
379            .evaluate(&MockResolver::default().with_tag("b"))
380            .unwrap())
381    }
382
383    #[test]
384    fn eval_not_nomatch() {
385        assert!(!Expression::parse("!a")
386            .unwrap()
387            .evaluate(&MockResolver::default().with_tag("a"))
388            .unwrap())
389    }
390
391    #[test]
392    fn eval_and_or_nested() {
393        assert!(Expression::parse("(a || b) && c")
394            .unwrap()
395            .evaluate(&MockResolver::default().with_tag("a").with_tag("c"))
396            .unwrap())
397    }
398
399    #[test]
400    fn eval_not_nested() {
401        assert!(Expression::parse("!(a && b) && !(!c)")
402            .unwrap()
403            .evaluate(&MockResolver::default().with_tag("a").with_tag("c"))
404            .unwrap())
405    }
406}