1use crate::error::ParseError;
6use crate::ir::*;
7use crate::validate::{ValidateComponentSelectorExt, ValidateExt, ValidateTreeSelectorExt};
8use bitflags::bitflags;
9use nom::branch::alt;
10use nom::bytes::complete::{escaped, is_not, tag, take_while};
11use nom::character::complete::{alphanumeric1, multispace0, none_of, one_of};
12use nom::combinator::{all_consuming, complete, cond, map, opt, peek, recognize, verify};
13use nom::error::{ErrorKind, ParseError as NomParseError};
14use nom::multi::{many1_count, separated_list1};
15use nom::sequence::{pair, preceded, separated_pair};
16use nom::{IResult, Parser};
17
18const ALL_TREE_NAMES_SELECTED_SYMBOL: &str = "...";
19
20bitflags! {
21 pub struct RequireEscaped: u8 {
22 const NONE = 0;
23 const COLONS = 1;
24 const WHITESPACE = 2;
25 }
26}
27
28fn whitespace0<'a, E>(input: &'a str) -> IResult<&'a str, &'a str, E>
30where
31 E: NomParseError<&'a str>,
32{
33 take_while(move |c| c == ' ' || c == '\t').parse(input)
34}
35
36fn spaced<'a, E, F, O>(parser: F) -> impl Parser<&'a str, Output = O, Error = E>
38where
39 F: Parser<&'a str, Output = O, Error = E>,
40 E: NomParseError<&'a str>,
41{
42 preceded(whitespace0, parser)
43}
44
45fn extract_conjoined_names<'a, E>(input: &'a str) -> IResult<&'a str, Option<&'a str>, E>
46where
47 E: NomParseError<&'a str>,
48{
49 let split = parse_quote_sensitive_separator(input, ']');
50 if split.len() == 1 { Ok((split[0], None)) } else { Ok((split[1], Some(&split[0][1..]))) }
51}
52
53fn extract_from_quotes(input: &str) -> &str {
54 if input.starts_with('"') && input.len() > 1 { &input[1..input.len() - 1] } else { input }
55}
56
57fn parse_quote_sensitive_separator(input: &str, sep: char) -> Vec<&str> {
58 let mut inside_quotes = false;
59 let mut last_split_index = 0;
60 let mut chars = input.chars().enumerate().peekable();
61 let mut result = vec![];
62
63 let mut push_substr = |curr_index| {
64 result.push(input[last_split_index..curr_index].trim_start());
65 last_split_index = curr_index + 1;
66 };
67
68 while let (Some((_, c1)), Some((idx2, c2))) = (chars.next(), chars.peek()) {
69 match (c1, c2) {
70 ('\\', '"') => {
71 chars.next();
72 }
73 ('"', c) if *c == sep && inside_quotes => {
74 inside_quotes = !inside_quotes;
75 push_substr(*idx2);
76 }
77 ('"', _) => {
78 inside_quotes = !inside_quotes;
79 }
80 (_, c) if *c == sep && !inside_quotes => {
81 push_substr(*idx2);
82 }
83 _ => {}
84 }
85 }
86
87 if last_split_index < input.len() {
89 let maybe_last_segment = input[last_split_index..].trim_start();
90 if !maybe_last_segment.is_empty() {
91 result.push(maybe_last_segment);
92 }
93 }
94
95 result
96}
97
98fn tree_selector<'a, E>(
100 required_escapes: RequireEscaped,
103) -> impl Parser<&'a str, Output = TreeSelector<'a>, Error = E>
104where
105 E: NomParseError<&'a str>,
106{
107 move |input| {
108 let mut esc = if required_escapes.intersects(RequireEscaped::WHITESPACE) {
109 escaped(none_of(":/\\ \t\n"), '\\', one_of("* \t/:\\"))
110 } else {
111 escaped(none_of(":/\\\t\n"), '\\', one_of("* \t/:\\"))
112 };
113
114 let (rest, unparsed_name_list) =
115 extract_conjoined_names::<E>(input).unwrap_or((input, None));
116
117 let tree_names = if unparsed_name_list == Some(ALL_TREE_NAMES_SELECTED_SYMBOL) {
118 Some(TreeNames::All)
119 } else {
120 let mut name_escapes = escaped(none_of(r#"*\"#), '\\', one_of(r#""*"#));
125 unparsed_name_list
126 .map(|names: &str| {
127 parse_quote_sensitive_separator(names, ',')
128 .into_iter()
129 .map(|name| {
130 let (_, (_, value)) =
131 spaced(separated_pair(tag("name"), tag("="), &mut name_escapes))
132 .parse(name)?;
133 Ok(extract_from_quotes(value))
134 })
135 .collect::<Result<Vec<_>, _>>()
136 })
137 .transpose()?
138 .map(|value| value.into())
139 };
140
141 let (rest, node_segments) = separated_list1(tag("/"), &mut esc).parse(rest)?;
142 let (rest, property_segment) = if peek(tag::<_, _, E>(":")).parse(rest).is_ok() {
143 let (rest, _) = tag(":").parse(rest)?;
144 let (rest, property) = verify(esc, |value: &str| !value.is_empty()).parse(rest)?;
145 (rest, Some(property))
146 } else {
147 (rest, None)
148 };
149 Ok((
150 rest,
151 TreeSelector {
152 node: node_segments.into_iter().map(|value| value.into()).collect(),
153 property: property_segment.map(|value| value.into()),
154 tree_names,
155 },
156 ))
157 }
158}
159
160fn component_selector<'a, E>(
163 required_escapes: RequireEscaped,
164) -> impl Parser<&'a str, Output = ComponentSelector<'a>, Error = E>
165where
166 E: NomParseError<&'a str>,
167{
168 fn inner_component_selector<'a, F, E>(
169 segment: F,
170 input: &'a str,
171 ) -> IResult<&'a str, ComponentSelector<'a>, E>
172 where
173 F: Parser<&'a str, Output = &'a str, Error = E>,
174 E: NomParseError<&'a str>,
175 {
176 let (rest, segments) =
178 preceded(opt(alt((tag("./"), tag("/")))), separated_list1(tag("/"), segment))
179 .parse(input)?;
180 Ok((
181 rest,
182 ComponentSelector { segments: segments.into_iter().map(Segment::from).collect() },
183 ))
184 }
185
186 move |input| {
187 if required_escapes.intersects(RequireEscaped::COLONS) {
188 inner_component_selector(
189 recognize(escaped(
190 alt((
191 alphanumeric1,
192 tag("*"),
193 tag("."),
194 tag("-"),
195 tag("_"),
196 tag(">"),
197 tag("<"),
198 )),
199 '\\',
200 tag(":"),
201 )),
202 input,
203 )
204 } else {
205 inner_component_selector(
206 recognize(many1_count(alt((
207 alphanumeric1,
208 tag("*"),
209 tag("."),
210 tag("-"),
211 tag("_"),
212 tag(">"),
213 tag("<"),
214 tag(":"),
215 )))),
216 input,
217 )
218 }
219 }
220}
221
222fn comment<'a, E>(input: &'a str) -> IResult<&'a str, &'a str, E>
224where
225 E: NomParseError<&'a str>,
226{
227 let (rest, comment) = spaced(preceded(tag("//"), is_not("\n\r"))).parse(input)?;
228 if !rest.is_empty() {
229 let (rest, _) = one_of("\n\r").parse(rest)?; return Ok((rest, comment));
231 }
232 Ok((rest, comment))
233}
234
235fn core_selector<'a, E>(
239 input: &'a str,
240) -> IResult<&'a str, (ComponentSelector<'a>, TreeSelector<'a>), E>
241where
242 E: NomParseError<&'a str>,
243{
244 let required_tree_escape =
245 if input.starts_with('"') { RequireEscaped::empty() } else { RequireEscaped::WHITESPACE };
246 let (rest, (component, _, tree)) =
247 (component_selector(RequireEscaped::COLONS), tag(":"), tree_selector(required_tree_escape))
248 .parse(extract_from_quotes(input))?;
249 Ok((rest, (component, tree)))
250}
251
252fn do_parse_selector<'a, E>(
254 allow_inline_comment: bool,
255) -> impl Parser<&'a str, Output = Selector<'a>, Error = E>
256where
257 E: NomParseError<&'a str>,
258{
259 map(
260 (spaced(core_selector), cond(allow_inline_comment, opt(comment)), multispace0),
261 move |((component, tree), _, _)| Selector { component, tree },
262 )
263}
264
265pub struct FastError;
268
269pub struct VerboseError;
272
273mod private {
274 pub trait Sealed {}
275
276 impl Sealed for super::FastError {}
277 impl Sealed for super::VerboseError {}
278}
279
280pub trait ParsingError<'a>: private::Sealed {
282 type Internal: NomParseError<&'a str>;
283
284 fn to_error(input: &str, err: Self::Internal) -> ParseError;
285}
286
287impl<'a> ParsingError<'a> for FastError {
288 type Internal = (&'a str, ErrorKind);
289
290 fn to_error(_: &str, (part, error_kind): Self::Internal) -> ParseError {
291 ParseError::Fast(part.to_owned(), error_kind)
292 }
293}
294
295impl<'a> ParsingError<'a> for VerboseError {
296 type Internal = nom_language::error::VerboseError<&'a str>;
297
298 fn to_error(input: &str, err: Self::Internal) -> ParseError {
299 ParseError::Verbose(nom_language::error::convert_error(input, err))
300 }
301}
302
303pub fn selector<'a, E>(input: &'a str) -> Result<Selector<'a>, ParseError>
305where
306 E: ParsingError<'a>,
307{
308 let result = complete(all_consuming(do_parse_selector::<<E as ParsingError<'_>>::Internal>(
309 false,
310 )))
311 .parse(input);
312 match result {
313 Ok((_, selector)) => {
314 selector.validate()?;
315 Ok(selector)
316 }
317 Err(nom::Err::Error(e) | nom::Err::Failure(e)) => Err(E::to_error(input, e)),
318 _ => unreachable!("through the complete combinator we get rid of Incomplete"),
319 }
320}
321
322pub fn standalone_tree_selector<'a, E>(input: &'a str) -> Result<TreeSelector<'a>, ParseError>
325where
326 E: ParsingError<'a>,
327{
328 let required_tree_escape =
329 if input.starts_with('"') { RequireEscaped::empty() } else { RequireEscaped::WHITESPACE };
330 let result = nom::combinator::all_consuming(pair(
331 spaced(tree_selector(required_tree_escape)),
332 multispace0,
333 ))
334 .parse(extract_from_quotes(input));
335 match result {
336 Ok((_, (tree_selector, _))) => {
337 tree_selector.validate()?;
338 Ok(tree_selector)
339 }
340 Err(nom::Err::Error(e) | nom::Err::Failure(e)) => Err(E::to_error(input, e)),
341 _ => unreachable!("through the complete combinator we get rid of Incomplete"),
342 }
343}
344
345pub fn consuming_component_selector<'a, E>(
348 input: &'a str,
349 required_escapes: RequireEscaped,
350) -> Result<ComponentSelector<'a>, ParseError>
351where
352 E: ParsingError<'a>,
353{
354 let result = nom::combinator::all_consuming(pair(
355 spaced(component_selector(required_escapes)),
356 multispace0,
357 ))
358 .parse(input);
359 match result {
360 Ok((_, (component_selector, _))) => {
361 component_selector.validate()?;
362 Ok(component_selector)
363 }
364 Err(nom::Err::Error(e) | nom::Err::Failure(e)) => Err(E::to_error(input, e)),
365 _ => unreachable!("through the complete combinator we get rid of Incomplete"),
366 }
367}
368
369pub fn selector_or_comment<'a, E>(input: &'a str) -> Result<Option<Selector<'a>>, ParseError>
371where
372 E: ParsingError<'a>,
373{
374 let result = complete(all_consuming(alt((
375 map(comment, |_| None),
376 map(
377 do_parse_selector::<<E as ParsingError<'_>>::Internal>(
378 true,
379 ),
380 Some,
381 ),
382 ))))
383 .parse(input);
384 match result {
385 Ok((_, maybe_selector)) => match maybe_selector {
386 None => Ok(None),
387 Some(selector) => {
388 selector.validate()?;
389 Ok(Some(selector))
390 }
391 },
392 Err(nom::Err::Error(e) | nom::Err::Failure(e)) => Err(E::to_error(input, e)),
393 _ => unreachable!("through the complete combinator we get rid of Incomplete"),
394 }
395}
396
397#[cfg(test)]
398mod tests {
399 use super::*;
400
401 #[fuchsia::test]
402 fn canonical_component_selector_test() {
403 let test_vector = vec![
404 (
405 "a/b/c",
406 vec![
407 Segment::ExactMatch("a".into()),
408 Segment::ExactMatch("b".into()),
409 Segment::ExactMatch("c".into()),
410 ],
411 ),
412 (
413 "a/*/c",
414 vec![
415 Segment::ExactMatch("a".into()),
416 Segment::Pattern("*".into()),
417 Segment::ExactMatch("c".into()),
418 ],
419 ),
420 (
421 "a/b*/c",
422 vec![
423 Segment::ExactMatch("a".into()),
424 Segment::Pattern("b*".into()),
425 Segment::ExactMatch("c".into()),
426 ],
427 ),
428 (
429 "a/b/**",
430 vec![
431 Segment::ExactMatch("a".into()),
432 Segment::ExactMatch("b".into()),
433 Segment::Pattern("**".into()),
434 ],
435 ),
436 (
437 "core/session\\:id/foo",
438 vec![
439 Segment::ExactMatch("core".into()),
440 Segment::ExactMatch("session:id".into()),
441 Segment::ExactMatch("foo".into()),
442 ],
443 ),
444 ("c", vec![Segment::ExactMatch("c".into())]),
445 ("<component_manager>", vec![Segment::ExactMatch("<component_manager>".into())]),
446 (
447 r#"a/*/b/**"#,
448 vec![
449 Segment::ExactMatch("a".into()),
450 Segment::Pattern("*".into()),
451 Segment::ExactMatch("b".into()),
452 Segment::Pattern("**".into()),
453 ],
454 ),
455 ];
456
457 for (test_string, expected_segments) in test_vector {
458 let (_, selector) = component_selector::<nom_language::error::VerboseError<&str>>(
459 RequireEscaped::COLONS,
460 )
461 .parse(test_string)
462 .unwrap();
463 assert_eq!(
464 expected_segments, selector.segments,
465 "For '{test_string}', got: {selector:?}",
466 );
467
468 let test_moniker_string = format!("/{test_string}");
470 let (_, selector) = component_selector::<nom_language::error::VerboseError<&str>>(
471 RequireEscaped::COLONS,
472 )
473 .parse(&test_moniker_string)
474 .unwrap();
475 assert_eq!(
476 expected_segments, selector.segments,
477 "For '{test_moniker_string}', got: {selector:?}",
478 );
479
480 let test_moniker_string = format!("./{test_string}");
482 let (_, selector) = component_selector::<nom_language::error::VerboseError<&str>>(
483 RequireEscaped::COLONS,
484 )
485 .parse(&test_moniker_string)
486 .unwrap();
487 assert_eq!(
488 expected_segments, selector.segments,
489 "For '{test_moniker_string}', got: {selector:?}",
490 );
491
492 let test_moniker_string = test_string.replace("\\:", ":");
494 let (_, selector) = component_selector::<nom_language::error::VerboseError<&str>>(
495 RequireEscaped::empty(),
496 )
497 .parse(&test_moniker_string)
498 .unwrap();
499 assert_eq!(
500 expected_segments, selector.segments,
501 "For '{test_moniker_string}', got: {selector:?}",
502 );
503 }
504 }
505
506 #[fuchsia::test]
507 fn missing_path_component_selector_test() {
508 let component_selector_string = "c";
509 let (_, component_selector) =
510 component_selector::<nom_language::error::VerboseError<&str>>(RequireEscaped::COLONS)
511 .parse(component_selector_string)
512 .unwrap();
513 let mut path_vec = component_selector.segments;
514 assert_eq!(path_vec.pop(), Some(Segment::ExactMatch("c".into())));
515 assert!(path_vec.is_empty());
516 }
517
518 #[fuchsia::test]
519 fn errorful_component_selector_test() {
520 let test_vector: Vec<&str> = vec![
521 "",
522 "a\\",
523 r#"a/b***/c"#,
524 r#"a/***/c"#,
525 r#"a/**/c"#,
526 " ",
529 r#"a/b\*/c"#,
532 r#"a/\*/c"#,
533 "a$c/d",
535 ];
536 for test_string in test_vector {
537 let component_selector_result =
538 consuming_component_selector::<VerboseError>(test_string, RequireEscaped::COLONS);
539 assert!(component_selector_result.is_err(), "expected '{test_string}' to fail");
540 }
541 }
542
543 #[fuchsia::test]
544 fn canonical_tree_selector_test() {
545 let test_vector = vec![
546 (
547 r#"[name="with internal ,"]b/c:d"#,
548 vec![Segment::ExactMatch("b".into()), Segment::ExactMatch("c".into())],
549 Some(Segment::ExactMatch("d".into())),
550 Some(vec![r#"with internal ,"#].into()),
551 ),
552 (
553 r#"[name="with internal \" escaped quote"]b/c:d"#,
554 vec![Segment::ExactMatch("b".into()), Segment::ExactMatch("c".into())],
555 Some(Segment::ExactMatch("d".into())),
556 Some(vec![r#"with internal " escaped quote"#].into()),
557 ),
558 (
559 r#"[name="with internal ] closing bracket"]b/c:d"#,
560 vec![Segment::ExactMatch("b".into()), Segment::ExactMatch("c".into())],
561 Some(Segment::ExactMatch("d".into())),
562 Some(vec!["with internal ] closing bracket"].into()),
563 ),
564 (
565 "[name=a]b/c:d",
566 vec![Segment::ExactMatch("b".into()), Segment::ExactMatch("c".into())],
567 Some(Segment::ExactMatch("d".into())),
568 Some(vec!["a"].into()),
569 ),
570 (
571 "[name=a:b:c:d]b/c:d",
572 vec![Segment::ExactMatch("b".into()), Segment::ExactMatch("c".into())],
573 Some(Segment::ExactMatch("d".into())),
574 Some(vec!["a:b:c:d"].into()),
575 ),
576 (
577 "[name=a,name=bb]b/c:d",
578 vec![Segment::ExactMatch("b".into()), Segment::ExactMatch("c".into())],
579 Some(Segment::ExactMatch("d".into())),
580 Some(vec!["a", "bb"].into()),
581 ),
582 (
583 "[...]b/c:d",
584 vec![Segment::ExactMatch("b".into()), Segment::ExactMatch("c".into())],
585 Some(Segment::ExactMatch("d".into())),
586 Some(TreeNames::All),
587 ),
588 (
589 "[name=a, name=bb]b/c:d",
590 vec![Segment::ExactMatch("b".into()), Segment::ExactMatch("c".into())],
591 Some(Segment::ExactMatch("d".into())),
592 Some(vec!["a", "bb"].into()),
593 ),
594 (
595 "[name=a, name=\"bb\"]b/c:d",
596 vec![Segment::ExactMatch("b".into()), Segment::ExactMatch("c".into())],
597 Some(Segment::ExactMatch("d".into())),
598 Some(vec!["a", "bb"].into()),
599 ),
600 (
601 r#"[name=a, name="a/\*:a"]b/c:d"#,
602 vec![Segment::ExactMatch("b".into()), Segment::ExactMatch("c".into())],
603 Some(Segment::ExactMatch("d".into())),
604 Some(vec!["a", "a/*:a"].into()),
605 ),
606 (
607 r#""a 1/b:d""#,
608 vec![Segment::ExactMatch("a 1".into()), Segment::ExactMatch("b".into())],
609 Some(Segment::ExactMatch("d".into())),
610 None,
611 ),
612 (
613 r#""a 1/b 2:d""#,
614 vec![Segment::ExactMatch("a 1".into()), Segment::ExactMatch("b 2".into())],
615 Some(Segment::ExactMatch("d".into())),
616 None,
617 ),
618 (
619 r#""a 1/b 2:d 3""#,
620 vec![Segment::ExactMatch("a 1".into()), Segment::ExactMatch("b 2".into())],
621 Some(Segment::ExactMatch("d 3".into())),
622 None,
623 ),
624 (
625 r#"a\ 1/b:d"#,
626 vec![Segment::ExactMatch("a 1".into()), Segment::ExactMatch("b".into())],
627 Some(Segment::ExactMatch("d".into())),
628 None,
629 ),
630 (
631 r#"a\ 1/b\ 2:d"#,
632 vec![Segment::ExactMatch("a 1".into()), Segment::ExactMatch("b 2".into())],
633 Some(Segment::ExactMatch("d".into())),
634 None,
635 ),
636 (
637 r#"a\ 1/b\ 2:d\ 3"#,
638 vec![Segment::ExactMatch("a 1".into()), Segment::ExactMatch("b 2".into())],
639 Some(Segment::ExactMatch("d 3".into())),
640 None,
641 ),
642 (
643 r#""a\ 1/b\ 2:d\ 3""#,
644 vec![Segment::ExactMatch("a 1".into()), Segment::ExactMatch("b 2".into())],
645 Some(Segment::ExactMatch("d 3".into())),
646 None,
647 ),
648 (
649 "a/b:c",
650 vec![Segment::ExactMatch("a".into()), Segment::ExactMatch("b".into())],
651 Some(Segment::ExactMatch("c".into())),
652 None,
653 ),
654 (
655 "a/*:c",
656 vec![Segment::ExactMatch("a".into()), Segment::Pattern("*".into())],
657 Some(Segment::ExactMatch("c".into())),
658 None,
659 ),
660 (
661 "a/b:*",
662 vec![Segment::ExactMatch("a".into()), Segment::ExactMatch("b".into())],
663 Some(Segment::Pattern("*".into())),
664 None,
665 ),
666 (
667 "a/b",
668 vec![Segment::ExactMatch("a".into()), Segment::ExactMatch("b".into())],
669 None,
670 None,
671 ),
672 (
673 r#"a/b\:\*c"#,
674 vec![Segment::ExactMatch("a".into()), Segment::ExactMatch("b:*c".into())],
675 None,
676 None,
677 ),
678 ];
679
680 for (string, expected_path, expected_property, expected_tree_name) in test_vector {
681 let tree_selector = standalone_tree_selector::<VerboseError>(string)
682 .unwrap_or_else(|e| panic!("input: |{string}| error: {e}"));
683 assert_eq!(
684 tree_selector,
685 TreeSelector {
686 node: expected_path,
687 property: expected_property,
688 tree_names: expected_tree_name,
689 },
690 "input: |{string}|",
691 );
692 }
693 }
694
695 #[fuchsia::test]
696 fn errorful_tree_selector_test() {
697 let test_vector = vec![
698 "a/b:",
700 "a/b:**",
702 r#"a/b**:c"#,
704 r#"a/b:c**"#,
706 "a/b:**",
707 "a/**:c",
709 ":c",
711 "a b:c",
713 "a*b:\tc",
714 ];
715 for string in test_vector {
716 let test_selector = format!("a:{string}");
718 assert!(
719 selector::<VerboseError>(&test_selector).is_err(),
720 "{test_selector} should fail"
721 );
722 }
723 }
724
725 #[fuchsia::test]
726 fn tree_selector_with_spaces() {
727 let with_spaces = vec![
728 (
729 r#"a\ b:c"#,
730 vec![Segment::ExactMatch("a b".into())],
731 Some(Segment::ExactMatch("c".into())),
732 ),
733 (
734 r#"ab/\ d:c\ "#,
735 vec![Segment::ExactMatch("ab".into()), Segment::ExactMatch(" d".into())],
736 Some(Segment::ExactMatch("c ".into())),
737 ),
738 (
739 "a\\\t*b:c",
740 vec![Segment::Pattern("a\t*b".into())],
741 Some(Segment::ExactMatch("c".into())),
742 ),
743 (
744 r#"a\ "x":c"#,
745 vec![Segment::ExactMatch(r#"a "x""#.into())],
746 Some(Segment::ExactMatch("c".into())),
747 ),
748 ];
749 for (string, node, property) in with_spaces {
750 assert_eq!(
751 all_consuming(tree_selector(RequireEscaped::WHITESPACE))
752 .parse(string)
753 .unwrap_or_else(|e: nom::Err<nom_language::error::VerboseError<_>>| panic!(
754 "all_consuming |{string}| failed: {e}"
755 ))
756 .1,
757 TreeSelector { node, property, tree_names: None },
758 "input: |{string}|",
759 );
760 }
761
762 assert!(standalone_tree_selector::<VerboseError>(r#"a/b:"xc"/d"#).is_err());
764 }
765
766 #[fuchsia::test]
767 fn parse_full_selector() {
768 assert_eq!(
769 selector::<VerboseError>("core/**:some-node/he*re:prop").unwrap(),
770 Selector {
771 component: ComponentSelector {
772 segments: vec![
773 Segment::ExactMatch("core".into()),
774 Segment::Pattern("**".into()),
775 ],
776 },
777 tree: TreeSelector {
778 node: vec![
779 Segment::ExactMatch("some-node".into()),
780 Segment::Pattern("he*re".into()),
781 ],
782 property: Some(Segment::ExactMatch("prop".into())),
783 tree_names: None,
784 },
785 }
786 );
787
788 assert_eq!(
790 selector::<VerboseError>(" foo:bar ").unwrap(),
791 Selector {
792 component: ComponentSelector { segments: vec![Segment::ExactMatch("foo".into())] },
793 tree: TreeSelector {
794 node: vec![Segment::ExactMatch("bar".into())],
795 property: None,
796 tree_names: None
797 },
798 }
799 );
800
801 assert_eq!(
803 selector::<VerboseError>(r#"core/**:[name=foo, name="bar\*"]some-node/he*re:prop"#)
804 .unwrap(),
805 Selector {
806 component: ComponentSelector {
807 segments: vec![
808 Segment::ExactMatch("core".into()),
809 Segment::Pattern("**".into()),
810 ],
811 },
812 tree: TreeSelector {
813 node: vec![
814 Segment::ExactMatch("some-node".into()),
815 Segment::Pattern("he*re".into()),
816 ],
817 property: Some(Segment::ExactMatch("prop".into())),
818 tree_names: Some(vec!["foo", r"bar*"].into()),
819 },
820 }
821 );
822
823 assert_eq!(
824 selector::<VerboseError>(r#"core/**:[name="foo:bar"]some-node/he*re:prop"#).unwrap(),
825 Selector {
826 component: ComponentSelector {
827 segments: vec![
828 Segment::ExactMatch("core".into()),
829 Segment::Pattern("**".into()),
830 ],
831 },
832 tree: TreeSelector {
833 node: vec![
834 Segment::ExactMatch("some-node".into()),
835 Segment::Pattern("he*re".into()),
836 ],
837 property: Some(Segment::ExactMatch("prop".into())),
838 tree_names: Some(vec!["foo:bar"].into()),
839 },
840 }
841 );
842
843 assert_eq!(
844 selector::<VerboseError>(r#"core/**:[name="name=bar"]some-node/he*re:prop"#).unwrap(),
845 Selector {
846 component: ComponentSelector {
847 segments: vec![
848 Segment::ExactMatch("core".into()),
849 Segment::Pattern("**".into()),
850 ],
851 },
852 tree: TreeSelector {
853 node: vec![
854 Segment::ExactMatch("some-node".into()),
855 Segment::Pattern("he*re".into()),
856 ],
857 property: Some(Segment::ExactMatch("prop".into())),
858 tree_names: Some(vec!["name=bar"].into()),
859 },
860 }
861 );
862
863 assert_eq!(
864 selector::<VerboseError>(r#"core/**:[name=foo-bar_baz]some-node/he*re:prop"#).unwrap(),
865 Selector {
866 component: ComponentSelector {
867 segments: vec![
868 Segment::ExactMatch("core".into()),
869 Segment::Pattern("**".into()),
870 ],
871 },
872 tree: TreeSelector {
873 node: vec![
874 Segment::ExactMatch("some-node".into()),
875 Segment::Pattern("he*re".into()),
876 ],
877 property: Some(Segment::ExactMatch("prop".into())),
878 tree_names: Some(vec!["foo-bar_baz"].into()),
879 },
880 }
881 );
882
883 assert!(selector::<VerboseError>("foo:bar where").is_err());
885 }
886
887 #[fuchsia::test]
888 fn assert_no_trailing_backward_slash() {
889 assert!(selector::<VerboseError>(r#"foo:bar:baz\"#).is_err());
890 }
891
892 #[fuchsia::test]
893 fn parse_full_selector_with_spaces() {
894 let expected_regardless_of_escape_or_quote = Selector {
895 component: ComponentSelector {
896 segments: vec![
897 Segment::ExactMatch("core".into()),
898 Segment::ExactMatch("foo".into()),
899 ],
900 },
901 tree: TreeSelector {
902 node: vec![Segment::ExactMatch("some node".into()), Segment::Pattern("*".into())],
903 property: Some(Segment::ExactMatch("prop".into())),
904 tree_names: None,
905 },
906 };
907 assert_eq!(
908 selector::<VerboseError>(r#"core/foo:some\ node/*:prop"#).unwrap(),
909 expected_regardless_of_escape_or_quote,
910 );
911
912 assert_eq!(
913 selector::<VerboseError>(r#""core/foo:some node/*:prop""#).unwrap(),
914 expected_regardless_of_escape_or_quote,
915 );
916 }
917
918 #[fuchsia::test]
919 fn test_extract_from_quotes() {
920 let test_cases = [
921 ("foo", "foo"),
922 (r#""foo""#, "foo"),
923 (r#""foo\"bar""#, r#"foo\"bar"#),
924 (r#""bar\*""#, r#"bar\*"#),
925 ];
926
927 for (case_number, (input, expected_extracted)) in test_cases.into_iter().enumerate() {
928 let actual_extracted = extract_from_quotes(input);
929 assert_eq!(
930 expected_extracted, actual_extracted,
931 "failed test case {case_number} on name_list: |{input}|",
932 );
933 }
934 }
935
936 #[fuchsia::test]
937 fn extract_name_list() {
938 let test_cases = [
939 ("root:prop", ("root:prop", None)),
940 ("[name=foo]root:prop", ("root:prop", Some("name=foo"))),
941 (r#"[name="with internal ,"]root"#, ("root", Some(r#"name="with internal ,""#))),
942 (r#"[name="f[o]o"]root:prop"#, ("root:prop", Some(r#"name="f[o]o""#))),
943 (
944 r#"[name="fo]o", name="[bar,baz"]root:prop"#,
945 ("root:prop", Some(r#"name="fo]o", name="[bar,baz""#)),
946 ),
947 (r#"ab/\ d:c\ "#, (r#"ab/\ d:c\ "#, None)),
948 ];
949
950 for (case_number, (input, (expected_residue, expected_name_list))) in
951 test_cases.into_iter().enumerate()
952 {
953 let (actual_residue, actual_name_list) =
954 extract_conjoined_names::<nom_language::error::VerboseError<&str>>(input).unwrap();
955 assert_eq!(
956 expected_residue, actual_residue,
957 "failed test case {case_number} on residue: |{input}|",
958 );
959 assert_eq!(
960 expected_name_list, actual_name_list,
961 "failed test case {case_number} on name_list: |{input}|",
962 );
963 }
964 }
965
966 #[fuchsia::test]
967 fn comma_separated_name_lists() {
968 let test_cases = [
969 (r#"name=foo, name=bar"#, vec!["name=foo", "name=bar"]),
970 (r#"name="with internal ,""#, vec![r#"name="with internal ,""#]),
971 (r#"name="foo", name=bar"#, vec![r#"name="foo""#, "name=bar"]),
972 (r#"name="foo,bar", name=baz"#, vec![r#"name="foo,bar""#, "name=baz"]),
973 (r#"name="foo,bar", name="baz""#, vec![r#"name="foo,bar""#, r#"name="baz""#]),
974 (r#"name="foo ,bar", name=baz"#, vec![r#"name="foo ,bar""#, "name=baz"]),
975 (r#"name="foo\",bar", name="baz""#, vec![r#"name="foo\",bar""#, r#"name="baz""#]),
976 (r#"name="foo\" ,bar", name="baz""#, vec![r#"name="foo\" ,bar""#, r#"name="baz""#]),
977 (r#"name="foo,bar", name=" baz ""#, vec![r#"name="foo,bar""#, r#"name=" baz ""#]),
978 (
979 r#"name="foo\", bar,", name=",,baz,,,""#,
980 vec![r#"name="foo\", bar,""#, r#"name=",,baz,,,""#],
981 ),
982 ];
983
984 for (case_number, (input, expected)) in test_cases.into_iter().enumerate() {
985 let actual = parse_quote_sensitive_separator(input, ',');
986 let actual_len = actual.len();
987 let expected_len = expected.len();
988 assert_eq!(
989 expected, actual,
990 "failed test case {case_number}: |{input}|\nexpected length: {expected_len}\nactual length: {actual_len}",
991 );
992 }
993 }
994}