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.ends_with('"') && input.len() >= 2 {
76 &input[1..input.len() - 1]
77 } else {
78 input
79 }
80}
81
82fn 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
124fn 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
174fn 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
203fn 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
214pub struct FastError;
217
218pub struct VerboseError;
221
222mod private {
223 pub trait Sealed {}
224
225 impl Sealed for super::FastError {}
226 impl Sealed for super::VerboseError {}
227}
228
229pub 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
253pub 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
269pub 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
291pub 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
312pub 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 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 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 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 " ",
454 r#"a/b\*/c"#,
457 r#"a/\*/c"#,
458 "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 "a/b:",
625 "a/b:**",
627 r#"a/b**:c"#,
629 r#"a/b:c**"#,
631 "a/b:**",
632 "a/**:c",
634 ":c",
636 "a b:c",
638 "a*b:\tc",
639 ];
640 for string in test_vector {
641 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 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 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 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 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}