simple_selectors/
parser.rs

1use itertools::Itertools;
2use std::collections::HashMap;
3use std::str::CharIndices;
4use std::iter::Peekable;
5
6use super::ParseError;
7
8/// String map type to pass to the parse function
9pub type LabelMap<'a> = HashMap<&'a str, &'a str>;
10
11fn parse_key(index: &mut usize, chars: &mut Peekable<CharIndices>) -> Result<String, ParseError> {
12    // find the first non matching char
13    let key: String = chars
14        .take_while_ref(|&(_i, c)| match c {
15            'a'...'z' | 'A'...'Z' | '0'...'9' | '-' | '_' => true,
16            _ => false,
17        })
18        .map(|(i, c)| {
19            *index = i;
20            c
21        })
22        .collect();
23
24    if key.is_empty() {
25        let next = chars.peek();
26        if let Some(&(i, c)) = next {
27            *index = i;
28            return match c {
29                ',' => Ok(key),
30                _ => Err(ParseError::InvalidKey(i)),
31            };
32        } else {
33            return Err(ParseError::InvalidKey(*index));
34        }
35    }
36    Ok(key)
37}
38
39fn skip_whitespaces(index: &mut usize, chars: &mut Peekable<CharIndices>) {
40    // skip until no more whitespaces
41    while let Some(&(i, c)) = chars.peek() {
42        *index = i;
43        if c == ' ' {
44            chars.next();
45        } else {
46            break;
47        }
48    }
49}
50
51#[derive(Debug, PartialEq)]
52enum Operator {
53    Equality,
54    InEquality,
55    InSet,
56    NotInSet,
57}
58
59fn parse_operator(
60    index: &mut usize,
61    chars: &mut Peekable<CharIndices>,
62) -> Result<Operator, ParseError> {
63    match chars.next() {
64        Some((i, c)) => {
65            *index = i;
66            match c {
67                '=' => {
68                    if let Some(&(i, c)) = chars.peek() {
69                        if c == '=' {
70                            chars.next();
71                            *index = i;
72                        }
73                    }
74                    Ok(Operator::Equality)
75                }
76                '!' => match chars.next() {
77                    Some((i, c)) => {
78                        *index = i;
79                        match c {
80                            '=' => Ok(Operator::InEquality),
81                            _ => Err(ParseError::InvalidOperator(i)),
82                        }
83                    }
84                    None => Err(ParseError::InvalidOperator(i)),
85                },
86                'i' => match chars.next() {
87                    Some((i, c)) => {
88                        *index = i;
89                        match c {
90                            'n' => Ok(Operator::InSet),
91                            _ => Err(ParseError::InvalidOperator(i)),
92                        }
93                    }
94                    None => Err(ParseError::InvalidOperator(i)),
95                },
96                'n' => {
97                    let s: String = chars.take(4).map(|(_i, c)| c).collect();
98
99                    if s == "otin" {
100                        *index += 5;
101                        Ok(Operator::NotInSet)
102                    } else {
103                        Err(ParseError::InvalidOperator(i))
104                    }
105                }
106                _ => Err(ParseError::InvalidOperator(i)),
107            }
108        }
109        None => Err(ParseError::InvalidOperator(*index)),
110    }
111}
112
113fn parse_value(index: &mut usize, chars: &mut Peekable<CharIndices>) -> Result<String, ParseError> {
114    let value: String = chars
115        .take_while_ref(|&(_i, c)| match c {
116            '!' | '=' | ',' | '(' | ')' => false,
117            _ => !c.is_whitespace(),
118        })
119        .map(|(i, c)| {
120            *index = i;
121            c
122        })
123        .collect();
124
125    if value.is_empty() {
126        Err(ParseError::ExpectingValue(*index))
127    } else {
128        Ok(value)
129    }
130}
131
132fn parse_set(
133    index: &mut usize,
134    chars: &mut Peekable<CharIndices>,
135) -> Result<Vec<String>, ParseError> {
136    let mut result = vec![];
137
138    let parens = chars.next();
139    match parens {
140        None => Err(ParseError::ExpectingLeftParenthesis(*index)),
141        Some((i, c)) => {
142            *index = i;
143            match c {
144                '(' => Ok(()),
145                _ => Err(ParseError::ExpectingLeftParenthesis(i)),
146            }
147        }
148    }?;
149
150    let mut first = true;
151    loop {
152        skip_whitespaces(index, chars);
153        if !first {
154            match chars.next() {
155                None => return Err(ParseError::ExpectingEndOrComma(*index)),
156                Some((i, c)) => {
157                    *index = i;
158                    match c {
159                        ')' => {
160                            break;
161                        }
162                        ',' => (),
163                        _ => {
164                            return Err(ParseError::ExpectingEndOrComma(i));
165                        }
166                    }
167                }
168            }
169        }
170        first = false;
171        result.push(parse_value(index, chars)?);
172    }
173    Ok(result)
174}
175
176fn compare_value(label: Option<&&str>, value: &str) -> bool {
177    match label {
178        None => false,
179        Some(label) => *label == value,
180    }
181}
182
183fn compare_set(label: Option<&&str>, value: &[String]) -> bool {
184    match label {
185        None => false,
186        Some(label) => value.contains(&String::from(*label)),
187    }
188}
189
190fn parse_operation(
191    index: &mut usize,
192    label: Option<&&str>,
193    chars: &mut Peekable<CharIndices>,
194) -> Result<bool, ParseError> {
195    let operator = parse_operator(index, chars)?;
196    skip_whitespaces(index, chars);
197
198    Ok(match operator {
199        Operator::Equality => compare_value(label, &parse_value(index, chars)?),
200        Operator::InEquality => !compare_value(label, &parse_value(index, chars)?),
201        Operator::InSet => compare_set(label, &parse_set(index, chars)?),
202        Operator::NotInSet => !compare_set(label, &parse_set(index, chars)?),
203    })
204}
205
206fn parse_requirement(
207    index: &mut usize,
208    chars: &mut Peekable<CharIndices>,
209    labels: &LabelMap,
210) -> Result<bool, ParseError> {
211    skip_whitespaces(index, chars);
212
213    let mut positive = true;
214    if let Some(&(i, c)) = chars.peek() {
215        if c == '!' {
216            *index = i;
217            positive = false;
218            chars.next();
219            skip_whitespaces(index, chars);
220        }
221    }
222    let key = parse_key(index, chars)?;
223    skip_whitespaces(index, chars);
224
225    let simple_exists_check = {
226        let next = chars.peek();
227        match next {
228            None => true,
229            Some(&(i, c)) => {
230                *index = i;
231                match c {
232                    ',' => true,
233                    _ => false,
234                }
235            }
236        }
237    };
238    let value = if simple_exists_check {
239        labels.contains_key(key.as_str())
240    } else {
241        parse_operation(index, labels.get(key.as_str()), chars)?
242    };
243
244    Ok(if positive { value } else { !value })
245}
246
247fn parse_selector(selector: &str, labels: &LabelMap) -> Result<bool, ParseError> {
248    let mut chars = selector.char_indices().peekable();
249
250    let mut index = 0;
251    let mut result = parse_requirement(&mut index, &mut chars, labels)?;
252    loop {
253        skip_whitespaces(&mut index, &mut chars);
254        if let Some(&(i, c)) = chars.peek() {
255            if c == ',' {
256                chars.next();
257            }
258            index = i;
259        } else {
260            break;
261        }
262        result = parse_requirement(&mut index, &mut chars, labels)? && result;
263    }
264    Ok(result)
265}
266
267/// this function will parse a selector and return its result.
268/// A (valid) selector will return a simple boolean indicating
269/// if the selector matched or not.
270pub fn parse(selector: &str, labelmap: &LabelMap) -> Result<bool, ParseError> {
271    if selector.is_empty() {
272        return Err(ParseError::EmptySelector);
273    }
274    parse_selector(selector, labelmap)
275}
276
277#[cfg(test)]
278mod tests {
279    #[test]
280    fn it_should_properly_parse_a_full_key() {
281        let selector = "test";
282
283        let mut index = 0;
284        let mut char_indices = selector.char_indices().peekable();
285        let result = super::parse_key(&mut index, &mut char_indices);
286        assert!(result.is_ok());
287        assert_eq!(result.unwrap(), "test");
288    }
289
290    #[test]
291    fn it_will_parse_keys_until_invalid_chars() {
292        let selector = "test/";
293
294        let mut index = 0;
295        let mut char_indices = selector.char_indices().peekable();
296        let result = super::parse_key(&mut index, &mut char_indices);
297        assert!(result.is_ok());
298        assert_eq!(result.unwrap(), "test");
299
300        let result = super::parse_key(&mut index, &mut char_indices);
301        assert_matches!(result, Err(super::ParseError::InvalidKey(4)));
302    }
303
304    #[test]
305    fn it_will_accept_valid_keys_but_stop_consuming() {
306        let selector = "test /";
307
308        let mut index = 0;
309        let mut char_indices = selector.char_indices().peekable();
310        let key = super::parse_key(&mut index, &mut char_indices);
311        assert_eq!(key.unwrap(), "test");
312        assert_matches!(char_indices.next(), Some((4, ' ')));
313    }
314
315    #[test]
316    fn it_will_parse_single_char_equality() {
317        let op = "=";
318
319        let mut index = 0;
320        let mut char_indices = op.char_indices().peekable();
321        let result = super::parse_operator(&mut index, &mut char_indices);
322        assert!(result.is_ok());
323        assert_eq!(result.unwrap(), super::Operator::Equality);
324    }
325
326    #[test]
327    fn it_will_parse_double_char_equality() {
328        let op = "==";
329
330        let mut index = 0;
331        let mut char_indices = op.char_indices().peekable();
332        let result = super::parse_operator(&mut index, &mut char_indices);
333        assert!(result.is_ok());
334        assert_eq!(result.unwrap(), super::Operator::Equality);
335
336        assert!(char_indices.next().is_none());
337    }
338
339    #[test]
340    fn it_will_reject_garbage() {
341        let op = "#";
342
343        let mut index = 0;
344        let mut char_indices = op.char_indices().peekable();
345        let result = super::parse_operator(&mut index, &mut char_indices);
346        assert_matches!(result, Err(super::ParseError::InvalidOperator(0)));
347    }
348
349    #[test]
350    fn it_will_parse_inequality() {
351        let op = "!=";
352
353        let mut index = 0;
354        let mut char_indices = op.char_indices().peekable();
355        let result = super::parse_operator(&mut index, &mut char_indices);
356        assert!(result.is_ok());
357        assert_eq!(result.unwrap(), super::Operator::InEquality);
358
359        assert!(char_indices.next().is_none());
360    }
361
362    #[test]
363    fn it_will_reject_garbage_inequality() {
364        let op = "!#";
365
366        let mut index = 0;
367        let mut char_indices = op.char_indices().peekable();
368        let result = super::parse_operator(&mut index, &mut char_indices);
369        assert!(result.is_err());
370        assert_matches!(result, Err(super::ParseError::InvalidOperator(1)));
371    }
372
373    #[test]
374    fn it_will_accept_in() {
375        let op = "in";
376
377        let mut index = 0;
378        let mut char_indices = op.char_indices().peekable();
379        let result = super::parse_operator(&mut index, &mut char_indices);
380        assert!(result.is_ok());
381        assert_eq!(result.unwrap(), super::Operator::InSet);
382    }
383
384    #[test]
385    fn it_will_accept_notin() {
386        let op = "notin";
387
388        let mut index = 0;
389        let mut char_indices = op.char_indices().peekable();
390        let result = super::parse_operator(&mut index, &mut char_indices);
391        assert!(result.is_ok());
392        assert_eq!(result.unwrap(), super::Operator::NotInSet);
393    }
394
395    #[test]
396    fn it_will_refuse_garbage_in() {
397        let op = "ian";
398
399        let mut index = 0;
400        let mut char_indices = op.char_indices().peekable();
401        let result = super::parse_operator(&mut index, &mut char_indices);
402        assert_matches!(result, Err(super::ParseError::InvalidOperator(1)));
403    }
404
405    #[test]
406    fn it_will_refuse_garbage_notin() {
407        let op = "no";
408
409        let mut index = 0;
410        let mut char_indices = op.char_indices().peekable();
411        let result = super::parse_operator(&mut index, &mut char_indices);
412        assert_matches!(result, Err(super::ParseError::InvalidOperator(0)));
413    }
414}