drogue_client/registry/v1/data/common/labels/
parser.rs1use 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}