hq_rs/filter/
parser.rs

1use pest::Parser;
2use pest_derive::Parser;
3
4use super::error::FilterError;
5
6#[derive(Parser)]
7#[grammar = "filter/grammar.pest"]
8pub struct Filter {}
9
10/// one segment of a filter ([`parse_filter`] returns `Vec<Field>`)
11///
12/// e.g. for the filter `'.foo{"bar"}.baz'` there are two segments:
13///
14/// * the name "foo" and the label "bar"
15/// * the name "baz"
16#[derive(Clone, Debug, PartialEq)]
17pub struct Field {
18    /// an attribute or block name
19    pub name: String,
20    /// block labels
21    pub labels: Vec<String>,
22}
23
24impl Field {
25    pub fn new(name: &str) -> Self {
26        Field {
27            name: name.to_string(),
28            labels: Vec::new(),
29        }
30    }
31
32    pub fn labeled(name: &str, labels: &[&str]) -> Self {
33        Field {
34            name: name.to_string(),
35            labels: labels.iter().map(|label| label.to_string()).collect(),
36        }
37    }
38}
39
40/// parse `input` and return a vector of [`Field`]s
41///
42/// a valid filter is one or more chained segments
43pub fn parse_filter(input: &str) -> Result<Vec<Field>, Box<FilterError<Rule>>> {
44    let mut fields = Vec::new();
45    let pairs = Filter::parse(Rule::filter, input)?;
46    for pair in pairs {
47        let mut name = String::new();
48        let mut labels = Vec::new();
49
50        let inner_pairs = pair.into_inner();
51        for inner in inner_pairs {
52            match inner.as_rule() {
53                Rule::name => {
54                    // according to clippy, this is a more efficient way of doing
55                    // `name = inner.as_str().to_owned()`
56                    inner.as_str().clone_into(&mut name);
57                }
58                Rule::quoted_name => {
59                    inner.as_str().clone_into(&mut name);
60                }
61                Rule::label => {
62                    labels.push(inner.as_str().to_owned());
63                }
64                _ => {}
65            }
66        }
67        if !name.is_empty() {
68            fields.push(Field { name, labels });
69        }
70    }
71    Ok(fields)
72}
73
74#[cfg(test)]
75mod tests {
76    use std::error::Error;
77
78    use super::*;
79
80    #[test]
81    fn name_filter() -> Result<(), Box<dyn Error>> {
82        let input = ".a_name";
83        let expected = vec![Field::new("a_name")];
84        let fields = parse_filter(input)?;
85        assert_eq!(expected, fields);
86        Ok(())
87    }
88
89    #[test]
90    fn label_filter() -> Result<(), Box<dyn Error>> {
91        let input = ".a_name{\"a_label\"}";
92        let expected = vec![Field::labeled("a_name", &["a_label"])];
93        let fields = parse_filter(input)?;
94        assert_eq!(expected, fields);
95        Ok(())
96    }
97
98    #[test]
99    fn traversal_filter() -> Result<(), Box<dyn Error>> {
100        let input = ".a_name{\"a_label\"}.another_name{\"another_label\"}.third_name";
101        let expected = vec![
102            Field::labeled("a_name", &["a_label"]),
103            Field::labeled("another_name", &["another_label"]),
104            Field::new("third_name"),
105        ];
106        let fields = parse_filter(input)?;
107        assert_eq!(expected, fields);
108        Ok(())
109    }
110}