drogue_client/registry/v1/data/common/labels/
parser.rs

1use super::Operation;
2use nom::{
3    branch::*, bytes::complete::*, character::complete::*, combinator::*, multi::*, sequence::*,
4    AsChar, IResult,
5};
6use std::fmt::Formatter;
7
8#[derive(Clone, Debug, PartialEq, Eq)]
9pub struct ParserError {
10    details: String,
11}
12
13impl core::fmt::Display for ParserError {
14    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
15        write!(f, "ParserError: {}", self.details)
16    }
17}
18
19#[cfg(feature = "nom")]
20pub fn parse_from(value: &str) -> Result<Vec<Operation>, ParserError> {
21    let (remain, ops) = parse(value).map_err(|err| ParserError {
22        details: err.to_string(),
23    })?;
24
25    if !remain.is_empty() {
26        Err(ParserError {
27            details: format!("Unparsable remaining content: '{}'", remain),
28        })
29    } else {
30        Ok(ops)
31    }
32}
33
34fn parse(input: &str) -> IResult<&str, Vec<Operation>> {
35    separated_list0(tag(","), parse_one)(input)
36}
37
38fn parse_one(input: &str) -> IResult<&str, Operation> {
39    alt((
40        parse_in,
41        parse_not_in,
42        parse_equals,
43        parse_not_equals,
44        parse_exists,
45        parse_not_exists,
46    ))(input)
47}
48
49fn parse_exists(input: &str) -> IResult<&str, Operation> {
50    map(parse_label, |label| Operation::Exists(label.into()))(input)
51}
52
53fn parse_not_exists(input: &str) -> IResult<&str, Operation> {
54    map(preceded(tag("!"), parse_label), |label| {
55        Operation::NotExists(label.into())
56    })(input)
57}
58
59fn parse_equals(input: &str) -> IResult<&str, Operation> {
60    map(
61        tuple((parse_label, space0, tag("="), parse_value)),
62        |(label, _, _, value)| Operation::Eq(label.into(), value.into()),
63    )(input)
64}
65
66fn parse_not_equals(input: &str) -> IResult<&str, Operation> {
67    map(
68        tuple((parse_label, space0, tag("!="), parse_value)),
69        |(label, _, _, value)| Operation::NotEq(label.into(), value.into()),
70    )(input)
71}
72
73fn parse_in(input: &str) -> IResult<&str, Operation> {
74    map(
75        tuple((
76            parse_label,
77            space1,
78            tag("in"),
79            space0,
80            tag("("),
81            separated_list0(tag(","), parse_value),
82            tag(")"),
83        )),
84        |(label, _, _, _, _, value, _)| {
85            Operation::In(label.into(), value.iter().map(|s| s.to_string()).collect())
86        },
87    )(input)
88}
89
90fn parse_not_in(input: &str) -> IResult<&str, Operation> {
91    map(
92        tuple((
93            parse_label,
94            space1,
95            tag("notin"),
96            space0,
97            tag("("),
98            separated_list0(tag(","), parse_value),
99            tag(")"),
100        )),
101        |(label, _, _, _, _, value, _)| {
102            Operation::NotIn(label.into(), value.iter().map(|s| s.to_string()).collect())
103        },
104    )(input)
105}
106
107fn parse_label(input: &str) -> IResult<&str, &str> {
108    preceded(
109        space0,
110        recognize(tuple((
111            opt(tuple((
112                preceded(alpha1, many0(satisfy(|c| c.is_alphanum() || c == '.'))),
113                tag("/"),
114            ))),
115            parse_raw_value,
116        ))),
117    )(input)
118}
119
120fn parse_value(input: &str) -> IResult<&str, &str> {
121    map(
122        tuple((space0, parse_raw_value, space0)),
123        |(_, result, _)| result,
124    )(input)
125}
126
127fn parse_raw_value(input: &str) -> IResult<&str, &str> {
128    recognize(preceded(
129        alphanumeric1,
130        many0(satisfy(|c| {
131            c.is_alphanum() || c == '_' || c == '-' || c == '.'
132        })),
133    ))(input)
134}
135
136#[cfg(test)]
137mod test {
138
139    use super::*;
140
141    #[test]
142    fn test_raw_value() {
143        let (rem, value) = parse_raw_value("foo").unwrap();
144        assert_eq!(rem, "");
145        assert_eq!(value, "foo");
146    }
147
148    #[test]
149    fn test_parse_0() {
150        assert_eq!(parse_from(""), Ok(vec![]));
151    }
152
153    #[test]
154    fn test_parse_1() {
155        assert_eq!(parse_from("foo"), Ok(vec![Operation::Exists("foo".into())]));
156        assert_eq!(
157            parse_from("foo/bar"),
158            Ok(vec![Operation::Exists("foo/bar".into())])
159        );
160        assert_eq!(
161            parse_from("foo.baz/bar.baz"),
162            Ok(vec![Operation::Exists("foo.baz/bar.baz".into())])
163        );
164    }
165
166    #[test]
167    fn test_invalid_label() {
168        assert!(parse_from("foo/bar/bar").is_err(),);
169        assert!(parse_from("foo/").is_err(),);
170        assert!(parse_from("/bar").is_err(),);
171        assert!(parse_from("foo-bar/baz").is_err(),);
172    }
173
174    #[test]
175    fn test_parse_2() {
176        assert_eq!(
177            parse_from("foo,!bar"),
178            Ok(vec![
179                Operation::Exists("foo".into()),
180                Operation::NotExists("bar".into())
181            ])
182        );
183    }
184
185    #[test]
186    fn test_parse_eq_3() {
187        assert_eq!(
188            parse_from("foo=bar,bar!=baz,foo/bar.baz = baz"),
189            Ok(vec![
190                Operation::Eq("foo".into(), "bar".into()),
191                Operation::NotEq("bar".into(), "baz".into()),
192                Operation::Eq("foo/bar.baz".into(), "baz".into()),
193            ])
194        );
195    }
196
197    #[test]
198    fn test_parse_in_3() {
199        assert_eq!(
200            parse_from("foo in (bar, baz),foo notin (baz, bar)"),
201            Ok(vec![
202                Operation::In("foo".into(), vec!["bar".into(), "baz".into()]),
203                Operation::NotIn("foo".into(), vec!["baz".into(), "bar".into()]),
204            ])
205        );
206    }
207
208    #[test]
209    fn test_parse_whitespaces_1() {
210        assert_eq!(
211            parse_from("foo, foo in (bar, baz), foo notin (baz, bar)"),
212            Ok(vec![
213                Operation::Exists("foo".into()),
214                Operation::In("foo".into(), vec!["bar".into(), "baz".into()]),
215                Operation::NotIn("foo".into(), vec!["baz".into(), "bar".into()]),
216            ])
217        );
218    }
219
220    #[test]
221    fn test_parse_rem() {
222        assert_eq!(
223            parse_from("foo,#"),
224            Err(ParserError {
225                details: "Unparsable remaining content: ',#'".into()
226            })
227        );
228    }
229
230    #[test]
231    fn test_parse_valid_value_1() {
232        assert_eq!(
233            parse_from("foo=1"),
234            Ok(vec![Operation::Eq("foo".into(), "1".into())])
235        );
236    }
237}