Skip to main content

selectors/
parser.rs

1// Copyright 2021 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use crate::error::ParseError;
6use crate::ir::*;
7use crate::validate::{ValidateComponentSelectorExt, ValidateExt, ValidateTreeSelectorExt};
8use bitflags::bitflags;
9
10use winnow::Parser;
11use winnow::ascii::{multispace0, take_escaped};
12use winnow::combinator::{alt, cond, eof, opt, preceded, separated};
13use winnow::error::{ErrMode, ParserError};
14use winnow::token::{none_of, one_of, take_while};
15
16const ALL_TREE_NAMES_SELECTED_SYMBOL: &str = "...";
17
18bitflags! {
19    #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
20    pub struct RequireEscaped: u8 {
21        const NONE = 0;
22        const COLONS = 1;
23        const WHITESPACE = 2;
24    }
25}
26
27/// Recognizes 0 or more spaces or tabs.
28fn whitespace0<'a, E>(input: &mut &'a str) -> Result<&'a str, ErrMode<E>>
29where
30    E: ParserError<&'a str>,
31{
32    take_while(0.., (' ', '\t')).parse_next(input)
33}
34
35/// Parses an input containing any number and type of whitespace at the front.
36fn spaced<'a, E, F, O>(parser: F) -> impl Parser<&'a str, O, ErrMode<E>>
37where
38    F: Parser<&'a str, O, ErrMode<E>>,
39    E: ParserError<&'a str>,
40{
41    preceded(whitespace0::<E>, parser)
42}
43
44fn tree_name_item<'a, E>(input: &mut &'a str) -> Result<&'a str, ErrMode<E>>
45where
46    E: ParserError<&'a str>,
47{
48    let value_parser = alt((
49        winnow::combinator::delimited(
50            '"',
51            take_escaped(none_of(['\\', '"']), '\\', one_of(['"', '*', '/', ':', ' '])),
52            '"',
53        ),
54        take_while(1.., |c| c != ',' && c != ']'),
55    ));
56    let (_, _, value) = spaced(("name", "=", value_parser)).parse_next(input)?;
57    Ok(value)
58}
59
60fn conjoined_tree_names<'a, E>() -> impl Parser<&'a str, Option<TreeNames<'a>>, ErrMode<E>>
61where
62    E: winnow::error::ParserError<&'a str>,
63{
64    opt(winnow::combinator::delimited(
65        '[',
66        alt((
67            spaced(ALL_TREE_NAMES_SELECTED_SYMBOL).map(|_| TreeNames::All),
68            separated(1.., tree_name_item::<E>, spaced(",")).map(|items: Vec<&str>| items.into()),
69        )),
70        ']',
71    ))
72}
73
74fn extract_from_quotes(input: &str) -> &str {
75    if input.starts_with('"') && input.len() > 1 { &input[1..input.len() - 1] } else { input }
76}
77
78/// Returns the parser for a tree selector, which is a node selector and an optional property selector.
79fn tree_selector<'a, E>(
80    required_escapes: RequireEscaped,
81) -> impl Parser<&'a str, TreeSelector<'a>, ErrMode<E>>
82where
83    E: ParserError<&'a str>,
84{
85    move |input: &mut &'a str| {
86        let mut esc = move |input: &mut &'a str| {
87            if required_escapes.intersects(RequireEscaped::WHITESPACE) {
88                take_escaped(
89                    none_of([':', '/', '\\', ' ', '\t', '\n']),
90                    '\\',
91                    one_of(['*', ' ', '\t', '/', ':', '\\']),
92                )
93                .parse_next(input)
94            } else {
95                take_escaped(
96                    none_of([':', '/', '\\', '\t', '\n']),
97                    '\\',
98                    one_of(['*', ' ', '\t', '/', ':', '\\']),
99                )
100                .parse_next(input)
101            }
102        };
103
104        let tree_names = conjoined_tree_names::<E>().parse_next(input)?;
105
106        let node_segments: Vec<&str> = separated(1.., esc.by_ref(), "/").parse_next(input)?;
107        let property_segment: Option<&str> = opt(winnow::combinator::preceded(
108            ":",
109            esc.by_ref().verify(|value: &str| !value.is_empty()),
110        ))
111        .parse_next(input)?;
112        Ok(TreeSelector {
113            node: node_segments.into_iter().map(|value| value.into()).collect(),
114            property: property_segment.map(|value| value.into()),
115            tree_names,
116        })
117    }
118}
119
120/// Returns the parser for a component selector. The parser accepts unescaped depending on the
121/// the argument `escape_colons`.
122fn component_selector<'a, E>(
123    required_escapes: RequireEscaped,
124) -> impl Parser<&'a str, ComponentSelector<'a>, ErrMode<E>>
125where
126    E: ParserError<&'a str>,
127{
128    move |input: &mut &'a str| {
129        let segments: Vec<&str> = if required_escapes.intersects(RequireEscaped::COLONS) {
130            let mut segment = take_escaped(
131                take_while(1.., ('a'..='z', 'A'..='Z', '0'..='9', '*', '.', '-', '_', '>', '<')),
132                '\\',
133                ":",
134            );
135            winnow::combinator::preceded(
136                opt(alt(("./", "/"))),
137                separated(1.., segment.by_ref(), "/"),
138            )
139            .parse_next(input)?
140        } else {
141            let mut segment = take_while(
142                1..,
143                ('a'..='z', 'A'..='Z', '0'..='9', '*', '.', '-', '_', '>', '<', ':'),
144            );
145            winnow::combinator::preceded(
146                opt(alt(("./", "/"))),
147                separated(1.., segment.by_ref(), "/"),
148            )
149            .parse_next(input)?
150        };
151        Ok(ComponentSelector { segments: segments.into_iter().map(Segment::from).collect() })
152    }
153}
154
155fn comment<'a, E>(input: &mut &'a str) -> Result<&'a str, ErrMode<E>>
156where
157    E: ParserError<&'a str>,
158{
159    let comment = spaced(winnow::combinator::preceded(
160        "//",
161        take_while(0.., |c: char| c != '\n' && c != '\r'),
162    ))
163    .parse_next(input)?;
164    if !input.is_empty() {
165        let _ = one_of(['\n', '\r']).parse_next(input)?;
166    }
167    Ok(comment)
168}
169
170/// Parses a core selector (component + tree + property). It accepts both raw selectors or
171/// selectors wrapped in double quotes. Selectors wrapped in quotes accept spaces in the tree and
172/// property names and require internal quotes to be escaped.
173fn core_selector<'a, E>(
174    input: &mut &'a str,
175) -> Result<(ComponentSelector<'a>, TreeSelector<'a>), ErrMode<E>>
176where
177    E: ParserError<&'a str>,
178{
179    let input_str = *input;
180    let required_tree_escape = if input_str.starts_with('"') {
181        RequireEscaped::empty()
182    } else {
183        RequireEscaped::WHITESPACE
184    };
185    let unwrapped = extract_from_quotes(input_str);
186    let mut unwrapped_input = unwrapped;
187    let (component, _, tree, _, _) = (
188        component_selector::<E>(RequireEscaped::COLONS),
189        ":",
190        tree_selector::<E>(required_tree_escape),
191        whitespace0::<E>,
192        eof,
193    )
194        .parse_next(&mut unwrapped_input)?;
195    *input = "";
196    Ok((component, tree))
197}
198
199/// Recognizes selectors, with comments allowed or disallowed.
200fn do_parse_selector<'a, E>(
201    allow_inline_comment: bool,
202) -> impl Parser<&'a str, Selector<'a>, ErrMode<E>>
203where
204    E: ParserError<&'a str>,
205{
206    (spaced(core_selector::<E>), cond(allow_inline_comment, opt(comment::<E>)), whitespace0::<E>)
207        .map(|((component, tree), _, _)| Selector { component, tree })
208}
209
210/// A fast efficient error that won't provide much information besides the name kind of nom parsers
211/// that failed and the position at which it failed.
212pub struct FastError;
213
214/// A slower but more user friendly error that will provide information about the chain of parsers
215/// that found the error and some context.
216pub struct VerboseError;
217
218mod private {
219    pub trait Sealed {}
220
221    impl Sealed for super::FastError {}
222    impl Sealed for super::VerboseError {}
223}
224
225/// Implemented by types which can be used to specify the error strategy the parsers should use.
226pub trait ParsingError<'a>: private::Sealed {
227    type Internal: ParserError<&'a str>;
228
229    fn to_error(input: &str, err: ErrMode<Self::Internal>) -> ParseError;
230}
231
232impl<'a> ParsingError<'a> for FastError {
233    type Internal = winnow::error::InputError<&'a str>;
234
235    fn to_error(_: &str, err: ErrMode<Self::Internal>) -> ParseError {
236        let e = err.into_inner().unwrap();
237        ParseError::Fast { input: e.input.to_string() }
238    }
239}
240
241impl<'a> ParsingError<'a> for VerboseError {
242    type Internal = winnow::error::ContextError;
243
244    fn to_error(_input: &str, err: ErrMode<Self::Internal>) -> ParseError {
245        ParseError::Verbose(format!("{:?}", err.into_inner().unwrap()))
246    }
247}
248
249/// Parses the input into a `Selector`.
250pub fn selector<'a, E>(input: &'a str) -> Result<Selector<'a>, ParseError>
251where
252    E: ParsingError<'a>,
253{
254    let mut input_ref = input;
255    let result = (do_parse_selector::<E::Internal>(false), eof).parse_next(&mut input_ref);
256    match result {
257        Ok((selector, _)) => {
258            selector.validate()?;
259            Ok(selector)
260        }
261        Err(e) => Err(E::to_error(input, e)),
262    }
263}
264
265/// Parses the input into a `TreeSelector` ignoring any whitespace around the component
266/// selector.
267pub fn standalone_tree_selector<'a, E>(input: &'a str) -> Result<TreeSelector<'a>, ParseError>
268where
269    E: ParsingError<'a>,
270{
271    let required_tree_escape =
272        if input.starts_with('"') { RequireEscaped::empty() } else { RequireEscaped::WHITESPACE };
273    let unwrapped = extract_from_quotes(input);
274
275    let mut input_ref = unwrapped;
276    let result = (spaced(tree_selector::<E::Internal>(required_tree_escape)), multispace0, eof)
277        .parse_next(&mut input_ref);
278    match result {
279        Ok((tree_selector, _, _)) => {
280            tree_selector.validate()?;
281            Ok(tree_selector)
282        }
283        Err(e) => Err(E::to_error(input, e)),
284    }
285}
286
287/// Parses the input into a `ComponentSelector` ignoring any whitespace around the component
288/// selector.
289pub fn consuming_component_selector<'a, E>(
290    input: &'a str,
291    required_escapes: RequireEscaped,
292) -> Result<ComponentSelector<'a>, ParseError>
293where
294    E: ParsingError<'a>,
295{
296    let mut input_ref = input;
297    let result = (spaced(component_selector::<E::Internal>(required_escapes)), multispace0, eof)
298        .parse_next(&mut input_ref);
299    match result {
300        Ok((component_selector, _, _)) => {
301            component_selector.validate()?;
302            Ok(component_selector)
303        }
304        Err(e) => Err(E::to_error(input, e)),
305    }
306}
307
308/// Parses the given input line into a Selector or None.
309pub fn selector_or_comment<'a, E>(input: &'a str) -> Result<Option<Selector<'a>>, ParseError>
310where
311    E: ParsingError<'a>,
312{
313    let mut input_ref = input;
314    let maybe_selector: Option<Selector<'a>> = match comment::<E::Internal>(&mut input_ref) {
315        Ok(_) => Ok(None),
316        Err(ErrMode::Backtrack(_)) => {
317            do_parse_selector::<E::Internal>(true).parse_next(&mut input_ref).map(Some)
318        }
319        Err(e) => Err(e),
320    }
321    .map_err(|e| E::to_error(input, e))?;
322
323    let _: &str = eof.parse_next(&mut input_ref).map_err(|e| E::to_error(input, e))?;
324
325    if let Some(selector) = maybe_selector {
326        selector.validate()?;
327        Ok(Some(selector))
328    } else {
329        Ok(None)
330    }
331}
332
333#[cfg(test)]
334mod tests {
335    use super::*;
336
337    #[fuchsia::test]
338    fn canonical_component_selector_test() {
339        let test_vector = vec![
340            (
341                "a/b/c",
342                vec![
343                    Segment::ExactMatch("a".into()),
344                    Segment::ExactMatch("b".into()),
345                    Segment::ExactMatch("c".into()),
346                ],
347            ),
348            (
349                "a/*/c",
350                vec![
351                    Segment::ExactMatch("a".into()),
352                    Segment::Pattern("*".into()),
353                    Segment::ExactMatch("c".into()),
354                ],
355            ),
356            (
357                "a/b*/c",
358                vec![
359                    Segment::ExactMatch("a".into()),
360                    Segment::Pattern("b*".into()),
361                    Segment::ExactMatch("c".into()),
362                ],
363            ),
364            (
365                "a/b/**",
366                vec![
367                    Segment::ExactMatch("a".into()),
368                    Segment::ExactMatch("b".into()),
369                    Segment::Pattern("**".into()),
370                ],
371            ),
372            (
373                "core/session\\:id/foo",
374                vec![
375                    Segment::ExactMatch("core".into()),
376                    Segment::ExactMatch("session:id".into()),
377                    Segment::ExactMatch("foo".into()),
378                ],
379            ),
380            ("c", vec![Segment::ExactMatch("c".into())]),
381            ("<component_manager>", vec![Segment::ExactMatch("<component_manager>".into())]),
382            (
383                r#"a/*/b/**"#,
384                vec![
385                    Segment::ExactMatch("a".into()),
386                    Segment::Pattern("*".into()),
387                    Segment::ExactMatch("b".into()),
388                    Segment::Pattern("**".into()),
389                ],
390            ),
391        ];
392
393        for (test_string, expected_segments) in test_vector {
394            let selector =
395                component_selector::<winnow::error::ContextError>(RequireEscaped::COLONS)
396                    .parse(test_string)
397                    .unwrap();
398
399            assert_eq!(expected_segments, selector.segments);
400
401            // Component selectors can start with `/`
402            let test_moniker_string = format!("/{test_string}");
403            let selector =
404                component_selector::<winnow::error::ContextError>(RequireEscaped::COLONS)
405                    .parse(&test_moniker_string)
406                    .unwrap();
407            assert_eq!(expected_segments, selector.segments);
408
409            // Component selectors can start with `./`
410            let test_moniker_string = format!("./{test_string}");
411            let selector =
412                component_selector::<winnow::error::ContextError>(RequireEscaped::COLONS)
413                    .parse(&test_moniker_string)
414                    .unwrap();
415            assert_eq!(expected_segments, selector.segments);
416
417            // We can also accept component selectors without escaping
418            let test_moniker_string = test_string.replace("\\:", ":");
419            let selector =
420                component_selector::<winnow::error::ContextError>(RequireEscaped::empty())
421                    .parse(&test_moniker_string)
422                    .unwrap();
423            assert_eq!(expected_segments, selector.segments);
424        }
425    }
426
427    #[fuchsia::test]
428    fn missing_path_component_selector_test() {
429        let component_selector_string = "c";
430        let cs = component_selector::<winnow::error::ContextError>(RequireEscaped::COLONS)
431            .parse(component_selector_string)
432            .unwrap();
433
434        let mut path_vec = cs.segments;
435        assert_eq!(path_vec.pop(), Some(Segment::ExactMatch("c".into())));
436        assert!(path_vec.is_empty());
437    }
438
439    #[fuchsia::test]
440    fn errorful_component_selector_test() {
441        let test_vector: Vec<&str> = vec![
442            "",
443            "a\\",
444            r#"a/b***/c"#,
445            r#"a/***/c"#,
446            r#"a/**/c"#,
447            // NOTE: This used to be accepted but not anymore. Spaces shouldn't be a valid component
448            // selector character since it's not a valid moniker character.
449            " ",
450            // NOTE: The previous parser was accepting quotes in component selectors. However, by
451            // definition, a component moniker (both in v1 and v2) doesn't allow a `*` in its name.
452            r#"a/b\*/c"#,
453            r#"a/\*/c"#,
454            // Invalid characters
455            "a$c/d",
456        ];
457        for test_string in test_vector {
458            let component_selector_result =
459                consuming_component_selector::<VerboseError>(test_string, RequireEscaped::COLONS);
460            assert!(component_selector_result.is_err(), "expected '{test_string}' to fail");
461        }
462    }
463
464    #[fuchsia::test]
465    fn canonical_tree_selector_test() {
466        let test_vector = vec![
467            (
468                r#"[name="with internal ,"]b/c:d"#,
469                vec![Segment::ExactMatch("b".into()), Segment::ExactMatch("c".into())],
470                Some(Segment::ExactMatch("d".into())),
471                Some(vec![r#"with internal ,"#].into()),
472            ),
473            (
474                r#"[name="with internal \" escaped quote"]b/c:d"#,
475                vec![Segment::ExactMatch("b".into()), Segment::ExactMatch("c".into())],
476                Some(Segment::ExactMatch("d".into())),
477                Some(vec![r#"with internal " escaped quote"#].into()),
478            ),
479            (
480                r#"[name="with internal ] closing bracket"]b/c:d"#,
481                vec![Segment::ExactMatch("b".into()), Segment::ExactMatch("c".into())],
482                Some(Segment::ExactMatch("d".into())),
483                Some(vec!["with internal ] closing bracket"].into()),
484            ),
485            (
486                "[name=a]b/c:d",
487                vec![Segment::ExactMatch("b".into()), Segment::ExactMatch("c".into())],
488                Some(Segment::ExactMatch("d".into())),
489                Some(vec!["a"].into()),
490            ),
491            (
492                "[name=a:b:c:d]b/c:d",
493                vec![Segment::ExactMatch("b".into()), Segment::ExactMatch("c".into())],
494                Some(Segment::ExactMatch("d".into())),
495                Some(vec!["a:b:c:d"].into()),
496            ),
497            (
498                "[name=a,name=bb]b/c:d",
499                vec![Segment::ExactMatch("b".into()), Segment::ExactMatch("c".into())],
500                Some(Segment::ExactMatch("d".into())),
501                Some(vec!["a", "bb"].into()),
502            ),
503            (
504                "[...]b/c:d",
505                vec![Segment::ExactMatch("b".into()), Segment::ExactMatch("c".into())],
506                Some(Segment::ExactMatch("d".into())),
507                Some(TreeNames::All),
508            ),
509            (
510                "[name=a, name=bb]b/c:d",
511                vec![Segment::ExactMatch("b".into()), Segment::ExactMatch("c".into())],
512                Some(Segment::ExactMatch("d".into())),
513                Some(vec!["a", "bb"].into()),
514            ),
515            (
516                "[name=a, name=\"bb\"]b/c:d",
517                vec![Segment::ExactMatch("b".into()), Segment::ExactMatch("c".into())],
518                Some(Segment::ExactMatch("d".into())),
519                Some(vec!["a", "bb"].into()),
520            ),
521            (
522                r#"[name=a, name="a/\*:a"]b/c:d"#,
523                vec![Segment::ExactMatch("b".into()), Segment::ExactMatch("c".into())],
524                Some(Segment::ExactMatch("d".into())),
525                Some(vec!["a", "a/*:a"].into()),
526            ),
527            (
528                r#""a 1/b:d""#,
529                vec![Segment::ExactMatch("a 1".into()), Segment::ExactMatch("b".into())],
530                Some(Segment::ExactMatch("d".into())),
531                None,
532            ),
533            (
534                r#""a 1/b 2:d""#,
535                vec![Segment::ExactMatch("a 1".into()), Segment::ExactMatch("b 2".into())],
536                Some(Segment::ExactMatch("d".into())),
537                None,
538            ),
539            (
540                r#""a 1/b 2:d 3""#,
541                vec![Segment::ExactMatch("a 1".into()), Segment::ExactMatch("b 2".into())],
542                Some(Segment::ExactMatch("d 3".into())),
543                None,
544            ),
545            (
546                r#"a\ 1/b:d"#,
547                vec![Segment::ExactMatch("a 1".into()), Segment::ExactMatch("b".into())],
548                Some(Segment::ExactMatch("d".into())),
549                None,
550            ),
551            (
552                r#"a\ 1/b\ 2:d"#,
553                vec![Segment::ExactMatch("a 1".into()), Segment::ExactMatch("b 2".into())],
554                Some(Segment::ExactMatch("d".into())),
555                None,
556            ),
557            (
558                r#"a\ 1/b\ 2:d\ 3"#,
559                vec![Segment::ExactMatch("a 1".into()), Segment::ExactMatch("b 2".into())],
560                Some(Segment::ExactMatch("d 3".into())),
561                None,
562            ),
563            (
564                r#""a\ 1/b\ 2:d\ 3""#,
565                vec![Segment::ExactMatch("a 1".into()), Segment::ExactMatch("b 2".into())],
566                Some(Segment::ExactMatch("d 3".into())),
567                None,
568            ),
569            (
570                "a/b:c",
571                vec![Segment::ExactMatch("a".into()), Segment::ExactMatch("b".into())],
572                Some(Segment::ExactMatch("c".into())),
573                None,
574            ),
575            (
576                "a/*:c",
577                vec![Segment::ExactMatch("a".into()), Segment::Pattern("*".into())],
578                Some(Segment::ExactMatch("c".into())),
579                None,
580            ),
581            (
582                "a/b:*",
583                vec![Segment::ExactMatch("a".into()), Segment::ExactMatch("b".into())],
584                Some(Segment::Pattern("*".into())),
585                None,
586            ),
587            (
588                "a/b",
589                vec![Segment::ExactMatch("a".into()), Segment::ExactMatch("b".into())],
590                None,
591                None,
592            ),
593            (
594                r#"a/b\:\*c"#,
595                vec![Segment::ExactMatch("a".into()), Segment::ExactMatch("b:*c".into())],
596                None,
597                None,
598            ),
599        ];
600
601        for (string, expected_path, expected_property, expected_tree_name) in test_vector {
602            let tree_selector = standalone_tree_selector::<VerboseError>(string)
603                .unwrap_or_else(|e| panic!("input: |{string}| error: {e}"));
604            assert_eq!(
605                tree_selector,
606                TreeSelector {
607                    node: expected_path,
608                    property: expected_property,
609                    tree_names: expected_tree_name,
610                },
611                "input: |{string}|",
612            );
613        }
614    }
615
616    #[fuchsia::test]
617    fn errorful_tree_selector_test() {
618        let test_vector = vec![
619            // Not allowed due to empty property selector.
620            "a/b:",
621            // Not allowed due to glob property selector.
622            "a/b:**",
623            // String literals can't have globs.
624            r#"a/b**:c"#,
625            // Property selector string literals cant have globs.
626            r#"a/b:c**"#,
627            "a/b:**",
628            // Node path cant have globs.
629            "a/**:c",
630            // Node path can't be empty
631            ":c",
632            // Spaces aren't accepted when parsing with allow_spaces=false.
633            "a b:c",
634            "a*b:\tc",
635        ];
636        for string in test_vector {
637            // prepend a placeholder component selector so that we exercise the validation code.
638            let test_selector = format!("a:{string}");
639            assert!(
640                selector::<VerboseError>(&test_selector).is_err(),
641                "{test_selector} should fail"
642            );
643        }
644    }
645
646    #[fuchsia::test]
647    fn tree_selector_with_spaces() {
648        let with_spaces = vec![
649            (
650                r#"a\ b:c"#,
651                vec![Segment::ExactMatch("a b".into())],
652                Some(Segment::ExactMatch("c".into())),
653            ),
654            (
655                r#"ab/\ d:c\ "#,
656                vec![Segment::ExactMatch("ab".into()), Segment::ExactMatch(" d".into())],
657                Some(Segment::ExactMatch("c ".into())),
658            ),
659            (
660                "a\\\t*b:c",
661                vec![Segment::Pattern("a\t*b".into())],
662                Some(Segment::ExactMatch("c".into())),
663            ),
664            (
665                r#"a\ "x":c"#,
666                vec![Segment::ExactMatch(r#"a "x""#.into())],
667                Some(Segment::ExactMatch("c".into())),
668            ),
669        ];
670        for (string, node, property) in with_spaces {
671            let ts = (tree_selector::<()>(RequireEscaped::WHITESPACE), eof)
672                .map(|(r, _)| r)
673                .parse(string)
674                .unwrap();
675            assert_eq!(ts, TreeSelector { node, property, tree_names: None });
676        }
677
678        // Un-escaped quotes aren't accepted when parsing with spaces.
679        assert!(standalone_tree_selector::<VerboseError>(r#"a/b:"xc"/d"#).is_err());
680    }
681
682    #[fuchsia::test]
683    fn parse_full_selector() {
684        assert_eq!(
685            selector::<VerboseError>("core/**:some-node/he*re:prop").unwrap(),
686            Selector {
687                component: ComponentSelector {
688                    segments: vec![
689                        Segment::ExactMatch("core".into()),
690                        Segment::Pattern("**".into()),
691                    ],
692                },
693                tree: TreeSelector {
694                    node: vec![
695                        Segment::ExactMatch("some-node".into()),
696                        Segment::Pattern("he*re".into()),
697                    ],
698                    property: Some(Segment::ExactMatch("prop".into())),
699                    tree_names: None,
700                },
701            }
702        );
703
704        // Ignores whitespace.
705        assert_eq!(
706            selector::<VerboseError>("   foo:bar  ").unwrap(),
707            Selector {
708                component: ComponentSelector { segments: vec![Segment::ExactMatch("foo".into())] },
709                tree: TreeSelector {
710                    node: vec![Segment::ExactMatch("bar".into())],
711                    property: None,
712                    tree_names: None
713                },
714            }
715        );
716
717        // parses tree names
718        assert_eq!(
719            selector::<VerboseError>(r#"core/**:[name=foo, name="bar\*"]some-node/he*re:prop"#)
720                .unwrap(),
721            Selector {
722                component: ComponentSelector {
723                    segments: vec![
724                        Segment::ExactMatch("core".into()),
725                        Segment::Pattern("**".into()),
726                    ],
727                },
728                tree: TreeSelector {
729                    node: vec![
730                        Segment::ExactMatch("some-node".into()),
731                        Segment::Pattern("he*re".into()),
732                    ],
733                    property: Some(Segment::ExactMatch("prop".into())),
734                    tree_names: Some(vec!["foo", r"bar*"].into()),
735                },
736            }
737        );
738
739        assert_eq!(
740            selector::<VerboseError>(r#"core/**:[name="foo:bar"]some-node/he*re:prop"#).unwrap(),
741            Selector {
742                component: ComponentSelector {
743                    segments: vec![
744                        Segment::ExactMatch("core".into()),
745                        Segment::Pattern("**".into()),
746                    ],
747                },
748                tree: TreeSelector {
749                    node: vec![
750                        Segment::ExactMatch("some-node".into()),
751                        Segment::Pattern("he*re".into()),
752                    ],
753                    property: Some(Segment::ExactMatch("prop".into())),
754                    tree_names: Some(vec!["foo:bar"].into()),
755                },
756            }
757        );
758
759        assert_eq!(
760            selector::<VerboseError>(r#"core/**:[name="name=bar"]some-node/he*re:prop"#).unwrap(),
761            Selector {
762                component: ComponentSelector {
763                    segments: vec![
764                        Segment::ExactMatch("core".into()),
765                        Segment::Pattern("**".into()),
766                    ],
767                },
768                tree: TreeSelector {
769                    node: vec![
770                        Segment::ExactMatch("some-node".into()),
771                        Segment::Pattern("he*re".into()),
772                    ],
773                    property: Some(Segment::ExactMatch("prop".into())),
774                    tree_names: Some(vec!["name=bar"].into()),
775                },
776            }
777        );
778
779        assert_eq!(
780            selector::<VerboseError>(r#"core/**:[name=foo-bar_baz]some-node/he*re:prop"#).unwrap(),
781            Selector {
782                component: ComponentSelector {
783                    segments: vec![
784                        Segment::ExactMatch("core".into()),
785                        Segment::Pattern("**".into()),
786                    ],
787                },
788                tree: TreeSelector {
789                    node: vec![
790                        Segment::ExactMatch("some-node".into()),
791                        Segment::Pattern("he*re".into()),
792                    ],
793                    property: Some(Segment::ExactMatch("prop".into())),
794                    tree_names: Some(vec!["foo-bar_baz"].into()),
795                },
796            }
797        );
798
799        // At least one filter is required when `where` is provided.
800        assert!(selector::<VerboseError>("foo:bar where").is_err());
801    }
802
803    #[fuchsia::test]
804    fn assert_no_trailing_backward_slash() {
805        assert!(selector::<VerboseError>(r#"foo:bar:baz\"#).is_err());
806    }
807
808    #[fuchsia::test]
809    fn parse_full_selector_with_spaces() {
810        let expected_regardless_of_escape_or_quote = Selector {
811            component: ComponentSelector {
812                segments: vec![
813                    Segment::ExactMatch("core".into()),
814                    Segment::ExactMatch("foo".into()),
815                ],
816            },
817            tree: TreeSelector {
818                node: vec![Segment::ExactMatch("some node".into()), Segment::Pattern("*".into())],
819                property: Some(Segment::ExactMatch("prop".into())),
820                tree_names: None,
821            },
822        };
823        assert_eq!(
824            selector::<VerboseError>(r#"core/foo:some\ node/*:prop"#).unwrap(),
825            expected_regardless_of_escape_or_quote,
826        );
827
828        assert_eq!(
829            selector::<VerboseError>(r#""core/foo:some node/*:prop""#).unwrap(),
830            expected_regardless_of_escape_or_quote,
831        );
832    }
833
834    #[fuchsia::test]
835    fn test_extract_from_quotes() {
836        let test_cases = [
837            ("foo", "foo"),
838            (r#""foo""#, "foo"),
839            (r#""foo\"bar""#, r#"foo\"bar"#),
840            (r#""bar\*""#, r#"bar\*"#),
841        ];
842
843        for (case_number, (input, expected_extracted)) in test_cases.into_iter().enumerate() {
844            let actual_extracted = extract_from_quotes(input);
845            assert_eq!(
846                expected_extracted, actual_extracted,
847                "failed test case {case_number} on name_list: |{input}|",
848            );
849        }
850    }
851
852    #[fuchsia::test]
853    fn extract_name_list() {
854        let test_cases = [
855            ("root:prop", ("root:prop", None)),
856            ("[name=foo]root:prop", ("root:prop", Some(TreeNames::from(vec!["foo"])))),
857            (
858                r#"[name="with internal ,"]root"#,
859                ("root", Some(TreeNames::from(vec!["with internal ,"]))),
860            ),
861            (r#"[name="f[o]o"]root:prop"#, ("root:prop", Some(TreeNames::from(vec!["f[o]o"])))),
862            (
863                r#"[name="fo]o", name="[bar,baz"]root:prop"#,
864                ("root:prop", Some(TreeNames::from(vec!["fo]o", "[bar,baz"]))),
865            ),
866            (r#"ab/\ d:c\ "#, (r#"ab/\ d:c\ "#, None)),
867        ];
868
869        for (case_number, (input, (expected_residue, expected_name_list))) in
870            test_cases.into_iter().enumerate()
871        {
872            let mut i = input;
873            let actual_name_list =
874                conjoined_tree_names::<winnow::error::ContextError>().parse_next(&mut i).unwrap();
875            let actual_residue = i;
876            assert_eq!(
877                expected_residue, actual_residue,
878                "failed test case {case_number} on residue: |{input}|",
879            );
880            assert_eq!(
881                expected_name_list, actual_name_list,
882                "failed test case {case_number} on name_list: |{input}|",
883            );
884        }
885    }
886
887    #[fuchsia::test]
888    fn comma_separated_name_lists() {
889        let test_cases = [
890            (r#"name=foo, name=bar"#, vec!["foo", "bar"]),
891            (r#"name="with internal ,""#, vec!["with internal ,"]),
892            (r#"name="foo", name=bar"#, vec!["foo", "bar"]),
893            (r#"name="foo,bar", name=baz"#, vec!["foo,bar", "baz"]),
894            (r#"name="foo,bar", name="baz""#, vec!["foo,bar", "baz"]),
895            (r#"name="foo ,bar", name=baz"#, vec!["foo ,bar", "baz"]),
896            (r#"name="foo\",bar", name="baz""#, vec![r#"foo\",bar"#, "baz"]),
897            (r#"name="foo\" ,bar", name="baz""#, vec![r#"foo\" ,bar"#, "baz"]),
898            (r#"name="foo,bar", name=" baz  ""#, vec!["foo,bar", " baz  "]),
899            (r#"name="foo\", bar,", name=",,baz,,,""#, vec![r#"foo\", bar,"#, ",,baz,,,"]),
900        ];
901
902        for (case_number, (input, expected)) in test_cases.into_iter().enumerate() {
903            let mut i = input;
904            let actual: Vec<&str> =
905                separated(1.., tree_name_item::<winnow::error::ContextError>, spaced(","))
906                    .parse_next(&mut i)
907                    .unwrap();
908            assert_eq!(expected, actual, "failed test case {case_number} on list: |{input}|",);
909        }
910    }
911}