1use 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
27fn 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
35fn 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
78fn 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
120fn 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
170fn 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
199fn 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
210pub struct FastError;
213
214pub struct VerboseError;
217
218mod private {
219 pub trait Sealed {}
220
221 impl Sealed for super::FastError {}
222 impl Sealed for super::VerboseError {}
223}
224
225pub 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
249pub 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
265pub 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
287pub 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
308pub 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 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 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 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 " ",
450 r#"a/b\*/c"#,
453 r#"a/\*/c"#,
454 "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 "a/b:",
621 "a/b:**",
623 r#"a/b**:c"#,
625 r#"a/b:c**"#,
627 "a/b:**",
628 "a/**:c",
630 ":c",
632 "a b:c",
634 "a*b:\tc",
635 ];
636 for string in test_vector {
637 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 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 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 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 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}