starlane_resources/
parse.rs

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}