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