1use std::convert::TryInto;
2use std::str::FromStr;
3
4use nom::{AsChar, InputTakeAtPosition};
5use nom::bytes::complete::{tag, take};
6use nom::character::complete::{alpha0, alpha1, anychar, digit0, digit1, one_of, alphanumeric1};
7use nom::combinator::{not, opt};
8use nom::error::{context, ErrorKind};
9use nom::multi::{many1, many_m_n, separated_list0, separated_list1};
10use nom::sequence::{delimited, preceded, terminated, tuple};
11use serde::{Deserialize, Serialize};
12
13use crate::{DomainCase, Res, ResourceKind, ResourceKindParts, ResourcePath, ResourcePathAndKind, ResourcePathAndType, ResourcePathSegmentKind, ResourceType, SkewerCase, Specific, Version, ResourceSelector, FieldSelection};
14use crate::error::Error;
15use crate::property::{ResourcePropertyValueSelector, DataSetAspectSelector, ResourceValueSelector};
16use nom::branch::alt;
17
18fn any_resource_path_segment<T>(i: T) -> Res<T, T>
19 where
20 T: InputTakeAtPosition,
21 <T as InputTakeAtPosition>::Item: AsChar,
22{
23 i.split_at_position1_complete(
24 |item| {
25 let char_item = item.as_char();
26 !(char_item == '-')
27 && !(char_item == '.')
28 && !(char_item == '/')
29 && !(char_item == '_')
30 && !(char_item.is_alpha() || char_item.is_dec_digit())
31 },
32 ErrorKind::AlphaNumeric,
33 )
34}
35
36fn loweralphanumerichyphen1<T>(i: T) -> Res<T, T>
37 where
38 T: InputTakeAtPosition,
39 <T as InputTakeAtPosition>::Item: AsChar,
40{
41 i.split_at_position1_complete(
42 |item| {
43 let char_item = item.as_char();
44 !(char_item == '-')
45 && !((char_item.is_alpha() && char_item.is_lowercase()) || char_item.is_dec_digit())
46 },
47 ErrorKind::AlphaNumeric,
48 )
49}
50
51
52fn anything_but_single_quote<T>(i: T) -> Res<T, T>
53 where
54 T: InputTakeAtPosition,
55 <T as InputTakeAtPosition>::Item: AsChar,
56{
57 i.split_at_position1_complete(
58 |item| {
59 let char_item = item.as_char();
60 char_item == '\''
61 },
62 ErrorKind::AlphaNumeric,
63 )
64}
65
66fn parse_domain(input: &str) -> Res<&str, DomainCase> {
67 context(
68 "domain",
69 tuple((
70 many1(terminated(loweralphanumerichyphen1, tag("."))),
71 loweralphanumerichyphen1,
72 )),
73 )(input)
74 .map(|(next_input, mut res)| {
75 if !res.1.is_empty() {
76 res.0.push(res.1);
77 }
78 (next_input, DomainCase::new( res.0.join(".").as_str()))
79 })
80}
81
82fn parse_version_major_minor_patch(input: &str) -> Res<&str, (usize, usize, usize)> {
83 context(
84 "version_major_minor_patch",
85 tuple((
86 terminated(digit1, tag(".")),
87 terminated(digit1, tag(".")),
88 terminated(digit1, not(digit1)),
89 )),
90 )(input)
91 .map(|(next_input, res)| {
92 (
93 next_input,
94 (
95 res.0.parse().unwrap(),
96 res.1.parse().unwrap(),
97 res.2.parse().unwrap(),
98 ),
99 )
100 })
101}
102
103fn parse_version(input: &str) -> Res<&str, Version> {
104 context(
105 "version",
106 tuple((parse_version_major_minor_patch, opt(preceded(tag("-"), parse_skewer)))),
107 )(input)
108 .map(|(next_input, ((major, minor, patch), release))| {
109 (next_input, Version::new(major, minor, patch, release))
110 })
111}
112
113fn parse_skewer(input: &str) -> Res<&str, SkewerCase> {
114 context("skewer-case", loweralphanumerichyphen1)(input)
115 .map(|(input, skewer)| (input, SkewerCase::new(skewer)))
116}
117
118fn parse_specific(input: &str) -> Res<&str, Specific> {
119 context(
120 "specific",
121 tuple((
122 terminated(parse_domain, tag(":")),
123 terminated(loweralphanumerichyphen1, tag(":")),
124 terminated(loweralphanumerichyphen1, tag(":")),
125 parse_version,
126 )),
127 )(input)
128 .map(|(next_input, (vendor, product, variant, version))| {
129 (
130 next_input,
131 Specific {
132 vendor: vendor,
133 product: product.to_string(),
134 variant: variant.to_string(),
135 version: version,
136 },
137 )
138 })
139}
140
141
142pub fn parse_resource_type(input: &str) -> Res<&str, Result<ResourceType,Error>> {
143 context(
144 "resource_type",
145 delimited(
146 tag("<"),
147 tuple((
148 alpha1,
149 opt(tag("<?>")),
150 )),
151 tag(">"),
152 ),
153 )(input)
154 .map(|(input, (rt, _))| {
155 (input, ResourceType::from_str(rt))
156 })
157}
158
159pub fn parse_resource_kind(input: &str) -> Res<&str, Result<ResourceKind,Error>> {
160 context(
161 "kind",
162 delimited(
163 tag("<"),
164 tuple((
165 alpha1,
166 opt(delimited(
167 tag("<"),
168 tuple((alpha1, opt(delimited(tag("<"), parse_specific, tag(">"))))),
169 tag(">"),
170 )),
171 )),
172 tag(">"),
173 ),
174 )(input)
175 .map(|(input, (rt, more))| {
176 let kind = match &more {
177 None => Option::None,
178 Some((kind, _)) => Option::Some((*kind).clone().to_string()),
179 };
180 let spec = match &more {
181 None => Option::None,
182 Some((_, Option::Some(spec))) => Option::Some(spec.clone()),
183 _ => Option::None,
184 };
185 (
186 input,
187 ResourceKindParts {
188 resource_type: rt.to_string(),
189 kind: kind,
190 specific: spec,
191 }.try_into(),
192 )
193 })
194}
195pub fn parse_resource_path(input: &str) -> Res<&str, ResourcePath> {
196 context(
197 "resource-path",
198 separated_list0(
199 nom::character::complete::char(':'),
200 any_resource_path_segment,
201 ),
202 )(input).map( |(next_input, segments) | {
203 let segments : Vec<String> = segments.iter().map(|s|s.to_string()).collect();
204 (next_input, ResourcePath {
205 segments
206 })
207 } )
208}
209
210pub fn parse_resource_path_and_kind(input: &str) -> Res<&str, Result<ResourcePathAndKind,Error>> {
211 context(
212 "parse_resource_path_and_kind",
213 tuple(
214 (parse_resource_path,
215 parse_resource_kind)
216 ),
217 )(input).map( |(next_input, (path, resource_kind)) | {
218
219 (next_input,
220
221 {
222 match resource_kind {
223 Ok(kind) => {
224 ResourcePathAndKind::new(path,kind)
225 }
226 Err(error) => {
227 Err(error.into())
228 }
229 }
230 }
231
232
233 )
234 } )
235}
236
237pub fn parse_resource_path_and_type(input: &str) -> Res<&str, Result<ResourcePathAndType,Error>> {
238 context(
239 "parse_resource_path_and_type",
240 tuple(
241 (parse_resource_path,
242 parse_resource_type)
243 ),
244 )(input).map( |(next_input, (path, resource_type)) | {
245
246 (next_input,
247
248 {
249 match resource_type{
250 Ok(resource_type) => {
251 Ok(ResourcePathAndType {
252 path,
253 resource_type
254 })
255 }
256 Err(error) => {
257 Err(error.into())
258 }
259 }
260 }
261
262
263 )
264 } )
265}
266
267pub fn parse_mapping(input: &str) -> Res<&str, &str> {
268 context( "parse_mapping",
269 delimited(
270 tag("['"),
271 anything_but_single_quote,
272 tag("']"),
273 ) ) (input)
274}
275
276pub fn parse_aspect_mapping(input: &str) -> Res<&str, SkewerCase> {
277 context(
278 "parse_aspect_mapping",
279 delimited(
280 tag("['"),
281 parse_skewer,
282 tag("']"),
283 ) ) (input)
284}
285
286pub fn parse_resource_property_value_selector(input: &str) -> Res<&str, Result<ResourcePropertyValueSelector,Error>> {
287 context(
288 "parse_resource_property_value_selector",
289 tuple(
290 (parse_skewer,opt(tuple( (alt( (parse_skewer,parse_aspect_mapping) ), opt(parse_mapping)) ) )
291 ),
292 ))(input) .map( |(next_input, (property,aspect))| {
293
294 match property.to_string().as_str() {
295 "state" => {
296 match aspect {
297 None => {
298 (next_input,Ok(ResourcePropertyValueSelector::state()))
299 }
300 Some((aspect, field) ) => {
301 match field {
302 None => {
303 (next_input,Ok(ResourcePropertyValueSelector::state_aspect(aspect.to_string().as_str())))
304 }
305 Some(field) => {
306 (next_input,Ok(ResourcePropertyValueSelector::state_aspect_field(aspect.to_string().as_str(), field )))
307 }
308 }
309 }
310 }
311 },
312 property => return (next_input, Err(format!("cannot match a selector for resource property '{}'",property).into()))
313 }
314
315 })
316}
317
318pub fn parse_resource_value_selector(input: &str) -> Res<&str, Result<ResourceValueSelector,Error>> {
319 context(
320 "parse_resource_value_selector",
321 tuple(
322 (terminated(parse_resource_path, tag("::")), parse_resource_property_value_selector )
323
324 ),
325 )(input).map( |(next_input, (path, property )) | {
326 match property {
327 Ok(property) => {
328 (next_input, Ok(ResourceValueSelector {
329 resource: path,
330 property
331 }))
332 }
333 Err(err) => {
334 (next_input, Err(err))
335 }
336 }
337 })
338}
339
340#[derive(Debug,Clone,Serialize,Deserialize,Eq,PartialEq,Hash)]
341pub enum ResourceExpression {
342 Path(ResourcePath),
343 Kind(ResourcePathAndKind)
344}
345
346#[cfg(test)]
347mod tests {
348 use std::convert::TryInto;
349 use std::str::FromStr;
350
351 use crate::{ResourcePath, ResourcePathAndKind};
352 use crate::error::Error;
353 use crate::parse::{parse_resource_path, parse_resource_path_and_kind, parse_resource_value_selector};
354 use crate::property::{ResourcePropertyValueSelector, DataSetAspectSelector, FieldValueSelector, MetaFieldValueSelector};
355
356 #[test]
357 fn test_parse_resource_path() -> Result<(), Error> {
358 let (leftover, path)= parse_resource_path("hello:my:future")?;
359 assert!(leftover.len()==0);
360 assert!(path.segments.len()==3);
361 let (leftover, path)= parse_resource_path("hello:my:future<")?;
362 assert!(leftover.len()==1);
363
364 let (leftover, path)= parse_resource_path("that.bi-zi:ba_tiko:/NOW_HE_DEAD")?;
365
366 assert!(leftover.len()==0);
367 assert!(path.segments.len()==3);
368
369 Ok(())
370 }
371
372 #[test]
373 fn test_resource_path_from_str() -> Result<(), Error> {
374 let p = "that.bi-zi:ba_tiko:/NOW_HE_DEAD";
375 let path = ResourcePath::from_str(p)?;
376 assert!( path.to_string().as_str() == p);
377
378 Ok(())
379 }
380
381 #[test]
382 fn test_resource_path_and_kind_from_str() -> Result<(), Error> {
383 let p = "hello:my:db<Database<Relational<mysql.org:mysql:innodb:7.0.0>>>";
384 let path = ResourcePathAndKind::from_str(p)?;
385 assert!( path.to_string().as_str() == p);
386
387 Ok(())
388 }
389
390
391 #[test]
392 fn test_parse_resource_path_and_kind() -> Result<(), Error> {
393 let (leftover, result)= parse_resource_path_and_kind("hello:my<SubSpace>")?;
394 let path = result?;
395 assert!(leftover.len()==0);
396 assert!(path.path.segments.len()==2);
397
398 let (leftover, result)= parse_resource_path_and_kind("hello:my:bundle:1.2.0<ArtifactBundle>")?;
399 let path = result?;
400 assert!(leftover.len()==0);
401 assert!(path.path.segments.len()==4);
402
403 let (leftover, result)= parse_resource_path_and_kind("hello:my:db<Database<Relational>>")?;
404 assert!(result.is_err());
405 let (leftover, result)= parse_resource_path_and_kind("hello:my:db<Database<Relational<mysql.org:mysql:innodb:7.0.0>>>")?;
406 let path = result?;
407 assert!(leftover.len()==0);
408 assert!(path.path.segments.len()==3);
409
410 Ok(())
411 }
412
413 #[test]
414 fn test_parse_resource_value_selector() -> Result<(), Error> {
415 let (leftover, result)= parse_resource_value_selector("hello:my::state")?;
416 let selector = result?;
417 assert!(leftover.len()==0);
418 match selector.property {
419 ResourcePropertyValueSelector::State { aspect: selector, field } => {
420 assert!(selector== DataSetAspectSelector::All);
421 assert!(field==FieldValueSelector::All);
422 }
423 _ => { assert!(false) }
424 }
425
426 let (leftover, result)= parse_resource_value_selector("hello:my::state['content']")?;
427 let selector = result?;
428 assert!(leftover.len()==0);
429 match selector.property {
430 ResourcePropertyValueSelector::State { aspect , field } => {
431 assert!(aspect== DataSetAspectSelector::Exact("content".to_string()));
432 assert!(field==FieldValueSelector::All);
433 }
434 _ => { assert!(false) }
435 }
436
437 let (leftover, result)= parse_resource_value_selector("hello:my::state['content']['Content-Type']")?;
438 let selector = result?;
439 assert!(leftover.len()==0);
440 match selector.property {
441 ResourcePropertyValueSelector::State { aspect , field } => {
442 assert!(aspect== DataSetAspectSelector::Exact("content".to_string()));
443 assert!(field==FieldValueSelector::Meta(MetaFieldValueSelector::Exact("Content-Type".to_string())));
444 }
445 _ => { assert!(false) }
446 }
447
448 let result = parse_resource_value_selector("hello:my:state['content']['Content-Type']");
449 assert!(result.is_err());
450
451 Ok(())
452 }
453}