1use std::fmt::Display;
2use std::hash::Hash;
3use std::str::FromStr;
4
5use apollo_compiler::collections::IndexSet;
6use itertools::Itertools;
7use nom::IResult;
8use nom::Input;
9use nom::Parser;
10use nom::branch::alt;
11use nom::character::complete::char;
12use nom::character::complete::one_of;
13use nom::combinator::all_consuming;
14use nom::combinator::map;
15use nom::combinator::opt;
16use nom::combinator::recognize;
17use nom::error::ParseError;
18use nom::multi::many0;
19use nom::sequence::pair;
20use nom::sequence::preceded;
21use nom::sequence::terminated;
22use serde_json_bytes::Value as JSON;
23
24use super::helpers::spaces_or_comments;
25use super::helpers::vec_push;
26use super::known_var::KnownVariable;
27use super::lit_expr::LitExpr;
28use super::location::OffsetRange;
29use super::location::Ranged;
30use super::location::Span;
31use super::location::SpanExtra;
32use super::location::WithRange;
33use super::location::merge_ranges;
34use super::location::new_span_with_spec;
35use super::location::ranged_span;
36use crate::connectors::ConnectSpec;
37use crate::connectors::Namespace;
38use crate::connectors::json_selection::location::get_connect_spec;
39use crate::connectors::json_selection::methods::ArrowMethod;
40use crate::connectors::variable::VariableNamespace;
41use crate::connectors::variable::VariableReference;
42
43pub(super) type ParseResult<'a, T> = IResult<Span<'a>, T>;
51
52pub(super) fn nom_error_message(
55 suffix: Span,
56 message: impl Into<String>,
62) -> nom::Err<nom::error::Error<Span>> {
63 let offset = suffix.location_offset();
64 nom::Err::Error(nom::error::Error::from_error_kind(
65 suffix.map_extra(|extra| SpanExtra {
66 errors: vec_push(extra.errors, (message.into(), offset)),
67 ..extra
68 }),
69 nom::error::ErrorKind::IsNot,
70 ))
71}
72
73pub(super) fn nom_fail_message(
78 suffix: Span,
79 message: impl Into<String>,
80) -> nom::Err<nom::error::Error<Span>> {
81 let offset = suffix.location_offset();
82 nom::Err::Failure(nom::error::Error::from_error_kind(
83 suffix.map_extra(|extra| SpanExtra {
84 errors: vec_push(extra.errors, (message.into(), offset)),
85 ..extra
86 }),
87 nom::error::ErrorKind::IsNot,
88 ))
89}
90
91pub(crate) trait VarPaths {
92 fn var_paths(&self) -> Vec<&PathSelection>;
97
98 fn external_var_paths(&self) -> Vec<&PathSelection> {
99 self.var_paths()
100 .into_iter()
101 .filter(|var_path| {
102 if let PathList::Var(known_var, _) = var_path.path.as_ref() {
103 matches!(known_var.as_ref(), KnownVariable::External(_))
104 } else {
105 false
106 }
107 })
108 .collect()
109 }
110
111 fn local_var_names(&self) -> IndexSet<String> {
114 self.var_paths()
115 .into_iter()
116 .flat_map(|var_path| {
117 if let PathList::Var(known_var, _) = var_path.path.as_ref() {
118 match known_var.as_ref() {
119 KnownVariable::Local(var_name) => Some(var_name.to_string()),
120 _ => None,
121 }
122 } else {
123 None
124 }
125 })
126 .collect()
127 }
128}
129
130#[derive(Debug, PartialEq, Eq, Clone)]
134pub struct JSONSelection {
135 pub(super) inner: TopLevelSelection,
136 pub spec: ConnectSpec,
137}
138
139#[derive(Debug, PartialEq, Eq, Clone)]
140pub(super) enum TopLevelSelection {
141 Named(SubSelection),
145 Path(PathSelection),
146}
147
148#[derive(thiserror::Error, Debug, PartialEq, Eq, Clone)]
152#[error("{message}: {fragment}")]
153pub struct JSONSelectionParseError {
154 pub message: String,
159
160 pub fragment: String,
163
164 pub offset: usize,
169
170 pub spec: ConnectSpec,
172}
173
174impl JSONSelection {
175 pub fn spec(&self) -> ConnectSpec {
176 self.spec
177 }
178
179 pub fn named(sub: SubSelection) -> Self {
180 Self {
181 inner: TopLevelSelection::Named(sub),
182 spec: ConnectSpec::latest(),
183 }
184 }
185
186 pub fn path(path: PathSelection) -> Self {
187 Self {
188 inner: TopLevelSelection::Path(path),
189 spec: ConnectSpec::latest(),
190 }
191 }
192
193 pub(crate) fn if_named_else_path<T>(
194 &self,
195 if_named: impl Fn(&SubSelection) -> T,
196 if_path: impl Fn(&PathSelection) -> T,
197 ) -> T {
198 match &self.inner {
199 TopLevelSelection::Named(subselect) => if_named(subselect),
200 TopLevelSelection::Path(path) => if_path(path),
201 }
202 }
203
204 pub fn empty() -> Self {
205 Self {
206 inner: TopLevelSelection::Named(SubSelection::default()),
207 spec: ConnectSpec::latest(),
208 }
209 }
210
211 pub fn is_empty(&self) -> bool {
212 match &self.inner {
213 TopLevelSelection::Named(subselect) => subselect.selections.is_empty(),
214 TopLevelSelection::Path(path) => *path.path == PathList::Empty,
215 }
216 }
217
218 pub fn parse(input: &str) -> Result<Self, JSONSelectionParseError> {
224 JSONSelection::parse_with_spec(input, ConnectSpec::latest())
225 }
226
227 pub fn parse_with_spec(
228 input: &str,
229 spec: ConnectSpec,
230 ) -> Result<Self, JSONSelectionParseError> {
231 let span = new_span_with_spec(input, spec);
232
233 match JSONSelection::parse_span(span) {
234 Ok((remainder, selection)) => {
235 let fragment = remainder.fragment();
236 let produced_errors = !remainder.extra.errors.is_empty();
237 if fragment.is_empty() && !produced_errors {
238 Ok(selection)
239 } else {
240 let mut message = remainder
241 .extra
242 .errors
243 .iter()
244 .map(|(msg, _offset)| msg.as_str())
245 .collect::<Vec<_>>()
246 .join("\n");
247
248 let (error_offset, error_fragment) =
250 if let Some((_, first_error_offset)) = remainder.extra.errors.first() {
251 let error_span =
252 new_span_with_spec(input, spec).take_from(*first_error_offset);
253 (
254 error_span.location_offset(),
255 error_span.fragment().to_string(),
256 )
257 } else {
258 (remainder.location_offset(), fragment.to_string())
259 };
260
261 if !fragment.is_empty() {
262 message
263 .push_str(&format!("\nUnexpected trailing characters: {}", fragment));
264 }
265 Err(JSONSelectionParseError {
266 message,
267 fragment: error_fragment,
268 offset: error_offset,
269 spec: remainder.extra.spec,
270 })
271 }
272 }
273
274 Err(e) => match e {
275 nom::Err::Error(e) | nom::Err::Failure(e) => Err(JSONSelectionParseError {
276 message: if e.input.extra.errors.is_empty() {
277 format!("nom::error::ErrorKind::{:?}", e.code)
278 } else {
279 e.input
280 .extra
281 .errors
282 .iter()
283 .map(|(msg, _offset)| msg.clone())
284 .join("\n")
285 },
286 fragment: e.input.fragment().to_string(),
287 offset: e.input.location_offset(),
288 spec: e.input.extra.spec,
289 }),
290
291 nom::Err::Incomplete(_) => unreachable!("nom::Err::Incomplete not expected here"),
292 },
293 }
294 }
295
296 fn parse_span(input: Span) -> ParseResult<Self> {
297 match get_connect_spec(&input) {
298 ConnectSpec::V0_1 | ConnectSpec::V0_2 => Self::parse_span_v0_2(input),
299 ConnectSpec::V0_3 | ConnectSpec::V0_4 => Self::parse_span_v0_3(input),
300 }
301 }
302
303 fn parse_span_v0_2(input: Span) -> ParseResult<Self> {
304 let spec = get_connect_spec(&input);
305
306 match alt((
307 all_consuming(terminated(
308 map(PathSelection::parse, |path| Self {
309 inner: TopLevelSelection::Path(path),
310 spec,
311 }),
312 spaces_or_comments,
316 )),
317 all_consuming(terminated(
318 map(SubSelection::parse_naked, |sub| Self {
319 inner: TopLevelSelection::Named(sub),
320 spec,
321 }),
322 spaces_or_comments,
330 )),
331 ))
332 .parse(input)
333 {
334 Ok((remainder, selection)) => {
335 if remainder.fragment().is_empty() {
336 Ok((remainder, selection))
337 } else {
338 Err(nom_fail_message(
339 remainder,
345 "Unexpected trailing characters",
346 ))
347 }
348 }
349 Err(e) => Err(e),
350 }
351 }
352
353 fn parse_span_v0_3(input: Span) -> ParseResult<Self> {
354 let spec = get_connect_spec(&input);
355
356 match all_consuming(terminated(
357 map(SubSelection::parse_naked, |sub| {
358 if let (1, Some(only)) = (sub.selections.len(), sub.selections.first()) {
359 if only.is_anonymous() || matches!(only.prefix, NamingPrefix::Spread(None)) {
383 return Self {
384 inner: TopLevelSelection::Path(only.path.clone()),
385 spec,
386 };
387 }
388 }
389 Self {
390 inner: TopLevelSelection::Named(sub),
391 spec,
392 }
393 }),
394 spaces_or_comments,
399 ))
400 .parse(input)
401 {
402 Ok((remainder, selection)) => {
403 if remainder.fragment().is_empty() {
404 Ok((remainder, selection))
405 } else {
406 Err(nom_fail_message(
407 remainder,
413 "Unexpected trailing characters",
414 ))
415 }
416 }
417 Err(e) => Err(e),
418 }
419 }
420
421 pub(crate) fn next_subselection(&self) -> Option<&SubSelection> {
422 match &self.inner {
423 TopLevelSelection::Named(subselect) => Some(subselect),
424 TopLevelSelection::Path(path) => path.next_subselection(),
425 }
426 }
427
428 #[allow(unused)]
429 pub(crate) fn next_mut_subselection(&mut self) -> Option<&mut SubSelection> {
430 match &mut self.inner {
431 TopLevelSelection::Named(subselect) => Some(subselect),
432 TopLevelSelection::Path(path) => path.next_mut_subselection(),
433 }
434 }
435
436 pub fn variable_references(&self) -> impl Iterator<Item = VariableReference<Namespace>> + '_ {
437 self.external_var_paths()
438 .into_iter()
439 .flat_map(|var_path| var_path.variable_reference())
440 }
441}
442
443impl VarPaths for JSONSelection {
444 fn var_paths(&self) -> Vec<&PathSelection> {
445 match &self.inner {
446 TopLevelSelection::Named(subselect) => subselect.var_paths(),
447 TopLevelSelection::Path(path) => path.var_paths(),
448 }
449 }
450}
451
452#[derive(Debug, PartialEq, Eq, Clone)]
456pub struct NamedSelection {
457 pub(super) prefix: NamingPrefix,
458 pub(super) path: PathSelection,
459}
460
461#[derive(Debug, PartialEq, Eq, Clone)]
462pub(super) enum NamingPrefix {
463 Alias(Alias),
466 Spread(OffsetRange),
471 None,
481}
482
483impl Ranged for NamedSelection {
488 fn range(&self) -> OffsetRange {
489 let alias_or_spread_range = match &self.prefix {
490 NamingPrefix::None => None,
491 NamingPrefix::Alias(alias) => alias.range(),
492 NamingPrefix::Spread(range) => range.clone(),
493 };
494 merge_ranges(alias_or_spread_range, self.path.range())
495 }
496}
497
498impl NamedSelection {
499 pub(super) fn has_single_output_key(&self) -> bool {
500 self.get_single_key().is_some()
501 }
502
503 pub(super) fn get_single_key(&self) -> Option<&WithRange<Key>> {
504 match &self.prefix {
505 NamingPrefix::None => self.path.get_single_key(),
506 NamingPrefix::Spread(_) => None,
507 NamingPrefix::Alias(alias) => Some(&alias.name),
508 }
509 }
510
511 pub(super) fn is_anonymous(&self) -> bool {
512 match &self.prefix {
513 NamingPrefix::None => self.path.is_anonymous(),
514 NamingPrefix::Alias(_) => false,
515 NamingPrefix::Spread(_) => false,
516 }
517 }
518
519 pub(super) fn field(
520 alias: Option<Alias>,
521 name: WithRange<Key>,
522 selection: Option<SubSelection>,
523 ) -> Self {
524 let name_range = name.range();
525 let tail = if let Some(selection) = selection.as_ref() {
526 WithRange::new(PathList::Selection(selection.clone()), selection.range())
527 } else {
528 let empty_range = name_range.as_ref().map(|range| range.end..range.end);
531 WithRange::new(PathList::Empty, empty_range)
532 };
533 let tail_range = tail.range();
534 let name_tail_range = merge_ranges(name_range, tail_range);
535 let prefix = if let Some(alias) = alias {
536 NamingPrefix::Alias(alias)
537 } else {
538 NamingPrefix::None
539 };
540 Self {
541 prefix,
542 path: PathSelection {
543 path: WithRange::new(PathList::Key(name, tail), name_tail_range),
544 },
545 }
546 }
547
548 pub(crate) fn parse(input: Span) -> ParseResult<Self> {
549 match get_connect_spec(&input) {
550 ConnectSpec::V0_1 | ConnectSpec::V0_2 => Self::parse_v0_2(input),
551 ConnectSpec::V0_3 => Self::parse_v0_3(input),
552 ConnectSpec::V0_4 => Self::parse_v0_4(input),
553 }
554 }
555
556 pub(crate) fn parse_v0_2(input: Span) -> ParseResult<Self> {
557 alt((
558 Self::parse_path,
567 Self::parse_field,
568 Self::parse_group,
569 ))
570 .parse(input)
571 }
572
573 fn parse_field(input: Span) -> ParseResult<Self> {
574 (
575 opt(Alias::parse),
576 Key::parse,
577 spaces_or_comments,
578 opt(SubSelection::parse),
579 )
580 .parse(input)
581 .map(|(remainder, (alias, name, _, selection))| {
582 (remainder, Self::field(alias, name, selection))
583 })
584 }
585
586 fn parse_path(input: Span) -> ParseResult<Self> {
588 if let Ok((remainder, alias)) = Alias::parse(input.clone()) {
589 match PathSelection::parse(remainder) {
590 Ok((remainder, path)) => Ok((
591 remainder,
592 Self {
593 prefix: NamingPrefix::Alias(alias),
594 path,
595 },
596 )),
597 Err(nom::Err::Failure(e)) => Err(nom::Err::Failure(e)),
598 Err(_) => Err(nom_error_message(
599 input.clone(),
600 "Path selection alias must be followed by a path",
601 )),
602 }
603 } else {
604 match PathSelection::parse(input.clone()) {
605 Ok((remainder, path)) => {
606 if path.is_anonymous() && path.has_subselection() {
607 Ok((
613 remainder,
614 Self {
615 prefix: NamingPrefix::Spread(None),
616 path,
617 },
618 ))
619 } else {
620 Err(nom_fail_message(
621 input.clone(),
622 "Named path selection must either begin with alias or ..., or end with subselection",
623 ))
624 }
625 }
626 Err(nom::Err::Failure(e)) => Err(nom::Err::Failure(e)),
627 Err(_) => Err(nom_error_message(
628 input.clone(),
629 "Path selection must either begin with alias or ..., or end with subselection",
630 )),
631 }
632 }
633 }
634
635 fn parse_group(input: Span) -> ParseResult<Self> {
636 (Alias::parse, SubSelection::parse)
637 .parse(input)
638 .map(|(input, (alias, group))| {
639 let group_range = group.range();
640 (
641 input,
642 NamedSelection {
643 prefix: NamingPrefix::Alias(alias),
644 path: PathSelection {
645 path: WithRange::new(PathList::Selection(group), group_range),
646 },
647 },
648 )
649 })
650 }
651
652 fn parse_v0_3(input: Span) -> ParseResult<Self> {
655 let (after_alias, alias) = opt(Alias::parse).parse(input.clone())?;
656
657 if let Some(alias) = alias {
658 if let Ok((remainder, sub)) = SubSelection::parse(after_alias.clone()) {
659 let sub_range = sub.range();
660 return Ok((
661 remainder,
662 Self {
663 prefix: NamingPrefix::Alias(alias),
664 path: PathSelection {
675 path: WithRange::new(PathList::Selection(sub), sub_range),
676 },
677 },
678 ));
679 }
680
681 PathSelection::parse(after_alias.clone()).map(|(remainder, path)| {
682 (
683 remainder,
684 Self {
685 prefix: NamingPrefix::Alias(alias),
686 path,
687 },
688 )
689 })
690 } else {
691 (
692 spaces_or_comments,
693 opt(ranged_span("...")),
694 PathSelection::parse,
695 )
696 .parse(input.clone())
697 .map(|(mut remainder, (_spaces, spread, path))| {
698 let prefix = if let Some(spread) = spread {
699 remainder.extra.errors.push((
701 "Spread syntax (...) is not supported in connect/v0.3 (use connect/v0.4)"
702 .to_string(),
703 input.location_offset(),
704 ));
705 NamingPrefix::Spread(spread.range())
711 } else if path.is_anonymous() && path.has_subselection() {
712 NamingPrefix::Spread(None)
722 } else {
723 NamingPrefix::None
730 };
731 (remainder, Self { prefix, path })
732 })
733 }
734 }
735
736 fn parse_v0_4(input: Span) -> ParseResult<Self> {
739 let (after_alias, alias) = opt(Alias::parse).parse(input.clone())?;
740
741 if let Some(alias) = alias {
742 if let Ok((remainder, sub)) = SubSelection::parse(after_alias.clone()) {
743 let sub_range = sub.range();
744 return Ok((
745 remainder,
746 Self {
747 prefix: NamingPrefix::Alias(alias),
748 path: PathSelection {
759 path: WithRange::new(PathList::Selection(sub), sub_range),
760 },
761 },
762 ));
763 }
764
765 PathSelection::parse(after_alias.clone()).map(|(remainder, path)| {
766 (
767 remainder,
768 Self {
769 prefix: NamingPrefix::Alias(alias),
770 path,
771 },
772 )
773 })
774 } else {
775 (
776 spaces_or_comments,
777 opt(ranged_span("...")),
778 PathSelection::parse,
779 )
780 .parse(input.clone())
781 .map(|(remainder, (_spaces, spread, path))| {
782 let prefix = if let Some(spread) = spread {
783 NamingPrefix::Spread(spread.range())
785 } else if path.is_anonymous() && path.has_subselection() {
786 NamingPrefix::Spread(None)
796 } else {
797 NamingPrefix::None
804 };
805 (remainder, Self { prefix, path })
806 })
807 }
808 }
809
810 pub(crate) fn names(&self) -> Vec<&str> {
811 if let Some(single_key) = self.get_single_key() {
812 vec![single_key.as_str()]
813 } else if let Some(sub) = self.path.next_subselection() {
814 let mut name_set = IndexSet::default();
817 for selection in sub.selections_iter() {
818 name_set.extend(selection.names());
819 }
820 name_set.into_iter().collect()
821 } else {
822 Vec::new()
823 }
824 }
825
826 pub(crate) fn next_subselection(&self) -> Option<&SubSelection> {
828 self.path.next_subselection()
829 }
830}
831
832impl VarPaths for NamedSelection {
833 fn var_paths(&self) -> Vec<&PathSelection> {
834 self.path.var_paths()
835 }
836}
837
838#[derive(Debug, PartialEq, Eq, Clone)]
848pub struct PathSelection {
849 pub(super) path: WithRange<PathList>,
850}
851
852impl Ranged for PathSelection {
857 fn range(&self) -> OffsetRange {
858 self.path.range()
859 }
860}
861
862impl PathSelection {
863 pub(crate) fn parse(input: Span) -> ParseResult<Self> {
864 PathList::parse(input).map(|(input, path)| (input, Self { path }))
865 }
866
867 pub(crate) fn variable_reference<N: FromStr + ToString>(&self) -> Option<VariableReference<N>> {
868 match self.path.as_ref() {
869 PathList::Var(var, tail) => match var.as_ref() {
870 KnownVariable::External(namespace) => {
871 let selection = tail.compute_selection_trie();
872 let full_range = merge_ranges(var.range(), tail.range());
873 Some(VariableReference {
874 namespace: VariableNamespace {
875 namespace: N::from_str(namespace).ok()?,
876 location: var.range(),
877 },
878 selection,
879 location: full_range,
880 })
881 }
882 _ => None,
883 },
884 _ => None,
885 }
886 }
887
888 #[allow(unused)]
889 pub(super) fn is_single_key(&self) -> bool {
890 self.path.is_single_key()
891 }
892
893 pub(super) fn get_single_key(&self) -> Option<&WithRange<Key>> {
894 self.path.get_single_key()
895 }
896
897 pub(super) fn is_anonymous(&self) -> bool {
898 self.path.is_anonymous()
899 }
900
901 #[allow(unused)]
902 pub(super) fn from_slice(keys: &[Key], selection: Option<SubSelection>) -> Self {
903 Self {
904 path: WithRange::new(PathList::from_slice(keys, selection), None),
905 }
906 }
907
908 #[allow(unused)]
909 pub(super) fn has_subselection(&self) -> bool {
910 self.path.has_subselection()
911 }
912
913 pub(super) fn next_subselection(&self) -> Option<&SubSelection> {
914 self.path.next_subselection()
915 }
916
917 #[allow(unused)]
918 pub(super) fn next_mut_subselection(&mut self) -> Option<&mut SubSelection> {
919 self.path.next_mut_subselection()
920 }
921}
922
923impl VarPaths for PathSelection {
924 fn var_paths(&self) -> Vec<&PathSelection> {
925 let mut paths = Vec::new();
926 match self.path.as_ref() {
927 PathList::Var(var_name, tail) => {
928 if matches!(
933 var_name.as_ref(),
934 KnownVariable::External(_) | KnownVariable::Local(_)
935 ) {
936 paths.push(self);
937 }
938 paths.extend(tail.var_paths());
939 }
940 other => {
941 paths.extend(other.var_paths());
942 }
943 };
944 paths
945 }
946}
947
948impl From<PathList> for PathSelection {
949 fn from(path: PathList) -> Self {
950 Self {
951 path: WithRange::new(path, None),
952 }
953 }
954}
955
956#[derive(Debug, PartialEq, Eq, Clone)]
957pub(crate) enum PathList {
958 Var(WithRange<KnownVariable>, WithRange<PathList>),
965
966 Key(WithRange<Key>, WithRange<PathList>),
970
971 Expr(WithRange<LitExpr>, WithRange<PathList>),
974
975 Method(WithRange<String>, Option<MethodArgs>, WithRange<PathList>),
978
979 Question(WithRange<PathList>),
987
988 Selection(SubSelection),
992
993 Empty,
996}
997
998impl PathList {
999 pub(crate) fn is_empty(&self) -> bool {
1000 matches!(self, PathList::Empty)
1001 }
1002
1003 pub(super) fn parse(input: Span) -> ParseResult<WithRange<Self>> {
1004 match Self::parse_with_depth(input.clone(), 0) {
1005 Ok((_, parsed)) if matches!(*parsed, Self::Empty) => Err(nom_error_message(
1006 input.clone(),
1007 "Path selection cannot be empty",
1016 )),
1017 otherwise => otherwise,
1018 }
1019 }
1020
1021 #[cfg(test)]
1022 pub(super) fn into_with_range(self) -> WithRange<Self> {
1023 WithRange::new(self, None)
1024 }
1025
1026 pub(super) fn parse_with_depth(input: Span, depth: usize) -> ParseResult<WithRange<Self>> {
1027 let spec = get_connect_spec(&input);
1028
1029 let offset_if_empty = input.location_offset();
1035 let range_if_empty: OffsetRange = Some(offset_if_empty..offset_if_empty);
1036
1037 let (input, _spaces) = spaces_or_comments(input)?;
1039
1040 if depth == 0 {
1044 match (
1050 spaces_or_comments,
1051 ranged_span("$("),
1052 LitExpr::parse,
1053 spaces_or_comments,
1054 ranged_span(")"),
1055 )
1056 .parse(input.clone())
1057 {
1058 Ok((suffix, (_, dollar_open_paren, expr, close_paren, _))) => {
1059 let (remainder, rest) = Self::parse_with_depth(suffix, depth + 1)?;
1060 let expr_range = merge_ranges(dollar_open_paren.range(), close_paren.range());
1061 let full_range = merge_ranges(expr_range, rest.range());
1062 return Ok((
1063 remainder,
1064 WithRange::new(Self::Expr(expr, rest), full_range),
1065 ));
1066 }
1067 Err(nom::Err::Failure(err)) => {
1068 return Err(nom::Err::Failure(err));
1069 }
1070 Err(_) => {
1071 }
1073 }
1074
1075 if let Ok((suffix, (dollar, opt_var))) =
1076 (ranged_span("$"), opt(parse_identifier_no_space)).parse(input.clone())
1077 {
1078 let dollar_range = dollar.range();
1079 let (remainder, rest) = Self::parse_with_depth(suffix, depth + 1)?;
1080 let full_range = merge_ranges(dollar_range.clone(), rest.range());
1081 return if let Some(var) = opt_var {
1082 let full_name = format!("{}{}", dollar.as_ref(), var.as_str());
1083 let known_var = if input.extra.is_local_var(&full_name) {
1087 KnownVariable::Local(full_name)
1088 } else {
1089 KnownVariable::External(full_name)
1090 };
1091 let var_range = merge_ranges(dollar_range, var.range());
1092 let ranged_known_var = WithRange::new(known_var, var_range);
1093 Ok((
1094 remainder,
1095 WithRange::new(Self::Var(ranged_known_var, rest), full_range),
1096 ))
1097 } else {
1098 let ranged_dollar_var = WithRange::new(KnownVariable::Dollar, dollar_range);
1099 Ok((
1100 remainder,
1101 WithRange::new(Self::Var(ranged_dollar_var, rest), full_range),
1102 ))
1103 };
1104 }
1105
1106 if let Ok((suffix, at)) = ranged_span("@").parse(input.clone()) {
1107 let (remainder, rest) = Self::parse_with_depth(suffix, depth + 1)?;
1108 let full_range = merge_ranges(at.range(), rest.range());
1109 return Ok((
1110 remainder,
1111 WithRange::new(
1112 Self::Var(WithRange::new(KnownVariable::AtSign, at.range()), rest),
1113 full_range,
1114 ),
1115 ));
1116 }
1117
1118 if let Ok((suffix, key)) = Key::parse(input.clone()) {
1119 let (remainder, rest) = Self::parse_with_depth(suffix, depth + 1)?;
1120
1121 return match spec {
1122 ConnectSpec::V0_1 | ConnectSpec::V0_2 => match rest.as_ref() {
1123 Self::Empty | Self::Selection(_) => Err(nom_error_message(
1128 input.clone(),
1129 "Single-key path must be prefixed with $. to avoid ambiguity with field name",
1133 )),
1134 _ => {
1135 let full_range = merge_ranges(key.range(), rest.range());
1136 Ok((remainder, WithRange::new(Self::Key(key, rest), full_range)))
1137 }
1138 },
1139
1140 ConnectSpec::V0_3 | ConnectSpec::V0_4 => {
1145 let full_range = merge_ranges(key.range(), rest.range());
1146 Ok((remainder, WithRange::new(Self::Key(key, rest), full_range)))
1147 }
1148 };
1149 }
1150 }
1151
1152 if depth == 0 {
1153 if (ranged_span("."), Key::parse).parse(input.clone()).is_ok() {
1156 return Err(nom_fail_message(
1160 input.clone(),
1161 "Key paths cannot start with just .key (use $.key instead)",
1162 ));
1163 }
1164 return Err(nom_error_message(
1167 input.clone(),
1168 "Path selection must start with key, $variable, $, @, or $(expression)",
1169 ));
1170 }
1171
1172 if input.fragment().starts_with("??") || input.fragment().starts_with("?!") {
1178 return Ok((input, WithRange::new(Self::Empty, range_if_empty)));
1179 }
1180
1181 match spec {
1182 ConnectSpec::V0_1 | ConnectSpec::V0_2 => {
1183 }
1185 ConnectSpec::V0_3 | ConnectSpec::V0_4 => {
1186 if let Ok((suffix, question)) = ranged_span("?").parse(input.clone()) {
1187 let (remainder, rest) = Self::parse_with_depth(suffix.clone(), depth + 1)?;
1188
1189 return match rest.as_ref() {
1190 PathList::Question(_) => {
1195 let empty_range = question.range().map(|range| range.end..range.end);
1196 let empty = WithRange::new(Self::Empty, empty_range);
1197 Ok((
1198 suffix,
1199 WithRange::new(Self::Question(empty), question.range()),
1200 ))
1201 }
1202 _ => {
1203 let full_range = merge_ranges(question.range(), rest.range());
1204 Ok((remainder, WithRange::new(Self::Question(rest), full_range)))
1205 }
1206 };
1207 }
1208 }
1209 };
1210
1211 if let Ok((remainder, (dot, key))) = (ranged_span("."), Key::parse).parse(input.clone()) {
1225 let (remainder, rest) = Self::parse_with_depth(remainder, depth + 1)?;
1226 let dot_key_range = merge_ranges(dot.range(), key.range());
1227 let full_range = merge_ranges(dot_key_range, rest.range());
1228 return Ok((remainder, WithRange::new(Self::Key(key, rest), full_range)));
1229 }
1230
1231 if input.fragment().starts_with('.') && !input.fragment().starts_with("...") {
1234 return Err(nom_fail_message(
1235 input.clone(),
1236 "Path selection . must be followed by key (identifier or quoted string literal)",
1237 ));
1238 }
1239
1240 if let Ok((suffix, arrow)) = ranged_span("->").parse(input.clone()) {
1243 return match (parse_identifier, opt(MethodArgs::parse)).parse(suffix) {
1248 Ok((suffix, (method, args_opt))) => {
1249 let mut local_var_name = None;
1250
1251 let args = if let Some(args) = args_opt.as_ref()
1256 && ArrowMethod::lookup(method.as_ref()) == Some(ArrowMethod::As)
1257 {
1258 let new_args = if let Some(old_first_arg) = args.args.first()
1259 && let LitExpr::Path(path_selection) = old_first_arg.as_ref()
1260 && let PathList::Var(var_name, var_tail) = path_selection.path.as_ref()
1261 && let KnownVariable::External(var_str) | KnownVariable::Local(var_str) =
1262 var_name.as_ref()
1263 {
1264 let as_var = WithRange::new(
1265 KnownVariable::Local(var_str.clone()),
1267 var_name.range(),
1268 );
1269
1270 local_var_name = Some(var_str.clone());
1271
1272 let new_first_arg = WithRange::new(
1273 LitExpr::Path(PathSelection {
1274 path: WithRange::new(
1275 PathList::Var(as_var, var_tail.clone()),
1276 path_selection.range(),
1277 ),
1278 }),
1279 old_first_arg.range(),
1280 );
1281
1282 let mut new_args = vec![new_first_arg];
1283 new_args.extend(args.args.iter().skip(1).cloned());
1284 new_args
1285 } else {
1286 args.args.clone()
1287 };
1288
1289 Some(MethodArgs {
1290 args: new_args,
1291 range: args.range(),
1292 })
1293 } else {
1294 args_opt
1295 };
1296
1297 let suffix_with_local_var = if let Some(var_name) = local_var_name {
1298 suffix.map_extra(|extra| extra.with_local_var(var_name))
1299 } else {
1300 suffix
1301 };
1302
1303 let (remainder, rest) =
1304 Self::parse_with_depth(suffix_with_local_var, depth + 1)?;
1305 let full_range = merge_ranges(arrow.range(), rest.range());
1306
1307 Ok((
1308 remainder,
1309 WithRange::new(Self::Method(method, args, rest), full_range),
1310 ))
1311 }
1312 Err(_) => Err(nom_fail_message(
1313 input.clone(),
1314 "Method name must follow ->",
1315 )),
1316 };
1317 }
1318
1319 if let Ok((suffix, selection)) = SubSelection::parse(input.clone()) {
1325 let selection_range = selection.range();
1326 return Ok((
1327 suffix,
1328 WithRange::new(Self::Selection(selection), selection_range),
1329 ));
1330 }
1331
1332 Ok((input.clone(), WithRange::new(Self::Empty, range_if_empty)))
1335 }
1336
1337 pub(super) fn is_anonymous(&self) -> bool {
1338 self.get_single_key().is_none()
1339 }
1340
1341 pub(super) fn is_single_key(&self) -> bool {
1342 self.get_single_key().is_some()
1343 }
1344
1345 pub(super) fn get_single_key(&self) -> Option<&WithRange<Key>> {
1346 fn rest_is_empty_or_selection(rest: &WithRange<PathList>) -> bool {
1347 match rest.as_ref() {
1348 PathList::Selection(_) | PathList::Empty => true,
1349 PathList::Question(tail) => rest_is_empty_or_selection(tail),
1350 PathList::Var(_, _)
1356 | PathList::Key(_, _)
1357 | PathList::Expr(_, _)
1358 | PathList::Method(_, _, _) => false,
1359 }
1360 }
1361
1362 match self {
1363 Self::Key(key, key_rest) => {
1364 if rest_is_empty_or_selection(key_rest) {
1365 Some(key)
1366 } else {
1367 None
1368 }
1369 }
1370 _ => None,
1371 }
1372 }
1373
1374 pub(super) fn is_question(&self) -> bool {
1375 matches!(self, Self::Question(_))
1376 }
1377
1378 #[allow(unused)]
1379 pub(super) fn from_slice(properties: &[Key], selection: Option<SubSelection>) -> Self {
1380 match properties {
1381 [] => selection.map_or(Self::Empty, Self::Selection),
1382 [head, tail @ ..] => Self::Key(
1383 WithRange::new(head.clone(), None),
1384 WithRange::new(Self::from_slice(tail, selection), None),
1385 ),
1386 }
1387 }
1388
1389 pub(super) fn has_subselection(&self) -> bool {
1390 self.next_subselection().is_some()
1391 }
1392
1393 pub(super) fn next_subselection(&self) -> Option<&SubSelection> {
1395 match self {
1396 Self::Var(_, tail) => tail.next_subselection(),
1397 Self::Key(_, tail) => tail.next_subselection(),
1398 Self::Expr(_, tail) => tail.next_subselection(),
1399 Self::Method(_, _, tail) => tail.next_subselection(),
1400 Self::Question(tail) => tail.next_subselection(),
1401 Self::Selection(sub) => Some(sub),
1402 Self::Empty => None,
1403 }
1404 }
1405
1406 #[allow(unused)]
1407 pub(super) fn next_mut_subselection(&mut self) -> Option<&mut SubSelection> {
1409 match self {
1410 Self::Var(_, tail) => tail.next_mut_subselection(),
1411 Self::Key(_, tail) => tail.next_mut_subselection(),
1412 Self::Expr(_, tail) => tail.next_mut_subselection(),
1413 Self::Method(_, _, tail) => tail.next_mut_subselection(),
1414 Self::Question(tail) => tail.next_mut_subselection(),
1415 Self::Selection(sub) => Some(sub),
1416 Self::Empty => None,
1417 }
1418 }
1419}
1420
1421impl VarPaths for PathList {
1422 fn var_paths(&self) -> Vec<&PathSelection> {
1423 let mut paths = Vec::new();
1424 match self {
1425 PathList::Var(_, rest) | PathList::Key(_, rest) => {
1432 paths.extend(rest.var_paths());
1433 }
1434 PathList::Expr(expr, rest) => {
1435 paths.extend(expr.var_paths());
1436 paths.extend(rest.var_paths());
1437 }
1438 PathList::Method(_, opt_args, rest) => {
1439 if let Some(args) = opt_args {
1440 for lit_arg in &args.args {
1441 paths.extend(lit_arg.var_paths());
1442 }
1443 }
1444 paths.extend(rest.var_paths());
1445 }
1446 PathList::Question(rest) => {
1447 paths.extend(rest.var_paths());
1448 }
1449 PathList::Selection(sub) => paths.extend(sub.var_paths()),
1450 PathList::Empty => {}
1451 }
1452 paths
1453 }
1454}
1455
1456#[derive(Debug, PartialEq, Eq, Clone, Default)]
1459pub struct SubSelection {
1460 pub(super) selections: Vec<NamedSelection>,
1461 pub(super) range: OffsetRange,
1462}
1463
1464impl Ranged for SubSelection {
1465 fn range(&self) -> OffsetRange {
1469 self.range.clone()
1470 }
1471}
1472
1473impl SubSelection {
1474 pub(crate) fn parse(input: Span) -> ParseResult<Self> {
1475 match (
1476 spaces_or_comments,
1477 ranged_span("{"),
1478 Self::parse_naked,
1479 spaces_or_comments,
1480 ranged_span("}"),
1481 )
1482 .parse(input)
1483 {
1484 Ok((remainder, (_, open_brace, sub, _, close_brace))) => {
1485 let range = merge_ranges(open_brace.range(), close_brace.range());
1486 Ok((
1487 remainder,
1488 Self {
1489 selections: sub.selections,
1490 range,
1491 },
1492 ))
1493 }
1494 Err(e) => Err(e),
1495 }
1496 }
1497
1498 fn parse_naked(input: Span) -> ParseResult<Self> {
1499 match many0(NamedSelection::parse).parse(input.clone()) {
1500 Ok((remainder, selections)) => {
1501 for sel in selections.iter() {
1505 if sel.is_anonymous() && selections.len() > 1 {
1506 return Err(nom_error_message(
1507 input.clone(),
1508 "SubSelection cannot contain multiple elements if it contains an anonymous NamedSelection",
1509 ));
1510 }
1511 }
1512
1513 let range = merge_ranges(
1514 selections.first().and_then(|first| first.range()),
1515 selections.last().and_then(|last| last.range()),
1516 );
1517
1518 Ok((remainder, Self { selections, range }))
1519 }
1520 Err(e) => Err(e),
1521 }
1522 }
1523
1524 pub fn selections_iter(&self) -> impl Iterator<Item = &NamedSelection> {
1529 let mut selections = Vec::new();
1532 for selection in &self.selections {
1533 if selection.has_single_output_key() {
1534 selections.push(selection);
1537 } else if let Some(sub) = selection.path.next_subselection() {
1538 selections.extend(sub.selections_iter());
1544 } else {
1545 debug_assert!(false, "PathSelection without Alias or SubSelection");
1548 }
1549 }
1550 selections.into_iter()
1551 }
1552
1553 pub fn append_selection(&mut self, selection: NamedSelection) {
1554 self.selections.push(selection);
1555 }
1556
1557 pub fn last_selection_mut(&mut self) -> Option<&mut NamedSelection> {
1558 self.selections.last_mut()
1559 }
1560}
1561
1562impl VarPaths for SubSelection {
1563 fn var_paths(&self) -> Vec<&PathSelection> {
1564 let mut paths = Vec::new();
1565 for selection in &self.selections {
1566 paths.extend(selection.var_paths());
1567 }
1568 paths
1569 }
1570}
1571
1572#[derive(Debug, PartialEq, Eq, Clone)]
1575pub(crate) struct Alias {
1576 pub(super) name: WithRange<Key>,
1577 pub(super) range: OffsetRange,
1578}
1579
1580impl Ranged for Alias {
1581 fn range(&self) -> OffsetRange {
1582 self.range.clone()
1583 }
1584}
1585
1586impl Alias {
1587 pub(crate) fn new(name: &str) -> Self {
1588 if is_identifier(name) {
1589 Self::field(name)
1590 } else {
1591 Self::quoted(name)
1592 }
1593 }
1594
1595 pub(crate) fn field(name: &str) -> Self {
1596 Self {
1597 name: WithRange::new(Key::field(name), None),
1598 range: None,
1599 }
1600 }
1601
1602 pub(crate) fn quoted(name: &str) -> Self {
1603 Self {
1604 name: WithRange::new(Key::quoted(name), None),
1605 range: None,
1606 }
1607 }
1608
1609 pub(crate) fn parse(input: Span) -> ParseResult<Self> {
1610 (Key::parse, spaces_or_comments, ranged_span(":"))
1611 .parse(input)
1612 .map(|(input, (name, _, colon))| {
1613 let range = merge_ranges(name.range(), colon.range());
1614 (input, Self { name, range })
1615 })
1616 }
1617}
1618
1619#[derive(Debug, PartialEq, Eq, Clone, Hash)]
1622pub enum Key {
1623 Field(String),
1624 Quoted(String),
1625}
1626
1627impl Key {
1628 pub(crate) fn parse(input: Span) -> ParseResult<WithRange<Self>> {
1629 alt((
1630 map(parse_identifier, |id| id.take_as(Key::Field)),
1631 map(parse_string_literal, |s| s.take_as(Key::Quoted)),
1632 ))
1633 .parse(input)
1634 }
1635
1636 pub fn field(name: &str) -> Self {
1637 Self::Field(name.to_string())
1638 }
1639
1640 pub fn quoted(name: &str) -> Self {
1641 Self::Quoted(name.to_string())
1642 }
1643
1644 pub fn into_with_range(self) -> WithRange<Self> {
1645 WithRange::new(self, None)
1646 }
1647
1648 pub fn is_quoted(&self) -> bool {
1649 matches!(self, Self::Quoted(_))
1650 }
1651
1652 pub fn to_json(&self) -> JSON {
1653 match self {
1654 Key::Field(name) => JSON::String(name.clone().into()),
1655 Key::Quoted(name) => JSON::String(name.clone().into()),
1656 }
1657 }
1658
1659 pub fn as_string(&self) -> String {
1663 match self {
1664 Key::Field(name) => name.clone(),
1665 Key::Quoted(name) => name.clone(),
1666 }
1667 }
1668 pub fn as_str(&self) -> &str {
1671 match self {
1672 Key::Field(name) => name.as_str(),
1673 Key::Quoted(name) => name.as_str(),
1674 }
1675 }
1676
1677 pub fn dotted(&self) -> String {
1682 match self {
1683 Key::Field(field) => format!(".{field}"),
1684 Key::Quoted(field) => {
1685 let quoted = serde_json_bytes::Value::String(field.clone().into()).to_string();
1689 format!(".{quoted}")
1690 }
1691 }
1692 }
1693}
1694
1695impl Display for Key {
1696 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1697 let dotted = self.dotted();
1698 write!(f, "{dotted}")
1699 }
1700}
1701
1702pub(super) fn is_identifier(input: &str) -> bool {
1705 all_consuming(parse_identifier_no_space)
1707 .parse(new_span_with_spec(input, ConnectSpec::latest()))
1708 .is_ok()
1709}
1710
1711fn parse_identifier(input: Span) -> ParseResult<WithRange<String>> {
1712 preceded(spaces_or_comments, parse_identifier_no_space).parse(input)
1713}
1714
1715fn parse_identifier_no_space(input: Span) -> ParseResult<WithRange<String>> {
1716 recognize(pair(
1717 one_of("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_"),
1718 many0(one_of(
1719 "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_0123456789",
1720 )),
1721 ))
1722 .parse(input)
1723 .map(|(remainder, name)| {
1724 let range = Some(name.location_offset()..remainder.location_offset());
1725 (remainder, WithRange::new(name.to_string(), range))
1726 })
1727}
1728
1729pub(crate) fn parse_string_literal(input: Span) -> ParseResult<WithRange<String>> {
1734 let input = spaces_or_comments(input)?.0;
1735 let start = input.location_offset();
1736 let mut input_char_indices = input.char_indices();
1737
1738 match input_char_indices.next() {
1739 Some((0, quote @ '\'')) | Some((0, quote @ '"')) => {
1740 let mut escape_next = false;
1741 let mut chars: Vec<char> = Vec::new();
1742 let mut remainder_opt: Option<Span> = None;
1743
1744 for (i, c) in input_char_indices {
1745 if escape_next {
1746 match c {
1747 'n' => chars.push('\n'),
1748 _ => chars.push(c),
1749 }
1750 escape_next = false;
1751 continue;
1752 }
1753 if c == '\\' {
1754 escape_next = true;
1755 continue;
1756 }
1757 if c == quote {
1758 remainder_opt = Some(input.take_from(i + 1));
1759 break;
1760 }
1761 chars.push(c);
1762 }
1763
1764 remainder_opt
1765 .ok_or_else(|| nom_fail_message(input, "Unterminated string literal"))
1766 .map(|remainder| {
1767 let range = Some(start..remainder.location_offset());
1768 (
1769 remainder,
1770 WithRange::new(chars.iter().collect::<String>(), range),
1771 )
1772 })
1773 }
1774
1775 _ => Err(nom_error_message(input, "Not a string literal")),
1776 }
1777}
1778
1779#[derive(Debug, PartialEq, Eq, Clone, Default)]
1780pub(crate) struct MethodArgs {
1781 pub(super) args: Vec<WithRange<LitExpr>>,
1782 pub(super) range: OffsetRange,
1783}
1784
1785impl Ranged for MethodArgs {
1786 fn range(&self) -> OffsetRange {
1787 self.range.clone()
1788 }
1789}
1790
1791impl MethodArgs {
1796 fn parse(input: Span) -> ParseResult<Self> {
1797 let input = spaces_or_comments(input)?.0;
1798 let (mut input, open_paren) = ranged_span("(").parse(input)?;
1799 input = spaces_or_comments(input)?.0;
1800
1801 let mut args = Vec::new();
1802 if let Ok((remainder, first)) = LitExpr::parse(input.clone()) {
1803 args.push(first);
1804 input = remainder;
1805
1806 while let Ok((remainder, _)) = (spaces_or_comments, char(',')).parse(input.clone()) {
1807 input = spaces_or_comments(remainder)?.0;
1808 if let Ok((remainder, arg)) = LitExpr::parse(input.clone()) {
1809 args.push(arg);
1810 input = remainder;
1811 } else {
1812 break;
1813 }
1814 }
1815 }
1816
1817 input = spaces_or_comments(input.clone())?.0;
1818 let (input, close_paren) = ranged_span(")").parse(input.clone())?;
1819
1820 let range = merge_ranges(open_paren.range(), close_paren.range());
1821 Ok((input, Self { args, range }))
1822 }
1823}
1824
1825#[cfg(test)]
1826mod tests {
1827 use apollo_compiler::collections::IndexMap;
1828 use rstest::rstest;
1829
1830 use super::super::location::strip_ranges::StripRanges;
1831 use super::*;
1832 use crate::assert_debug_snapshot;
1833 use crate::connectors::json_selection::PrettyPrintable;
1834 use crate::connectors::json_selection::SelectionTrie;
1835 use crate::connectors::json_selection::fixtures::Namespace;
1836 use crate::connectors::json_selection::helpers::span_is_all_spaces_or_comments;
1837 use crate::connectors::json_selection::location::new_span;
1838 use crate::selection;
1839
1840 #[test]
1841 fn test_identifier() {
1842 fn check(input: &str, expected_name: &str) {
1843 let (remainder, name) = parse_identifier(new_span(input)).unwrap();
1844 assert!(
1845 span_is_all_spaces_or_comments(remainder.clone()),
1846 "remainder is `{:?}`",
1847 remainder.clone(),
1848 );
1849 assert_eq!(name.as_ref(), expected_name);
1850 }
1851
1852 check("hello", "hello");
1853 check("hello_world", "hello_world");
1854 check(" hello_world ", "hello_world");
1855 check("hello_world_123", "hello_world_123");
1856 check(" hello ", "hello");
1857
1858 fn check_no_space(input: &str, expected_name: &str) {
1859 let name = parse_identifier_no_space(new_span(input)).unwrap().1;
1860 assert_eq!(name.as_ref(), expected_name);
1861 }
1862
1863 check_no_space("oyez", "oyez");
1864 check_no_space("oyez ", "oyez");
1865
1866 {
1867 let identifier_with_leading_space = new_span(" oyez ");
1868 assert_eq!(
1869 parse_identifier_no_space(identifier_with_leading_space.clone()),
1870 Err(nom::Err::Error(nom::error::Error::from_error_kind(
1871 identifier_with_leading_space.clone(),
1875 nom::error::ErrorKind::OneOf,
1876 ))),
1877 );
1878 }
1879 }
1880
1881 #[test]
1882 fn test_string_literal() {
1883 fn check(input: &str, expected: &str) {
1884 let (remainder, lit) = parse_string_literal(new_span(input)).unwrap();
1885 assert!(
1886 span_is_all_spaces_or_comments(remainder.clone()),
1887 "remainder is `{:?}`",
1888 remainder.clone(),
1889 );
1890 assert_eq!(lit.as_ref(), expected);
1891 }
1892 check("'hello world'", "hello world");
1893 check("\"hello world\"", "hello world");
1894 check("'hello \"world\"'", "hello \"world\"");
1895 check("\"hello \\\"world\\\"\"", "hello \"world\"");
1896 check("'hello \\'world\\''", "hello 'world'");
1897 }
1898
1899 #[test]
1900 fn test_key() {
1901 fn check(input: &str, expected: &Key) {
1902 let (remainder, key) = Key::parse(new_span(input)).unwrap();
1903 assert!(
1904 span_is_all_spaces_or_comments(remainder.clone()),
1905 "remainder is `{:?}`",
1906 remainder.clone(),
1907 );
1908 assert_eq!(key.as_ref(), expected);
1909 }
1910
1911 check("hello", &Key::field("hello"));
1912 check("'hello'", &Key::quoted("hello"));
1913 check(" hello ", &Key::field("hello"));
1914 check("\"hello\"", &Key::quoted("hello"));
1915 check(" \"hello\" ", &Key::quoted("hello"));
1916 }
1917
1918 #[test]
1919 fn test_alias() {
1920 fn check(input: &str, alias: &str) {
1921 let (remainder, parsed) = Alias::parse(new_span(input)).unwrap();
1922 assert!(
1923 span_is_all_spaces_or_comments(remainder.clone()),
1924 "remainder is `{:?}`",
1925 remainder.clone(),
1926 );
1927 assert_eq!(parsed.name.as_str(), alias);
1928 }
1929
1930 check("hello:", "hello");
1931 check("hello :", "hello");
1932 check("hello : ", "hello");
1933 check(" hello :", "hello");
1934 check("hello: ", "hello");
1935 }
1936
1937 #[test]
1938 fn test_named_selection() {
1939 #[track_caller]
1940 fn assert_result_and_names(input: &str, expected: NamedSelection, names: &[&str]) {
1941 let (remainder, selection) = NamedSelection::parse(new_span(input)).unwrap();
1942 assert!(
1943 span_is_all_spaces_or_comments(remainder.clone()),
1944 "remainder is `{:?}`",
1945 remainder.clone(),
1946 );
1947 let selection = selection.strip_ranges();
1948 assert_eq!(selection, expected);
1949 assert_eq!(selection.names(), names);
1950 assert_eq!(
1951 selection!(input).strip_ranges(),
1952 JSONSelection::named(SubSelection {
1953 selections: vec![expected],
1954 ..Default::default()
1955 },),
1956 );
1957 }
1958
1959 assert_result_and_names(
1960 "hello",
1961 NamedSelection::field(None, Key::field("hello").into_with_range(), None),
1962 &["hello"],
1963 );
1964
1965 assert_result_and_names(
1966 "hello { world }",
1967 NamedSelection::field(
1968 None,
1969 Key::field("hello").into_with_range(),
1970 Some(SubSelection {
1971 selections: vec![NamedSelection::field(
1972 None,
1973 Key::field("world").into_with_range(),
1974 None,
1975 )],
1976 ..Default::default()
1977 }),
1978 ),
1979 &["hello"],
1980 );
1981
1982 assert_result_and_names(
1983 "hi: hello",
1984 NamedSelection::field(
1985 Some(Alias::new("hi")),
1986 Key::field("hello").into_with_range(),
1987 None,
1988 ),
1989 &["hi"],
1990 );
1991
1992 assert_result_and_names(
1993 "hi: 'hello world'",
1994 NamedSelection::field(
1995 Some(Alias::new("hi")),
1996 Key::quoted("hello world").into_with_range(),
1997 None,
1998 ),
1999 &["hi"],
2000 );
2001
2002 assert_result_and_names(
2003 "hi: hello { world }",
2004 NamedSelection::field(
2005 Some(Alias::new("hi")),
2006 Key::field("hello").into_with_range(),
2007 Some(SubSelection {
2008 selections: vec![NamedSelection::field(
2009 None,
2010 Key::field("world").into_with_range(),
2011 None,
2012 )],
2013 ..Default::default()
2014 }),
2015 ),
2016 &["hi"],
2017 );
2018
2019 assert_result_and_names(
2020 "hey: hello { world again }",
2021 NamedSelection::field(
2022 Some(Alias::new("hey")),
2023 Key::field("hello").into_with_range(),
2024 Some(SubSelection {
2025 selections: vec![
2026 NamedSelection::field(None, Key::field("world").into_with_range(), None),
2027 NamedSelection::field(None, Key::field("again").into_with_range(), None),
2028 ],
2029 ..Default::default()
2030 }),
2031 ),
2032 &["hey"],
2033 );
2034
2035 assert_result_and_names(
2036 "hey: 'hello world' { again }",
2037 NamedSelection::field(
2038 Some(Alias::new("hey")),
2039 Key::quoted("hello world").into_with_range(),
2040 Some(SubSelection {
2041 selections: vec![NamedSelection::field(
2042 None,
2043 Key::field("again").into_with_range(),
2044 None,
2045 )],
2046 ..Default::default()
2047 }),
2048 ),
2049 &["hey"],
2050 );
2051
2052 assert_result_and_names(
2053 "leggo: 'my ego'",
2054 NamedSelection::field(
2055 Some(Alias::new("leggo")),
2056 Key::quoted("my ego").into_with_range(),
2057 None,
2058 ),
2059 &["leggo"],
2060 );
2061
2062 assert_result_and_names(
2063 "'let go': 'my ego'",
2064 NamedSelection::field(
2065 Some(Alias::quoted("let go")),
2066 Key::quoted("my ego").into_with_range(),
2067 None,
2068 ),
2069 &["let go"],
2070 );
2071 }
2072
2073 #[test]
2074 fn test_selection() {
2075 assert_eq!(
2076 selection!("").strip_ranges(),
2077 JSONSelection::named(SubSelection {
2078 selections: vec![],
2079 ..Default::default()
2080 }),
2081 );
2082
2083 assert_eq!(
2084 selection!(" ").strip_ranges(),
2085 JSONSelection::named(SubSelection {
2086 selections: vec![],
2087 ..Default::default()
2088 }),
2089 );
2090
2091 assert_eq!(
2092 selection!("hello").strip_ranges(),
2093 JSONSelection::named(SubSelection {
2094 selections: vec![NamedSelection::field(
2095 None,
2096 Key::field("hello").into_with_range(),
2097 None
2098 )],
2099 ..Default::default()
2100 }),
2101 );
2102
2103 assert_eq!(
2104 selection!("$.hello").strip_ranges(),
2105 JSONSelection::path(PathSelection {
2106 path: PathList::Var(
2107 KnownVariable::Dollar.into_with_range(),
2108 PathList::Key(
2109 Key::field("hello").into_with_range(),
2110 PathList::Empty.into_with_range()
2111 )
2112 .into_with_range(),
2113 )
2114 .into_with_range(),
2115 }),
2116 );
2117
2118 {
2119 let expected = JSONSelection::named(SubSelection {
2120 selections: vec![NamedSelection {
2121 prefix: NamingPrefix::Alias(Alias::new("hi")),
2122 path: PathSelection::from_slice(
2123 &[
2124 Key::Field("hello".to_string()),
2125 Key::Field("world".to_string()),
2126 ],
2127 None,
2128 ),
2129 }],
2130 ..Default::default()
2131 });
2132
2133 assert_eq!(selection!("hi: hello.world").strip_ranges(), expected);
2134 assert_eq!(selection!("hi: hello .world").strip_ranges(), expected);
2135 assert_eq!(selection!("hi: hello. world").strip_ranges(), expected);
2136 assert_eq!(selection!("hi: hello . world").strip_ranges(), expected);
2137 assert_eq!(selection!("hi: hello.world").strip_ranges(), expected);
2138 assert_eq!(selection!("hi: hello. world").strip_ranges(), expected);
2139 assert_eq!(selection!("hi: hello .world").strip_ranges(), expected);
2140 assert_eq!(selection!("hi: hello . world ").strip_ranges(), expected);
2141 }
2142
2143 {
2144 let expected = JSONSelection::named(SubSelection {
2145 selections: vec![
2146 NamedSelection::field(None, Key::field("before").into_with_range(), None),
2147 NamedSelection {
2148 prefix: NamingPrefix::Alias(Alias::new("hi")),
2149 path: PathSelection::from_slice(
2150 &[
2151 Key::Field("hello".to_string()),
2152 Key::Field("world".to_string()),
2153 ],
2154 None,
2155 ),
2156 },
2157 NamedSelection::field(None, Key::field("after").into_with_range(), None),
2158 ],
2159 ..Default::default()
2160 });
2161
2162 assert_eq!(
2163 selection!("before hi: hello.world after").strip_ranges(),
2164 expected
2165 );
2166 assert_eq!(
2167 selection!("before hi: hello .world after").strip_ranges(),
2168 expected
2169 );
2170 assert_eq!(
2171 selection!("before hi: hello. world after").strip_ranges(),
2172 expected
2173 );
2174 assert_eq!(
2175 selection!("before hi: hello . world after").strip_ranges(),
2176 expected
2177 );
2178 assert_eq!(
2179 selection!("before hi: hello.world after").strip_ranges(),
2180 expected
2181 );
2182 assert_eq!(
2183 selection!("before hi: hello .world after").strip_ranges(),
2184 expected
2185 );
2186 assert_eq!(
2187 selection!("before hi: hello. world after").strip_ranges(),
2188 expected
2189 );
2190 assert_eq!(
2191 selection!("before hi: hello . world after").strip_ranges(),
2192 expected
2193 );
2194 }
2195
2196 {
2197 let expected = JSONSelection::named(SubSelection {
2198 selections: vec![
2199 NamedSelection::field(None, Key::field("before").into_with_range(), None),
2200 NamedSelection {
2201 prefix: NamingPrefix::Alias(Alias::new("hi")),
2202 path: PathSelection::from_slice(
2203 &[
2204 Key::Field("hello".to_string()),
2205 Key::Field("world".to_string()),
2206 ],
2207 Some(SubSelection {
2208 selections: vec![
2209 NamedSelection::field(
2210 None,
2211 Key::field("nested").into_with_range(),
2212 None,
2213 ),
2214 NamedSelection::field(
2215 None,
2216 Key::field("names").into_with_range(),
2217 None,
2218 ),
2219 ],
2220 ..Default::default()
2221 }),
2222 ),
2223 },
2224 NamedSelection::field(None, Key::field("after").into_with_range(), None),
2225 ],
2226 ..Default::default()
2227 });
2228
2229 assert_eq!(
2230 selection!("before hi: hello.world { nested names } after").strip_ranges(),
2231 expected
2232 );
2233 assert_eq!(
2234 selection!("before hi:hello.world{nested names}after").strip_ranges(),
2235 expected
2236 );
2237 assert_eq!(
2238 selection!(" before hi : hello . world { nested names } after ").strip_ranges(),
2239 expected
2240 );
2241 }
2242
2243 assert_debug_snapshot!(selection!(
2244 "
2245 # Comments are supported because we parse them as whitespace
2246 topLevelAlias: topLevelField {
2247 identifier: 'property name with spaces'
2248 'unaliased non-identifier property'
2249 'non-identifier alias': identifier
2250
2251 # This extracts the value located at the given path and applies a
2252 # selection set to it before renaming the result to pathSelection
2253 pathSelection: some.nested.path {
2254 still: yet
2255 more
2256 properties
2257 }
2258
2259 # An aliased SubSelection of fields nests the fields together
2260 # under the given alias
2261 siblingGroup: { brother sister }
2262 }"
2263 ));
2264 }
2265
2266 #[track_caller]
2267 fn check_path_selection(spec: ConnectSpec, input: &str, expected: PathSelection) {
2268 let (remainder, path_selection) =
2269 PathSelection::parse(new_span_with_spec(input, spec)).unwrap();
2270 assert!(
2271 span_is_all_spaces_or_comments(remainder.clone()),
2272 "remainder is `{:?}`",
2273 remainder.clone(),
2274 );
2275 let path_without_ranges = path_selection.strip_ranges();
2276 assert_eq!(&path_without_ranges, &expected);
2277 assert_eq!(
2278 selection!(input, spec).strip_ranges(),
2279 JSONSelection {
2280 inner: TopLevelSelection::Path(path_without_ranges),
2281 spec,
2282 },
2283 );
2284 }
2285
2286 #[rstest]
2287 #[case::v0_2(ConnectSpec::V0_2)]
2288 #[case::v0_3(ConnectSpec::V0_3)]
2289 #[case::v0_4(ConnectSpec::V0_4)]
2290 fn test_path_selection(#[case] spec: ConnectSpec) {
2291 check_path_selection(
2292 spec,
2293 "$.hello",
2294 PathSelection {
2295 path: PathList::Var(
2296 KnownVariable::Dollar.into_with_range(),
2297 PathList::Key(
2298 Key::field("hello").into_with_range(),
2299 PathList::Empty.into_with_range(),
2300 )
2301 .into_with_range(),
2302 )
2303 .into_with_range(),
2304 },
2305 );
2306
2307 {
2308 let expected = PathSelection {
2309 path: PathList::Var(
2310 KnownVariable::Dollar.into_with_range(),
2311 PathList::Key(
2312 Key::field("hello").into_with_range(),
2313 PathList::Key(
2314 Key::field("world").into_with_range(),
2315 PathList::Empty.into_with_range(),
2316 )
2317 .into_with_range(),
2318 )
2319 .into_with_range(),
2320 )
2321 .into_with_range(),
2322 };
2323 check_path_selection(spec, "$.hello.world", expected.clone());
2324 check_path_selection(spec, "$.hello .world", expected.clone());
2325 check_path_selection(spec, "$.hello. world", expected.clone());
2326 check_path_selection(spec, "$.hello . world", expected.clone());
2327 check_path_selection(spec, "$ . hello . world", expected.clone());
2328 check_path_selection(spec, " $ . hello . world ", expected);
2329 }
2330
2331 {
2332 let expected = PathSelection::from_slice(
2333 &[
2334 Key::Field("hello".to_string()),
2335 Key::Field("world".to_string()),
2336 ],
2337 None,
2338 );
2339 check_path_selection(spec, "hello.world", expected.clone());
2340 check_path_selection(spec, "hello .world", expected.clone());
2341 check_path_selection(spec, "hello. world", expected.clone());
2342 check_path_selection(spec, "hello . world", expected.clone());
2343 check_path_selection(spec, " hello . world ", expected);
2344 }
2345
2346 {
2347 let expected = PathSelection::from_slice(
2348 &[
2349 Key::Field("hello".to_string()),
2350 Key::Field("world".to_string()),
2351 ],
2352 Some(SubSelection {
2353 selections: vec![NamedSelection::field(
2354 None,
2355 Key::field("hello").into_with_range(),
2356 None,
2357 )],
2358 ..Default::default()
2359 }),
2360 );
2361 check_path_selection(spec, "hello.world{hello}", expected.clone());
2362 check_path_selection(spec, "hello.world { hello }", expected.clone());
2363 check_path_selection(spec, "hello .world { hello }", expected.clone());
2364 check_path_selection(spec, "hello. world { hello }", expected.clone());
2365 check_path_selection(spec, "hello . world { hello }", expected.clone());
2366 check_path_selection(spec, " hello . world { hello } ", expected);
2367 }
2368
2369 {
2370 let expected = PathSelection::from_slice(
2371 &[
2372 Key::Field("nested".to_string()),
2373 Key::Quoted("string literal".to_string()),
2374 Key::Quoted("property".to_string()),
2375 Key::Field("name".to_string()),
2376 ],
2377 None,
2378 );
2379 check_path_selection(
2380 spec,
2381 "nested.'string literal'.\"property\".name",
2382 expected.clone(),
2383 );
2384 check_path_selection(
2385 spec,
2386 "nested. 'string literal'.\"property\".name",
2387 expected.clone(),
2388 );
2389 check_path_selection(
2390 spec,
2391 "nested.'string literal'. \"property\".name",
2392 expected.clone(),
2393 );
2394 check_path_selection(
2395 spec,
2396 "nested.'string literal'.\"property\" .name",
2397 expected.clone(),
2398 );
2399 check_path_selection(
2400 spec,
2401 "nested.'string literal'.\"property\". name",
2402 expected.clone(),
2403 );
2404 check_path_selection(
2405 spec,
2406 " nested . 'string literal' . \"property\" . name ",
2407 expected,
2408 );
2409 }
2410
2411 {
2412 let expected = PathSelection::from_slice(
2413 &[
2414 Key::Field("nested".to_string()),
2415 Key::Quoted("string literal".to_string()),
2416 ],
2417 Some(SubSelection {
2418 selections: vec![NamedSelection::field(
2419 Some(Alias::new("leggo")),
2420 Key::quoted("my ego").into_with_range(),
2421 None,
2422 )],
2423 ..Default::default()
2424 }),
2425 );
2426
2427 check_path_selection(
2428 spec,
2429 "nested.'string literal' { leggo: 'my ego' }",
2430 expected.clone(),
2431 );
2432
2433 check_path_selection(
2434 spec,
2435 " nested . 'string literal' { leggo : 'my ego' } ",
2436 expected.clone(),
2437 );
2438
2439 check_path_selection(
2440 spec,
2441 "nested. 'string literal' { leggo: 'my ego' }",
2442 expected.clone(),
2443 );
2444
2445 check_path_selection(
2446 spec,
2447 "nested . 'string literal' { leggo: 'my ego' }",
2448 expected.clone(),
2449 );
2450 check_path_selection(
2451 spec,
2452 " nested . \"string literal\" { leggo: 'my ego' } ",
2453 expected,
2454 );
2455 }
2456
2457 {
2458 let expected = PathSelection {
2459 path: PathList::Var(
2460 KnownVariable::Dollar.into_with_range(),
2461 PathList::Key(
2462 Key::field("results").into_with_range(),
2463 PathList::Selection(SubSelection {
2464 selections: vec![NamedSelection::field(
2465 None,
2466 Key::quoted("quoted without alias").into_with_range(),
2467 Some(SubSelection {
2468 selections: vec![
2469 NamedSelection::field(
2470 None,
2471 Key::field("id").into_with_range(),
2472 None,
2473 ),
2474 NamedSelection::field(
2475 None,
2476 Key::quoted("n a m e").into_with_range(),
2477 None,
2478 ),
2479 ],
2480 ..Default::default()
2481 }),
2482 )],
2483 ..Default::default()
2484 })
2485 .into_with_range(),
2486 )
2487 .into_with_range(),
2488 )
2489 .into_with_range(),
2490 };
2491 check_path_selection(
2492 spec,
2493 "$.results{'quoted without alias'{id'n a m e'}}",
2494 expected.clone(),
2495 );
2496 check_path_selection(
2497 spec,
2498 " $ . results { 'quoted without alias' { id 'n a m e' } } ",
2499 expected,
2500 );
2501 }
2502
2503 {
2504 let expected = PathSelection {
2505 path: PathList::Var(
2506 KnownVariable::Dollar.into_with_range(),
2507 PathList::Key(
2508 Key::field("results").into_with_range(),
2509 PathList::Selection(SubSelection {
2510 selections: vec![NamedSelection::field(
2511 Some(Alias::quoted("non-identifier alias")),
2512 Key::quoted("quoted with alias").into_with_range(),
2513 Some(SubSelection {
2514 selections: vec![
2515 NamedSelection::field(
2516 None,
2517 Key::field("id").into_with_range(),
2518 None,
2519 ),
2520 NamedSelection::field(
2521 Some(Alias::quoted("n a m e")),
2522 Key::field("name").into_with_range(),
2523 None,
2524 ),
2525 ],
2526 ..Default::default()
2527 }),
2528 )],
2529 ..Default::default()
2530 })
2531 .into_with_range(),
2532 )
2533 .into_with_range(),
2534 )
2535 .into_with_range(),
2536 };
2537 check_path_selection(
2538 spec,
2539 "$.results{'non-identifier alias':'quoted with alias'{id'n a m e':name}}",
2540 expected.clone(),
2541 );
2542 check_path_selection(
2543 spec,
2544 " $ . results { 'non-identifier alias' : 'quoted with alias' { id 'n a m e': name } } ",
2545 expected,
2546 );
2547 }
2548 }
2549
2550 #[rstest]
2551 #[case::v0_2(ConnectSpec::V0_2)]
2552 #[case::v0_3(ConnectSpec::V0_3)]
2553 #[case::v0_4(ConnectSpec::V0_4)]
2554 fn test_path_selection_vars(#[case] spec: ConnectSpec) {
2555 check_path_selection(
2556 spec,
2557 "$this",
2558 PathSelection {
2559 path: PathList::Var(
2560 KnownVariable::External(Namespace::This.to_string()).into_with_range(),
2561 PathList::Empty.into_with_range(),
2562 )
2563 .into_with_range(),
2564 },
2565 );
2566
2567 check_path_selection(
2568 spec,
2569 "$",
2570 PathSelection {
2571 path: PathList::Var(
2572 KnownVariable::Dollar.into_with_range(),
2573 PathList::Empty.into_with_range(),
2574 )
2575 .into_with_range(),
2576 },
2577 );
2578
2579 check_path_selection(
2580 spec,
2581 "$this { hello }",
2582 PathSelection {
2583 path: PathList::Var(
2584 KnownVariable::External(Namespace::This.to_string()).into_with_range(),
2585 PathList::Selection(SubSelection {
2586 selections: vec![NamedSelection::field(
2587 None,
2588 Key::field("hello").into_with_range(),
2589 None,
2590 )],
2591 ..Default::default()
2592 })
2593 .into_with_range(),
2594 )
2595 .into_with_range(),
2596 },
2597 );
2598
2599 check_path_selection(
2600 spec,
2601 "$ { hello }",
2602 PathSelection {
2603 path: PathList::Var(
2604 KnownVariable::Dollar.into_with_range(),
2605 PathList::Selection(SubSelection {
2606 selections: vec![NamedSelection::field(
2607 None,
2608 Key::field("hello").into_with_range(),
2609 None,
2610 )],
2611 ..Default::default()
2612 })
2613 .into_with_range(),
2614 )
2615 .into_with_range(),
2616 },
2617 );
2618
2619 check_path_selection(
2620 spec,
2621 "$this { before alias: $args.arg after }",
2622 PathList::Var(
2623 KnownVariable::External(Namespace::This.to_string()).into_with_range(),
2624 PathList::Selection(SubSelection {
2625 selections: vec![
2626 NamedSelection::field(None, Key::field("before").into_with_range(), None),
2627 NamedSelection {
2628 prefix: NamingPrefix::Alias(Alias::new("alias")),
2629 path: PathSelection {
2630 path: PathList::Var(
2631 KnownVariable::External(Namespace::Args.to_string())
2632 .into_with_range(),
2633 PathList::Key(
2634 Key::field("arg").into_with_range(),
2635 PathList::Empty.into_with_range(),
2636 )
2637 .into_with_range(),
2638 )
2639 .into_with_range(),
2640 },
2641 },
2642 NamedSelection::field(None, Key::field("after").into_with_range(), None),
2643 ],
2644 ..Default::default()
2645 })
2646 .into_with_range(),
2647 )
2648 .into(),
2649 );
2650
2651 check_path_selection(
2652 spec,
2653 "$.nested { key injected: $args.arg }",
2654 PathSelection {
2655 path: PathList::Var(
2656 KnownVariable::Dollar.into_with_range(),
2657 PathList::Key(
2658 Key::field("nested").into_with_range(),
2659 PathList::Selection(SubSelection {
2660 selections: vec![
2661 NamedSelection::field(
2662 None,
2663 Key::field("key").into_with_range(),
2664 None,
2665 ),
2666 NamedSelection {
2667 prefix: NamingPrefix::Alias(Alias::new("injected")),
2668 path: PathSelection {
2669 path: PathList::Var(
2670 KnownVariable::External(Namespace::Args.to_string())
2671 .into_with_range(),
2672 PathList::Key(
2673 Key::field("arg").into_with_range(),
2674 PathList::Empty.into_with_range(),
2675 )
2676 .into_with_range(),
2677 )
2678 .into_with_range(),
2679 },
2680 },
2681 ],
2682 ..Default::default()
2683 })
2684 .into_with_range(),
2685 )
2686 .into_with_range(),
2687 )
2688 .into_with_range(),
2689 },
2690 );
2691
2692 check_path_selection(
2693 spec,
2694 "$args.a.b.c",
2695 PathSelection {
2696 path: PathList::Var(
2697 KnownVariable::External(Namespace::Args.to_string()).into_with_range(),
2698 PathList::from_slice(
2699 &[
2700 Key::Field("a".to_string()),
2701 Key::Field("b".to_string()),
2702 Key::Field("c".to_string()),
2703 ],
2704 None,
2705 )
2706 .into_with_range(),
2707 )
2708 .into_with_range(),
2709 },
2710 );
2711
2712 check_path_selection(
2713 spec,
2714 "root.x.y.z",
2715 PathSelection::from_slice(
2716 &[
2717 Key::Field("root".to_string()),
2718 Key::Field("x".to_string()),
2719 Key::Field("y".to_string()),
2720 Key::Field("z".to_string()),
2721 ],
2722 None,
2723 ),
2724 );
2725
2726 check_path_selection(
2727 spec,
2728 "$.data",
2729 PathSelection {
2730 path: PathList::Var(
2731 KnownVariable::Dollar.into_with_range(),
2732 PathList::Key(
2733 Key::field("data").into_with_range(),
2734 PathList::Empty.into_with_range(),
2735 )
2736 .into_with_range(),
2737 )
2738 .into_with_range(),
2739 },
2740 );
2741
2742 check_path_selection(
2743 spec,
2744 "$.data.'quoted property'.nested",
2745 PathSelection {
2746 path: PathList::Var(
2747 KnownVariable::Dollar.into_with_range(),
2748 PathList::Key(
2749 Key::field("data").into_with_range(),
2750 PathList::Key(
2751 Key::quoted("quoted property").into_with_range(),
2752 PathList::Key(
2753 Key::field("nested").into_with_range(),
2754 PathList::Empty.into_with_range(),
2755 )
2756 .into_with_range(),
2757 )
2758 .into_with_range(),
2759 )
2760 .into_with_range(),
2761 )
2762 .into_with_range(),
2763 },
2764 );
2765
2766 #[track_caller]
2767 fn check_path_parse_error(
2768 spec: ConnectSpec,
2769 input: &str,
2770 expected_offset: usize,
2771 expected_message: impl Into<String>,
2772 ) {
2773 let expected_message: String = expected_message.into();
2774 match PathSelection::parse(new_span_with_spec(input, spec)) {
2775 Ok((remainder, path)) => {
2776 panic!(
2777 "Expected error at offset {expected_offset} with message '{expected_message}', but got path {path:?} and remainder {remainder:?}",
2778 );
2779 }
2780 Err(nom::Err::Error(e) | nom::Err::Failure(e)) => {
2781 assert_eq!(&input[expected_offset..], *e.input.fragment());
2782 assert_eq!(
2786 e.input.extra,
2787 SpanExtra {
2788 spec,
2789 errors: vec![(expected_message, expected_offset)],
2790 local_vars: Vec::new(),
2791 }
2792 );
2793 }
2794 Err(e) => {
2795 panic!("Unexpected error {e:?}");
2796 }
2797 }
2798 }
2799
2800 let single_key_path_error_message =
2803 "Single-key path must be prefixed with $. to avoid ambiguity with field name";
2804 check_path_parse_error(
2805 ConnectSpec::V0_2,
2806 new_span("naked").fragment(),
2807 0,
2808 single_key_path_error_message,
2809 );
2810 check_path_parse_error(
2811 ConnectSpec::V0_2,
2812 new_span("naked { hi }").fragment(),
2813 0,
2814 single_key_path_error_message,
2815 );
2816 check_path_parse_error(
2817 ConnectSpec::V0_2,
2818 new_span(" naked { hi }").fragment(),
2819 2,
2820 single_key_path_error_message,
2821 );
2822
2823 let path_key_ambiguity_error_message =
2824 "Path selection . must be followed by key (identifier or quoted string literal)";
2825 check_path_parse_error(
2826 ConnectSpec::latest(),
2827 new_span("valid.$invalid").fragment(),
2828 5,
2829 path_key_ambiguity_error_message,
2830 );
2831 check_path_parse_error(
2832 ConnectSpec::latest(),
2833 new_span(" valid.$invalid").fragment(),
2834 7,
2835 path_key_ambiguity_error_message,
2836 );
2837 check_path_parse_error(
2838 ConnectSpec::latest(),
2839 new_span(" valid . $invalid").fragment(),
2840 8,
2841 path_key_ambiguity_error_message,
2842 );
2843
2844 assert_eq!(
2845 selection!("$").strip_ranges(),
2846 JSONSelection::path(PathSelection {
2847 path: PathList::Var(
2848 KnownVariable::Dollar.into_with_range(),
2849 PathList::Empty.into_with_range()
2850 )
2851 .into_with_range(),
2852 }),
2853 );
2854
2855 assert_eq!(
2856 selection!("$this").strip_ranges(),
2857 JSONSelection::path(PathSelection {
2858 path: PathList::Var(
2859 KnownVariable::External(Namespace::This.to_string()).into_with_range(),
2860 PathList::Empty.into_with_range()
2861 )
2862 .into_with_range(),
2863 }),
2864 );
2865
2866 assert_eq!(
2867 selection!("value: $ a { b c }").strip_ranges(),
2868 JSONSelection::named(SubSelection {
2869 selections: vec![
2870 NamedSelection {
2871 prefix: NamingPrefix::Alias(Alias::new("value")),
2872 path: PathSelection {
2873 path: PathList::Var(
2874 KnownVariable::Dollar.into_with_range(),
2875 PathList::Empty.into_with_range()
2876 )
2877 .into_with_range(),
2878 },
2879 },
2880 NamedSelection::field(
2881 None,
2882 Key::field("a").into_with_range(),
2883 Some(SubSelection {
2884 selections: vec![
2885 NamedSelection::field(
2886 None,
2887 Key::field("b").into_with_range(),
2888 None
2889 ),
2890 NamedSelection::field(
2891 None,
2892 Key::field("c").into_with_range(),
2893 None
2894 ),
2895 ],
2896 ..Default::default()
2897 }),
2898 ),
2899 ],
2900 ..Default::default()
2901 }),
2902 );
2903 assert_eq!(
2904 selection!("value: $this { b c }").strip_ranges(),
2905 JSONSelection::named(SubSelection {
2906 selections: vec![NamedSelection {
2907 prefix: NamingPrefix::Alias(Alias::new("value")),
2908 path: PathSelection {
2909 path: PathList::Var(
2910 KnownVariable::External(Namespace::This.to_string()).into_with_range(),
2911 PathList::Selection(SubSelection {
2912 selections: vec![
2913 NamedSelection::field(
2914 None,
2915 Key::field("b").into_with_range(),
2916 None
2917 ),
2918 NamedSelection::field(
2919 None,
2920 Key::field("c").into_with_range(),
2921 None
2922 ),
2923 ],
2924 ..Default::default()
2925 })
2926 .into_with_range(),
2927 )
2928 .into_with_range(),
2929 },
2930 }],
2931 ..Default::default()
2932 }),
2933 );
2934 }
2935
2936 #[test]
2937 fn test_error_snapshots_v0_2() {
2938 let spec = ConnectSpec::V0_2;
2939
2940 assert_debug_snapshot!(JSONSelection::parse_with_spec(".data", spec));
2944
2945 assert_debug_snapshot!(JSONSelection::parse_with_spec("id $.object", spec));
2950 }
2951
2952 #[test]
2953 fn test_error_snapshots_v0_3() {
2954 let spec = ConnectSpec::V0_3;
2955
2956 assert_debug_snapshot!(JSONSelection::parse_with_spec(".data", spec));
2960
2961 assert_debug_snapshot!(JSONSelection::parse_with_spec("id $.object", spec));
2966 }
2967
2968 #[test]
2969 fn test_error_snapshots_v0_4() {
2970 let spec = ConnectSpec::V0_4;
2971
2972 assert_eq!(spec, ConnectSpec::next());
2976
2977 assert_debug_snapshot!(JSONSelection::parse_with_spec(".data", spec));
2981
2982 assert_debug_snapshot!(JSONSelection::parse_with_spec("id $.object", spec));
2987 }
2988
2989 #[rstest]
2990 #[case::v0_2(ConnectSpec::V0_2)]
2991 #[case::v0_3(ConnectSpec::V0_3)]
2992 #[case::v0_4(ConnectSpec::V0_4)]
2993 fn test_path_selection_at(#[case] spec: ConnectSpec) {
2994 check_path_selection(
2995 spec,
2996 "@",
2997 PathSelection {
2998 path: PathList::Var(
2999 KnownVariable::AtSign.into_with_range(),
3000 PathList::Empty.into_with_range(),
3001 )
3002 .into_with_range(),
3003 },
3004 );
3005
3006 check_path_selection(
3007 spec,
3008 "@.a.b.c",
3009 PathSelection {
3010 path: PathList::Var(
3011 KnownVariable::AtSign.into_with_range(),
3012 PathList::from_slice(
3013 &[
3014 Key::Field("a".to_string()),
3015 Key::Field("b".to_string()),
3016 Key::Field("c".to_string()),
3017 ],
3018 None,
3019 )
3020 .into_with_range(),
3021 )
3022 .into_with_range(),
3023 },
3024 );
3025
3026 check_path_selection(
3027 spec,
3028 "@.items->first",
3029 PathSelection {
3030 path: PathList::Var(
3031 KnownVariable::AtSign.into_with_range(),
3032 PathList::Key(
3033 Key::field("items").into_with_range(),
3034 PathList::Method(
3035 WithRange::new("first".to_string(), None),
3036 None,
3037 PathList::Empty.into_with_range(),
3038 )
3039 .into_with_range(),
3040 )
3041 .into_with_range(),
3042 )
3043 .into_with_range(),
3044 },
3045 );
3046 }
3047
3048 #[rstest]
3049 #[case::v0_2(ConnectSpec::V0_2)]
3050 #[case::v0_3(ConnectSpec::V0_3)]
3051 #[case::v0_4(ConnectSpec::V0_4)]
3052 fn test_expr_path_selections(#[case] spec: ConnectSpec) {
3053 fn check_simple_lit_expr(spec: ConnectSpec, input: &str, expected: LitExpr) {
3054 check_path_selection(
3055 spec,
3056 input,
3057 PathSelection {
3058 path: PathList::Expr(
3059 expected.into_with_range(),
3060 PathList::Empty.into_with_range(),
3061 )
3062 .into_with_range(),
3063 },
3064 );
3065 }
3066
3067 check_simple_lit_expr(spec, "$(null)", LitExpr::Null);
3068
3069 check_simple_lit_expr(spec, "$(true)", LitExpr::Bool(true));
3070 check_simple_lit_expr(spec, "$(false)", LitExpr::Bool(false));
3071
3072 check_simple_lit_expr(
3073 spec,
3074 "$(1234)",
3075 LitExpr::Number("1234".parse().expect("serde_json::Number parse error")),
3076 );
3077 check_simple_lit_expr(
3078 spec,
3079 "$(1234.5678)",
3080 LitExpr::Number("1234.5678".parse().expect("serde_json::Number parse error")),
3081 );
3082
3083 check_simple_lit_expr(
3084 spec,
3085 "$('hello world')",
3086 LitExpr::String("hello world".to_string()),
3087 );
3088 check_simple_lit_expr(
3089 spec,
3090 "$(\"hello world\")",
3091 LitExpr::String("hello world".to_string()),
3092 );
3093 check_simple_lit_expr(
3094 spec,
3095 "$(\"hello \\\"world\\\"\")",
3096 LitExpr::String("hello \"world\"".to_string()),
3097 );
3098
3099 check_simple_lit_expr(
3100 spec,
3101 "$([1, 2, 3])",
3102 LitExpr::Array(
3103 vec!["1".parse(), "2".parse(), "3".parse()]
3104 .into_iter()
3105 .map(|n| {
3106 LitExpr::Number(n.expect("serde_json::Number parse error"))
3107 .into_with_range()
3108 })
3109 .collect(),
3110 ),
3111 );
3112
3113 check_simple_lit_expr(spec, "$({})", LitExpr::Object(IndexMap::default()));
3114 check_simple_lit_expr(
3115 spec,
3116 "$({ a: 1, b: 2, c: 3 })",
3117 LitExpr::Object({
3118 let mut map = IndexMap::default();
3119 for (key, value) in &[("a", "1"), ("b", "2"), ("c", "3")] {
3120 map.insert(
3121 Key::field(key).into_with_range(),
3122 LitExpr::Number(value.parse().expect("serde_json::Number parse error"))
3123 .into_with_range(),
3124 );
3125 }
3126 map
3127 }),
3128 );
3129 }
3130
3131 #[test]
3132 fn test_path_expr_with_spaces_v0_2() {
3133 assert_debug_snapshot!(selection!(
3134 " suffix : results -> slice ( $( - 1 ) -> mul ( $args . suffixLength ) ) ",
3135 ConnectSpec::V0_2
3140 ));
3141 }
3142
3143 #[test]
3144 fn test_path_expr_with_spaces_v0_3() {
3145 assert_debug_snapshot!(selection!(
3146 " suffix : results -> slice ( $( - 1 ) -> mul ( $args . suffixLength ) ) ",
3147 ConnectSpec::V0_3
3148 ));
3149 }
3150
3151 #[rstest]
3152 #[case::v0_2(ConnectSpec::V0_2)]
3153 #[case::v0_3(ConnectSpec::V0_3)]
3154 #[case::v0_4(ConnectSpec::V0_4)]
3155 fn test_path_methods(#[case] spec: ConnectSpec) {
3156 check_path_selection(
3157 spec,
3158 "data.x->or(data.y)",
3159 PathSelection {
3160 path: PathList::Key(
3161 Key::field("data").into_with_range(),
3162 PathList::Key(
3163 Key::field("x").into_with_range(),
3164 PathList::Method(
3165 WithRange::new("or".to_string(), None),
3166 Some(MethodArgs {
3167 args: vec![
3168 LitExpr::Path(PathSelection::from_slice(
3169 &[Key::field("data"), Key::field("y")],
3170 None,
3171 ))
3172 .into_with_range(),
3173 ],
3174 ..Default::default()
3175 }),
3176 PathList::Empty.into_with_range(),
3177 )
3178 .into_with_range(),
3179 )
3180 .into_with_range(),
3181 )
3182 .into_with_range(),
3183 },
3184 );
3185
3186 {
3187 fn make_dollar_key_expr(key: &str) -> WithRange<LitExpr> {
3188 WithRange::new(
3189 LitExpr::Path(PathSelection {
3190 path: PathList::Var(
3191 KnownVariable::Dollar.into_with_range(),
3192 PathList::Key(
3193 Key::field(key).into_with_range(),
3194 PathList::Empty.into_with_range(),
3195 )
3196 .into_with_range(),
3197 )
3198 .into_with_range(),
3199 }),
3200 None,
3201 )
3202 }
3203
3204 let expected = PathSelection {
3205 path: PathList::Key(
3206 Key::field("data").into_with_range(),
3207 PathList::Method(
3208 WithRange::new("query".to_string(), None),
3209 Some(MethodArgs {
3210 args: vec![
3211 make_dollar_key_expr("a"),
3212 make_dollar_key_expr("b"),
3213 make_dollar_key_expr("c"),
3214 ],
3215 ..Default::default()
3216 }),
3217 PathList::Empty.into_with_range(),
3218 )
3219 .into_with_range(),
3220 )
3221 .into_with_range(),
3222 };
3223 check_path_selection(spec, "data->query($.a, $.b, $.c)", expected.clone());
3224 check_path_selection(spec, "data->query($.a, $.b, $.c )", expected.clone());
3225 check_path_selection(spec, "data->query($.a, $.b, $.c,)", expected.clone());
3226 check_path_selection(spec, "data->query($.a, $.b, $.c ,)", expected.clone());
3227 check_path_selection(spec, "data->query($.a, $.b, $.c , )", expected);
3228 }
3229
3230 {
3231 let expected = PathSelection {
3232 path: PathList::Key(
3233 Key::field("data").into_with_range(),
3234 PathList::Key(
3235 Key::field("x").into_with_range(),
3236 PathList::Method(
3237 WithRange::new("concat".to_string(), None),
3238 Some(MethodArgs {
3239 args: vec![
3240 LitExpr::Array(vec![
3241 LitExpr::Path(PathSelection::from_slice(
3242 &[Key::field("data"), Key::field("y")],
3243 None,
3244 ))
3245 .into_with_range(),
3246 LitExpr::Path(PathSelection::from_slice(
3247 &[Key::field("data"), Key::field("z")],
3248 None,
3249 ))
3250 .into_with_range(),
3251 ])
3252 .into_with_range(),
3253 ],
3254 ..Default::default()
3255 }),
3256 PathList::Empty.into_with_range(),
3257 )
3258 .into_with_range(),
3259 )
3260 .into_with_range(),
3261 )
3262 .into_with_range(),
3263 };
3264 check_path_selection(spec, "data.x->concat([data.y, data.z])", expected.clone());
3265 check_path_selection(spec, "data.x->concat([ data.y, data.z ])", expected.clone());
3266 check_path_selection(spec, "data.x->concat([data.y, data.z,])", expected.clone());
3267 check_path_selection(
3268 spec,
3269 "data.x->concat([data.y, data.z , ])",
3270 expected.clone(),
3271 );
3272 check_path_selection(spec, "data.x->concat([data.y, data.z,],)", expected.clone());
3273 check_path_selection(spec, "data.x->concat([data.y, data.z , ] , )", expected);
3274 }
3275
3276 check_path_selection(
3277 spec,
3278 "data->method([$ { x2: x->times(2) }, $ { y2: y->times(2) }])",
3279 PathSelection {
3280 path: PathList::Key(
3281 Key::field("data").into_with_range(),
3282 PathList::Method(
3283 WithRange::new("method".to_string(), None),
3284 Some(MethodArgs {
3285 args: vec![LitExpr::Array(vec![
3286 LitExpr::Path(PathSelection {
3287 path: PathList::Var(
3288 KnownVariable::Dollar.into_with_range(),
3289 PathList::Selection(
3290 SubSelection {
3291 selections: vec![NamedSelection {
3292 prefix: NamingPrefix::Alias(Alias::new("x2")),
3293 path: PathSelection {
3294 path: PathList::Key(
3295 Key::field("x").into_with_range(),
3296 PathList::Method(
3297 WithRange::new(
3298 "times".to_string(),
3299 None,
3300 ),
3301 Some(MethodArgs {
3302 args: vec![LitExpr::Number(
3303 "2".parse().expect(
3304 "serde_json::Number parse error",
3305 ),
3306 ).into_with_range()],
3307 ..Default::default()
3308 }),
3309 PathList::Empty.into_with_range(),
3310 )
3311 .into_with_range(),
3312 )
3313 .into_with_range(),
3314 },
3315 }],
3316 ..Default::default()
3317 },
3318 )
3319 .into_with_range(),
3320 )
3321 .into_with_range(),
3322 })
3323 .into_with_range(),
3324 LitExpr::Path(PathSelection {
3325 path: PathList::Var(
3326 KnownVariable::Dollar.into_with_range(),
3327 PathList::Selection(
3328 SubSelection {
3329 selections: vec![NamedSelection {
3330 prefix: NamingPrefix::Alias(Alias::new("y2")),
3331 path: PathSelection {
3332 path: PathList::Key(
3333 Key::field("y").into_with_range(),
3334 PathList::Method(
3335 WithRange::new(
3336 "times".to_string(),
3337 None,
3338 ),
3339 Some(
3340 MethodArgs {
3341 args: vec![LitExpr::Number(
3342 "2".parse().expect(
3343 "serde_json::Number parse error",
3344 ),
3345 ).into_with_range()],
3346 ..Default::default()
3347 },
3348 ),
3349 PathList::Empty.into_with_range(),
3350 )
3351 .into_with_range(),
3352 )
3353 .into_with_range(),
3354 },
3355 }],
3356 ..Default::default()
3357 },
3358 )
3359 .into_with_range(),
3360 )
3361 .into_with_range(),
3362 })
3363 .into_with_range(),
3364 ])
3365 .into_with_range()],
3366 ..Default::default()
3367 }),
3368 PathList::Empty.into_with_range(),
3369 )
3370 .into_with_range(),
3371 )
3372 .into_with_range(),
3373 },
3374 );
3375 }
3376
3377 #[test]
3378 fn test_path_with_subselection() {
3379 assert_debug_snapshot!(selection!(
3380 r#"
3381 choices->first.message { content role }
3382 "#
3383 ));
3384
3385 assert_debug_snapshot!(selection!(
3386 r#"
3387 id
3388 created
3389 choices->first.message { content role }
3390 model
3391 "#
3392 ));
3393
3394 assert_debug_snapshot!(selection!(
3395 r#"
3396 id
3397 created
3398 choices->first.message { content role }
3399 model
3400 choices->last.message { lastContent: content }
3401 "#
3402 ));
3403
3404 assert_debug_snapshot!(JSONSelection::parse(
3405 r#"
3406 id
3407 created
3408 choices->first.message
3409 model
3410 "#
3411 ));
3412
3413 assert_debug_snapshot!(JSONSelection::parse(
3414 r#"
3415 id: $this.id
3416 $args.input {
3417 title
3418 body
3419 }
3420 "#
3421 ));
3422
3423 assert_debug_snapshot!(JSONSelection::parse(
3426 r#"
3427 $this { id }
3428 $args { $.input { title body } }
3429 "#
3430 ));
3431
3432 assert_debug_snapshot!(JSONSelection::parse(
3433 r#"
3434 # Equivalent to id: $this.id
3435 $this { id }
3436
3437 $args {
3438 __typename: $("Args")
3439
3440 # Using $. instead of just . prevents .input from
3441 # parsing as a key applied to the $("Args") string.
3442 $.input { title body }
3443
3444 extra
3445 }
3446
3447 from: $.from
3448 "#
3449 ));
3450 }
3451
3452 #[test]
3453 fn test_subselection() {
3454 fn check_parsed(input: &str, expected: SubSelection) {
3455 let (remainder, parsed) = SubSelection::parse(new_span(input)).unwrap();
3456 assert!(
3457 span_is_all_spaces_or_comments(remainder.clone()),
3458 "remainder is `{:?}`",
3459 remainder.clone(),
3460 );
3461 assert_eq!(parsed.strip_ranges(), expected);
3462 }
3463
3464 check_parsed(
3465 " { \n } ",
3466 SubSelection {
3467 selections: vec![],
3468 ..Default::default()
3469 },
3470 );
3471
3472 check_parsed(
3473 "{hello}",
3474 SubSelection {
3475 selections: vec![NamedSelection::field(
3476 None,
3477 Key::field("hello").into_with_range(),
3478 None,
3479 )],
3480 ..Default::default()
3481 },
3482 );
3483
3484 check_parsed(
3485 "{ hello }",
3486 SubSelection {
3487 selections: vec![NamedSelection::field(
3488 None,
3489 Key::field("hello").into_with_range(),
3490 None,
3491 )],
3492 ..Default::default()
3493 },
3494 );
3495
3496 check_parsed(
3497 " { padded } ",
3498 SubSelection {
3499 selections: vec![NamedSelection::field(
3500 None,
3501 Key::field("padded").into_with_range(),
3502 None,
3503 )],
3504 ..Default::default()
3505 },
3506 );
3507
3508 check_parsed(
3509 "{ hello world }",
3510 SubSelection {
3511 selections: vec![
3512 NamedSelection::field(None, Key::field("hello").into_with_range(), None),
3513 NamedSelection::field(None, Key::field("world").into_with_range(), None),
3514 ],
3515 ..Default::default()
3516 },
3517 );
3518
3519 check_parsed(
3520 "{ hello { world } }",
3521 SubSelection {
3522 selections: vec![NamedSelection::field(
3523 None,
3524 Key::field("hello").into_with_range(),
3525 Some(SubSelection {
3526 selections: vec![NamedSelection::field(
3527 None,
3528 Key::field("world").into_with_range(),
3529 None,
3530 )],
3531 ..Default::default()
3532 }),
3533 )],
3534 ..Default::default()
3535 },
3536 );
3537 }
3538
3539 #[test]
3540 fn test_external_var_paths() {
3541 fn parse(input: &str) -> PathSelection {
3542 PathSelection::parse(new_span(input))
3543 .unwrap()
3544 .1
3545 .strip_ranges()
3546 }
3547
3548 {
3549 let sel = selection!(
3550 r#"
3551 $->echo([$args.arg1, $args.arg2, @.items->first])
3552 "#
3553 )
3554 .strip_ranges();
3555 let args_arg1_path = parse("$args.arg1");
3556 let args_arg2_path = parse("$args.arg2");
3557 assert_eq!(
3558 sel.external_var_paths(),
3559 vec![&args_arg1_path, &args_arg2_path]
3560 );
3561 }
3562 {
3563 let sel = selection!(
3564 r#"
3565 $this.kind->match(
3566 ["A", $this.a],
3567 ["B", $this.b],
3568 ["C", $this.c],
3569 [@, @->to_lower_case],
3570 )
3571 "#
3572 )
3573 .strip_ranges();
3574 let this_kind_path = match &sel.inner {
3575 TopLevelSelection::Path(path) => path,
3576 _ => panic!("Expected PathSelection"),
3577 };
3578 let this_a_path = parse("$this.a");
3579 let this_b_path = parse("$this.b");
3580 let this_c_path = parse("$this.c");
3581 assert_eq!(
3582 sel.external_var_paths(),
3583 vec![this_kind_path, &this_a_path, &this_b_path, &this_c_path,]
3584 );
3585 }
3586 {
3587 let sel = selection!(
3588 r#"
3589 data.results->slice($args.start, $args.end) {
3590 id
3591 __typename: $args.type
3592 }
3593 "#
3594 )
3595 .strip_ranges();
3596 let start_path = parse("$args.start");
3597 let end_path = parse("$args.end");
3598 let args_type_path = parse("$args.type");
3599 assert_eq!(
3600 sel.external_var_paths(),
3601 vec![&start_path, &end_path, &args_type_path]
3602 );
3603 }
3604 }
3605
3606 #[test]
3607 fn test_local_var_paths() {
3608 let spec = ConnectSpec::V0_3;
3609 let name_selection = selection!(
3610 "person->as($name, @.name)->as($stray, 123)->echo({ hello: $name })",
3611 spec
3612 );
3613 let local_var_names = name_selection.local_var_names();
3614 assert_eq!(local_var_names.len(), 2);
3615 assert!(local_var_names.contains("$name"));
3616 assert!(local_var_names.contains("$stray"));
3617 }
3618
3619 #[test]
3620 fn test_ranged_locations() {
3621 fn check(input: &str, expected: JSONSelection) {
3622 let parsed = JSONSelection::parse(input).unwrap();
3623 assert_eq!(parsed, expected);
3624 }
3625
3626 check(
3627 "hello",
3628 JSONSelection::named(SubSelection {
3629 selections: vec![NamedSelection::field(
3630 None,
3631 WithRange::new(Key::field("hello"), Some(0..5)),
3632 None,
3633 )],
3634 range: Some(0..5),
3635 }),
3636 );
3637
3638 check(
3639 " hello ",
3640 JSONSelection::named(SubSelection {
3641 selections: vec![NamedSelection::field(
3642 None,
3643 WithRange::new(Key::field("hello"), Some(2..7)),
3644 None,
3645 )],
3646 range: Some(2..7),
3647 }),
3648 );
3649
3650 check(
3651 " hello { hi name }",
3652 JSONSelection::named(SubSelection {
3653 selections: vec![NamedSelection::field(
3654 None,
3655 WithRange::new(Key::field("hello"), Some(2..7)),
3656 Some(SubSelection {
3657 selections: vec![
3658 NamedSelection::field(
3659 None,
3660 WithRange::new(Key::field("hi"), Some(11..13)),
3661 None,
3662 ),
3663 NamedSelection::field(
3664 None,
3665 WithRange::new(Key::field("name"), Some(14..18)),
3666 None,
3667 ),
3668 ],
3669 range: Some(9..20),
3670 }),
3671 )],
3672 range: Some(2..20),
3673 }),
3674 );
3675
3676 check(
3677 "$args.product.id",
3678 JSONSelection::path(PathSelection {
3679 path: WithRange::new(
3680 PathList::Var(
3681 WithRange::new(
3682 KnownVariable::External(Namespace::Args.to_string()),
3683 Some(0..5),
3684 ),
3685 WithRange::new(
3686 PathList::Key(
3687 WithRange::new(Key::field("product"), Some(6..13)),
3688 WithRange::new(
3689 PathList::Key(
3690 WithRange::new(Key::field("id"), Some(14..16)),
3691 WithRange::new(PathList::Empty, Some(16..16)),
3692 ),
3693 Some(13..16),
3694 ),
3695 ),
3696 Some(5..16),
3697 ),
3698 ),
3699 Some(0..16),
3700 ),
3701 }),
3702 );
3703
3704 check(
3705 " $args . product . id ",
3706 JSONSelection::path(PathSelection {
3707 path: WithRange::new(
3708 PathList::Var(
3709 WithRange::new(
3710 KnownVariable::External(Namespace::Args.to_string()),
3711 Some(1..6),
3712 ),
3713 WithRange::new(
3714 PathList::Key(
3715 WithRange::new(Key::field("product"), Some(9..16)),
3716 WithRange::new(
3717 PathList::Key(
3718 WithRange::new(Key::field("id"), Some(19..21)),
3719 WithRange::new(PathList::Empty, Some(21..21)),
3720 ),
3721 Some(17..21),
3722 ),
3723 ),
3724 Some(7..21),
3725 ),
3726 ),
3727 Some(1..21),
3728 ),
3729 }),
3730 );
3731
3732 check(
3733 "before product:$args.product{id name}after",
3734 JSONSelection::named(SubSelection {
3735 selections: vec![
3736 NamedSelection::field(
3737 None,
3738 WithRange::new(Key::field("before"), Some(0..6)),
3739 None,
3740 ),
3741 NamedSelection {
3742 prefix: NamingPrefix::Alias(Alias {
3743 name: WithRange::new(Key::field("product"), Some(7..14)),
3744 range: Some(7..15),
3745 }),
3746 path: PathSelection {
3747 path: WithRange::new(
3748 PathList::Var(
3749 WithRange::new(
3750 KnownVariable::External(Namespace::Args.to_string()),
3751 Some(15..20),
3752 ),
3753 WithRange::new(
3754 PathList::Key(
3755 WithRange::new(Key::field("product"), Some(21..28)),
3756 WithRange::new(
3757 PathList::Selection(SubSelection {
3758 selections: vec![
3759 NamedSelection::field(
3760 None,
3761 WithRange::new(
3762 Key::field("id"),
3763 Some(29..31),
3764 ),
3765 None,
3766 ),
3767 NamedSelection::field(
3768 None,
3769 WithRange::new(
3770 Key::field("name"),
3771 Some(32..36),
3772 ),
3773 None,
3774 ),
3775 ],
3776 range: Some(28..37),
3777 }),
3778 Some(28..37),
3779 ),
3780 ),
3781 Some(20..37),
3782 ),
3783 ),
3784 Some(15..37),
3785 ),
3786 },
3787 },
3788 NamedSelection::field(
3789 None,
3790 WithRange::new(Key::field("after"), Some(37..42)),
3791 None,
3792 ),
3793 ],
3794 range: Some(0..42),
3795 }),
3796 );
3797 }
3798
3799 #[test]
3800 fn test_variable_reference_no_path() {
3801 let selection = JSONSelection::parse("$this").unwrap();
3802 let var_paths = selection.external_var_paths();
3803 assert_eq!(var_paths.len(), 1);
3804 assert_eq!(
3805 var_paths[0].variable_reference(),
3806 Some(VariableReference {
3807 namespace: VariableNamespace {
3808 namespace: Namespace::This,
3809 location: Some(0..5),
3810 },
3811 selection: {
3812 let mut selection = SelectionTrie::new();
3813 selection.add_str_path([]);
3814 selection
3815 },
3816 location: Some(0..5),
3817 })
3818 );
3819 }
3820
3821 #[test]
3822 fn test_variable_reference_with_path() {
3823 let selection = JSONSelection::parse("$this.a.b.c").unwrap();
3824 let var_paths = selection.external_var_paths();
3825 assert_eq!(var_paths.len(), 1);
3826
3827 let var_ref = var_paths[0].variable_reference().unwrap();
3828 assert_eq!(
3829 var_ref.namespace,
3830 VariableNamespace {
3831 namespace: Namespace::This,
3832 location: Some(0..5)
3833 }
3834 );
3835 assert_eq!(var_ref.selection.to_string(), "a { b { c } }");
3836 assert_eq!(var_ref.location, Some(0..11));
3837
3838 assert_eq!(
3839 var_ref.selection.key_ranges("a").collect::<Vec<_>>(),
3840 vec![6..7]
3841 );
3842 let a_trie = var_ref.selection.get("a").unwrap();
3843 assert_eq!(a_trie.key_ranges("b").collect::<Vec<_>>(), vec![8..9]);
3844 let b_trie = a_trie.get("b").unwrap();
3845 assert_eq!(b_trie.key_ranges("c").collect::<Vec<_>>(), vec![10..11]);
3846 }
3847
3848 #[test]
3849 fn test_variable_reference_nested() {
3850 let selection = JSONSelection::parse("a b { c: $this.x.y.z { d } }").unwrap();
3851 let var_paths = selection.external_var_paths();
3852 assert_eq!(var_paths.len(), 1);
3853
3854 let var_ref = var_paths[0].variable_reference().unwrap();
3855 assert_eq!(
3856 var_ref.namespace,
3857 VariableNamespace {
3858 namespace: Namespace::This,
3859 location: Some(9..14),
3860 }
3861 );
3862 assert_eq!(var_ref.selection.to_string(), "x { y { z { d } } }");
3863 assert_eq!(var_ref.location, Some(9..26));
3864
3865 assert_eq!(
3866 var_ref.selection.key_ranges("x").collect::<Vec<_>>(),
3867 vec![15..16]
3868 );
3869 let x_trie = var_ref.selection.get("x").unwrap();
3870 assert_eq!(x_trie.key_ranges("y").collect::<Vec<_>>(), vec![17..18]);
3871 let y_trie = x_trie.get("y").unwrap();
3872 assert_eq!(y_trie.key_ranges("z").collect::<Vec<_>>(), vec![19..20]);
3873 let z_trie = y_trie.get("z").unwrap();
3874 assert_eq!(z_trie.key_ranges("d").collect::<Vec<_>>(), vec![23..24]);
3875 }
3876
3877 #[test]
3878 fn test_external_var_paths_no_variable() {
3879 let selection = JSONSelection::parse("a.b.c").unwrap();
3880 let var_paths = selection.external_var_paths();
3881 assert_eq!(var_paths.len(), 0);
3882 }
3883
3884 #[test]
3885 fn test_naked_literal_path_for_connect_v0_2() {
3886 let spec = ConnectSpec::V0_2;
3887
3888 let selection_null_stringify_v0_2 = selection!("$(null->jsonStringify)", spec);
3889 assert_eq!(
3890 selection_null_stringify_v0_2.pretty_print(),
3891 "$(null->jsonStringify)"
3892 );
3893
3894 let selection_hello_slice_v0_2 = selection!("sliced: $('hello'->slice(1, 3))", spec);
3895 assert_eq!(
3896 selection_hello_slice_v0_2.pretty_print(),
3897 "sliced: $(\"hello\"->slice(1, 3))"
3898 );
3899
3900 let selection_true_not_v0_2 = selection!("true->not", spec);
3901 assert_eq!(selection_true_not_v0_2.pretty_print(), "true->not");
3902
3903 let selection_false_not_v0_2 = selection!("false->not", spec);
3904 assert_eq!(selection_false_not_v0_2.pretty_print(), "false->not");
3905
3906 let selection_object_path_v0_2 = selection!("$({ a: 123 }.a)", spec);
3907 assert_eq!(
3908 selection_object_path_v0_2.pretty_print_with_indentation(true, 0),
3909 "$({ a: 123 }.a)"
3910 );
3911
3912 let selection_array_path_v0_2 = selection!("$([1, 2, 3]->get(1))", spec);
3913 assert_eq!(
3914 selection_array_path_v0_2.pretty_print(),
3915 "$([1, 2, 3]->get(1))"
3916 );
3917
3918 assert_debug_snapshot!(selection_null_stringify_v0_2);
3919 assert_debug_snapshot!(selection_hello_slice_v0_2);
3920 assert_debug_snapshot!(selection_true_not_v0_2);
3921 assert_debug_snapshot!(selection_false_not_v0_2);
3922 assert_debug_snapshot!(selection_object_path_v0_2);
3923 assert_debug_snapshot!(selection_array_path_v0_2);
3924 }
3925
3926 #[test]
3927 fn test_optional_key_access() {
3928 let spec = ConnectSpec::V0_3;
3929
3930 check_path_selection(
3931 spec,
3932 "$.foo?.bar",
3933 PathSelection {
3934 path: PathList::Var(
3935 KnownVariable::Dollar.into_with_range(),
3936 PathList::Key(
3937 Key::field("foo").into_with_range(),
3938 PathList::Question(
3939 PathList::Key(
3940 Key::field("bar").into_with_range(),
3941 PathList::Empty.into_with_range(),
3942 )
3943 .into_with_range(),
3944 )
3945 .into_with_range(),
3946 )
3947 .into_with_range(),
3948 )
3949 .into_with_range(),
3950 },
3951 );
3952 }
3953
3954 #[test]
3955 fn test_unambiguous_single_key_paths_v0_2() {
3956 let spec = ConnectSpec::V0_2;
3957
3958 let mul_with_dollars = selection!("a->mul($.b, $.c)", spec);
3959 mul_with_dollars.if_named_else_path(
3960 |named| {
3961 panic!("Expected a path selection, got named: {named:?}");
3962 },
3963 |path| {
3964 assert_eq!(path.get_single_key(), None);
3965 assert_eq!(path.pretty_print(), "a->mul($.b, $.c)");
3966 },
3967 );
3968
3969 assert_debug_snapshot!(mul_with_dollars);
3970 }
3971
3972 #[test]
3973 fn test_invalid_single_key_paths_v0_2() {
3974 let spec = ConnectSpec::V0_2;
3975
3976 let a_plus_b_plus_c = JSONSelection::parse_with_spec("a->add(b, c)", spec);
3977 assert_eq!(a_plus_b_plus_c, Err(JSONSelectionParseError {
3978 message: "Named path selection must either begin with alias or ..., or end with subselection".to_string(),
3979 fragment: "a->add(b, c)".to_string(),
3980 offset: 0,
3981 spec: ConnectSpec::V0_2,
3982 }));
3983
3984 let sum_a_plus_b_plus_c = JSONSelection::parse_with_spec("sum: a->add(b, c)", spec);
3985 assert_eq!(
3986 sum_a_plus_b_plus_c,
3987 Err(JSONSelectionParseError {
3988 message: "nom::error::ErrorKind::Eof".to_string(),
3989 fragment: "(b, c)".to_string(),
3990 offset: 11,
3991 spec: ConnectSpec::V0_2,
3992 })
3993 );
3994 }
3995
3996 #[test]
3997 fn test_optional_method_call() {
3998 let spec = ConnectSpec::V0_3;
3999
4000 check_path_selection(
4001 spec,
4002 "$.foo?->method",
4003 PathSelection {
4004 path: PathList::Var(
4005 KnownVariable::Dollar.into_with_range(),
4006 PathList::Key(
4007 Key::field("foo").into_with_range(),
4008 PathList::Question(
4009 PathList::Method(
4010 WithRange::new("method".to_string(), None),
4011 None,
4012 PathList::Empty.into_with_range(),
4013 )
4014 .into_with_range(),
4015 )
4016 .into_with_range(),
4017 )
4018 .into_with_range(),
4019 )
4020 .into_with_range(),
4021 },
4022 );
4023 }
4024
4025 #[test]
4026 fn test_chained_optional_accesses() {
4027 let spec = ConnectSpec::V0_3;
4028
4029 check_path_selection(
4030 spec,
4031 "$.foo?.bar?.baz",
4032 PathSelection {
4033 path: PathList::Var(
4034 KnownVariable::Dollar.into_with_range(),
4035 PathList::Key(
4036 Key::field("foo").into_with_range(),
4037 PathList::Question(
4038 PathList::Key(
4039 Key::field("bar").into_with_range(),
4040 PathList::Question(
4041 PathList::Key(
4042 Key::field("baz").into_with_range(),
4043 PathList::Empty.into_with_range(),
4044 )
4045 .into_with_range(),
4046 )
4047 .into_with_range(),
4048 )
4049 .into_with_range(),
4050 )
4051 .into_with_range(),
4052 )
4053 .into_with_range(),
4054 )
4055 .into_with_range(),
4056 },
4057 );
4058 }
4059
4060 #[test]
4061 fn test_mixed_regular_and_optional_access() {
4062 let spec = ConnectSpec::V0_3;
4063
4064 check_path_selection(
4065 spec,
4066 "$.foo.bar?.baz",
4067 PathSelection {
4068 path: PathList::Var(
4069 KnownVariable::Dollar.into_with_range(),
4070 PathList::Key(
4071 Key::field("foo").into_with_range(),
4072 PathList::Key(
4073 Key::field("bar").into_with_range(),
4074 PathList::Question(
4075 PathList::Key(
4076 Key::field("baz").into_with_range(),
4077 PathList::Empty.into_with_range(),
4078 )
4079 .into_with_range(),
4080 )
4081 .into_with_range(),
4082 )
4083 .into_with_range(),
4084 )
4085 .into_with_range(),
4086 )
4087 .into_with_range(),
4088 },
4089 );
4090 }
4091
4092 #[test]
4093 fn test_invalid_sequential_question_marks() {
4094 let spec = ConnectSpec::V0_3;
4095
4096 assert_eq!(
4097 JSONSelection::parse_with_spec("baz: $.foo??.bar", spec),
4098 Err(JSONSelectionParseError {
4099 message: "nom::error::ErrorKind::Eof".to_string(),
4100 fragment: "??.bar".to_string(),
4101 offset: 10,
4102 spec,
4103 }),
4104 );
4105
4106 assert_eq!(
4107 JSONSelection::parse_with_spec("baz: $.foo?->echo(null)??.bar", spec),
4108 Err(JSONSelectionParseError {
4109 message: "nom::error::ErrorKind::Eof".to_string(),
4110 fragment: "??.bar".to_string(),
4111 offset: 23,
4112 spec,
4113 }),
4114 );
4115 }
4116
4117 #[test]
4118 fn test_invalid_infix_operator_parsing() {
4119 let spec = ConnectSpec::V0_2;
4120
4121 assert_eq!(
4122 JSONSelection::parse_with_spec("aOrB: $($.a ?? $.b)", spec),
4123 Err(JSONSelectionParseError {
4124 message: "nom::error::ErrorKind::Eof".to_string(),
4125 fragment: "($.a ?? $.b)".to_string(),
4126 offset: 7,
4127 spec,
4128 }),
4129 );
4130
4131 assert_eq!(
4132 JSONSelection::parse_with_spec("aOrB: $($.a ?! $.b)", spec),
4133 Err(JSONSelectionParseError {
4134 message: "nom::error::ErrorKind::Eof".to_string(),
4135 fragment: "($.a ?! $.b)".to_string(),
4136 offset: 7,
4137 spec,
4138 }),
4139 );
4140 }
4141
4142 #[test]
4143 fn test_optional_chaining_with_subselection() {
4144 let spec = ConnectSpec::V0_3;
4145
4146 check_path_selection(
4147 spec,
4148 "$.foo?.bar { id name }",
4149 PathSelection {
4150 path: PathList::Var(
4151 KnownVariable::Dollar.into_with_range(),
4152 PathList::Key(
4153 Key::field("foo").into_with_range(),
4154 PathList::Question(
4155 PathList::Key(
4156 Key::field("bar").into_with_range(),
4157 PathList::Selection(SubSelection {
4158 selections: vec![
4159 NamedSelection::field(
4160 None,
4161 Key::field("id").into_with_range(),
4162 None,
4163 ),
4164 NamedSelection::field(
4165 None,
4166 Key::field("name").into_with_range(),
4167 None,
4168 ),
4169 ],
4170 ..Default::default()
4171 })
4172 .into_with_range(),
4173 )
4174 .into_with_range(),
4175 )
4176 .into_with_range(),
4177 )
4178 .into_with_range(),
4179 )
4180 .into_with_range(),
4181 },
4182 );
4183 }
4184
4185 #[test]
4186 fn test_optional_method_with_arguments() {
4187 let spec = ConnectSpec::V0_3;
4188
4189 check_path_selection(
4190 spec,
4191 "$.foo?->filter('active')",
4192 PathSelection {
4193 path: PathList::Var(
4194 KnownVariable::Dollar.into_with_range(),
4195 PathList::Key(
4196 Key::field("foo").into_with_range(),
4197 PathList::Question(
4198 PathList::Method(
4199 WithRange::new("filter".to_string(), None),
4200 Some(MethodArgs {
4201 args: vec![
4202 LitExpr::String("active".to_string()).into_with_range(),
4203 ],
4204 ..Default::default()
4205 }),
4206 PathList::Empty.into_with_range(),
4207 )
4208 .into_with_range(),
4209 )
4210 .into_with_range(),
4211 )
4212 .into_with_range(),
4213 )
4214 .into_with_range(),
4215 },
4216 );
4217 }
4218
4219 #[test]
4220 fn test_unambiguous_single_key_paths_v0_3() {
4221 let spec = ConnectSpec::V0_3;
4222
4223 let mul_with_dollars = selection!("a->mul($.b, $.c)", spec);
4224 mul_with_dollars.if_named_else_path(
4225 |named| {
4226 panic!("Expected a path selection, got named: {named:?}");
4227 },
4228 |path| {
4229 assert_eq!(path.get_single_key(), None);
4230 assert_eq!(path.pretty_print(), "a->mul($.b, $.c)");
4231 },
4232 );
4233
4234 assert_debug_snapshot!(mul_with_dollars);
4235 }
4236
4237 #[test]
4238 fn test_valid_single_key_path_v0_3() {
4239 let spec = ConnectSpec::V0_3;
4240
4241 let a_plus_b_plus_c = JSONSelection::parse_with_spec("a->add(b, c)", spec);
4242 if let Ok(selection) = a_plus_b_plus_c {
4243 selection.if_named_else_path(
4244 |named| {
4245 panic!("Expected a path selection, got named: {named:?}");
4246 },
4247 |path| {
4248 assert_eq!(path.pretty_print(), "a->add(b, c)");
4249 assert_eq!(path.get_single_key(), None);
4250 },
4251 );
4252 assert_debug_snapshot!(selection);
4253 } else {
4254 panic!("Expected a valid selection, got error: {a_plus_b_plus_c:?}");
4255 }
4256 }
4257
4258 #[test]
4259 fn test_valid_single_key_path_with_alias_v0_3() {
4260 let spec = ConnectSpec::V0_3;
4261
4262 let sum_a_plus_b_plus_c = JSONSelection::parse_with_spec("sum: a->add(b, c)", spec);
4263 if let Ok(selection) = sum_a_plus_b_plus_c {
4264 selection.if_named_else_path(
4265 |named| {
4266 for selection in named.selections_iter() {
4267 assert_eq!(selection.pretty_print(), "sum: a->add(b, c)");
4268 assert_eq!(
4269 selection.get_single_key().map(|key| key.as_str()),
4270 Some("sum")
4271 );
4272 }
4273 },
4274 |path| {
4275 panic!("Expected any number of named selections, got path: {path:?}");
4276 },
4277 );
4278 assert_debug_snapshot!(selection);
4279 } else {
4280 panic!("Expected a valid selection, got error: {sum_a_plus_b_plus_c:?}");
4281 }
4282 }
4283
4284 #[test]
4285 fn test_disallowed_spread_syntax_error() {
4286 assert_eq!(
4287 JSONSelection::parse_with_spec("id ...names", ConnectSpec::V0_2),
4288 Err(JSONSelectionParseError {
4289 message: "nom::error::ErrorKind::Eof".to_string(),
4290 fragment: "...names".to_string(),
4291 offset: 3,
4292 spec: ConnectSpec::V0_2,
4293 }),
4294 );
4295
4296 assert_eq!(
4297 JSONSelection::parse_with_spec("id ...names", ConnectSpec::V0_3),
4298 Err(JSONSelectionParseError {
4299 message: "Spread syntax (...) is not supported in connect/v0.3 (use connect/v0.4)"
4300 .to_string(),
4301 fragment: "...names".to_string(),
4305 offset: 3,
4306 spec: ConnectSpec::V0_3,
4307 }),
4308 );
4309
4310 }
4313
4314 #[cfg(test)]
4316 mod spread_parsing {
4317 use crate::connectors::ConnectSpec;
4318 use crate::connectors::json_selection::PrettyPrintable;
4319 use crate::selection;
4320
4321 #[track_caller]
4322 pub(super) fn check(spec: ConnectSpec, input: &str, expected_pretty: &str) {
4323 let selection = selection!(input, spec);
4324 assert_eq!(selection.pretty_print(), expected_pretty);
4325 }
4326 }
4327
4328 #[test]
4329 fn test_basic_spread_parsing_one_field() {
4330 let spec = ConnectSpec::V0_4;
4331 let expected = "... a";
4332 spread_parsing::check(spec, "...a", expected);
4333 spread_parsing::check(spec, "... a", expected);
4334 spread_parsing::check(spec, "...a ", expected);
4335 spread_parsing::check(spec, "... a ", expected);
4336 spread_parsing::check(spec, " ... a ", expected);
4337 spread_parsing::check(spec, "...\na", expected);
4338 assert_debug_snapshot!(selection!("...a", spec));
4339 }
4340
4341 #[test]
4342 fn test_spread_parsing_spread_a_spread_b() {
4343 let spec = ConnectSpec::V0_4;
4344 let expected = "... a\n... b";
4345 spread_parsing::check(spec, "...a...b", expected);
4346 spread_parsing::check(spec, "... a ... b", expected);
4347 spread_parsing::check(spec, "... a ...b", expected);
4348 spread_parsing::check(spec, "... a ... b ", expected);
4349 spread_parsing::check(spec, " ... a ... b ", expected);
4350 assert_debug_snapshot!(selection!("...a...b", spec));
4351 }
4352
4353 #[test]
4354 fn test_spread_parsing_a_spread_b() {
4355 let spec = ConnectSpec::V0_4;
4356 let expected = "a\n... b";
4357 spread_parsing::check(spec, "a...b", expected);
4358 spread_parsing::check(spec, "a ... b", expected);
4359 spread_parsing::check(spec, "a\n...b", expected);
4360 spread_parsing::check(spec, "a\n...\nb", expected);
4361 spread_parsing::check(spec, "a...\nb", expected);
4362 spread_parsing::check(spec, " a ... b", expected);
4363 spread_parsing::check(spec, " a ...b", expected);
4364 spread_parsing::check(spec, " a ... b ", expected);
4365 assert_debug_snapshot!(selection!("a...b", spec));
4366 }
4367
4368 #[test]
4369 fn test_spread_parsing_spread_a_b() {
4370 let spec = ConnectSpec::V0_4;
4371 let expected = "... a\nb";
4372 spread_parsing::check(spec, "...a b", expected);
4373 spread_parsing::check(spec, "... a b", expected);
4374 spread_parsing::check(spec, "... a b ", expected);
4375 spread_parsing::check(spec, "... a\nb", expected);
4376 spread_parsing::check(spec, "... a\n b", expected);
4377 spread_parsing::check(spec, " ... a b ", expected);
4378 assert_debug_snapshot!(selection!("...a b", spec));
4379 }
4380
4381 #[test]
4382 fn test_spread_parsing_spread_a_b_c() {
4383 let spec = ConnectSpec::V0_4;
4384 let expected = "... a\nb\nc";
4385 spread_parsing::check(spec, "...a b c", expected);
4386 spread_parsing::check(spec, "... a b c", expected);
4387 spread_parsing::check(spec, "... a b c ", expected);
4388 spread_parsing::check(spec, "... a\nb\nc", expected);
4389 spread_parsing::check(spec, "... a\nb\n c", expected);
4390 spread_parsing::check(spec, " ... a b c ", expected);
4391 spread_parsing::check(spec, "...\na b c", expected);
4392 assert_debug_snapshot!(selection!("...a b c", spec));
4393 }
4394
4395 #[test]
4396 fn test_spread_parsing_spread_spread_a_sub_b() {
4397 let spec = ConnectSpec::V0_4;
4398 let expected = "... a {\n b\n}";
4399 spread_parsing::check(spec, "...a{b}", expected);
4400 spread_parsing::check(spec, "... a { b }", expected);
4401 spread_parsing::check(spec, "...a { b }", expected);
4402 spread_parsing::check(spec, "... a { b } ", expected);
4403 spread_parsing::check(spec, "... a\n{ b }", expected);
4404 spread_parsing::check(spec, "... a\n{b}", expected);
4405 spread_parsing::check(spec, " ... a { b } ", expected);
4406 spread_parsing::check(spec, "...\na { b }", expected);
4407 assert_debug_snapshot!(selection!("...a{b}", spec));
4408 }
4409
4410 #[test]
4411 fn test_spread_parsing_spread_a_sub_b_c() {
4412 let spec = ConnectSpec::V0_4;
4413 let expected = "... a {\n b\n c\n}";
4414 spread_parsing::check(spec, "...a{b c}", expected);
4415 spread_parsing::check(spec, "... a { b c }", expected);
4416 spread_parsing::check(spec, "...a { b c }", expected);
4417 spread_parsing::check(spec, "... a { b c } ", expected);
4418 spread_parsing::check(spec, "... a\n{ b c }", expected);
4419 spread_parsing::check(spec, "... a\n{b c}", expected);
4420 spread_parsing::check(spec, " ... a { b c } ", expected);
4421 spread_parsing::check(spec, "...\na { b c }", expected);
4422 spread_parsing::check(spec, "...\na { b\nc }", expected);
4423 assert_debug_snapshot!(selection!("...a{b c}", spec));
4424 }
4425
4426 #[test]
4427 fn test_spread_parsing_spread_a_sub_b_spread_c() {
4428 let spec = ConnectSpec::V0_4;
4429 let expected = "... a {\n b\n ... c\n}";
4430 spread_parsing::check(spec, "...a{b...c}", expected);
4431 spread_parsing::check(spec, "... a { b ... c }", expected);
4432 spread_parsing::check(spec, "...a { b ... c }", expected);
4433 spread_parsing::check(spec, "... a { b ... c } ", expected);
4434 spread_parsing::check(spec, "... a\n{ b ... c }", expected);
4435 spread_parsing::check(spec, "... a\n{b ... c}", expected);
4436 spread_parsing::check(spec, " ... a { b ... c } ", expected);
4437 spread_parsing::check(spec, "...\na { b ... c }", expected);
4438 spread_parsing::check(spec, "...\na {b ...\nc }", expected);
4439 assert_debug_snapshot!(selection!("...a{b...c}", spec));
4440 }
4441
4442 #[test]
4443 fn test_spread_parsing_spread_a_sub_b_spread_c_d() {
4444 let spec = ConnectSpec::V0_4;
4445 let expected = "... a {\n b\n ... c\n d\n}";
4446 spread_parsing::check(spec, "...a{b...c d}", expected);
4447 spread_parsing::check(spec, "... a { b ... c d }", expected);
4448 spread_parsing::check(spec, "...a { b ... c d }", expected);
4449 spread_parsing::check(spec, "... a { b ... c d } ", expected);
4450 spread_parsing::check(spec, "... a\n{ b ... c d }", expected);
4451 spread_parsing::check(spec, "... a\n{b ... c d}", expected);
4452 spread_parsing::check(spec, " ... a { b ... c d } ", expected);
4453 spread_parsing::check(spec, "...\na { b ... c d }", expected);
4454 spread_parsing::check(spec, "...\na {b ...\nc d }", expected);
4455 assert_debug_snapshot!(selection!("...a{b...c d}", spec));
4456 }
4457
4458 #[test]
4459 fn test_spread_parsing_spread_a_sub_spread_b_c_d_spread_e() {
4460 let spec = ConnectSpec::V0_4;
4461 let expected = "... a {\n ... b\n c\n d\n ... e\n}";
4462 spread_parsing::check(spec, "...a{...b c d...e}", expected);
4463 spread_parsing::check(spec, "... a { ... b c d ... e }", expected);
4464 spread_parsing::check(spec, "...a { ... b c d ... e }", expected);
4465 spread_parsing::check(spec, "... a { ... b c d ... e } ", expected);
4466 spread_parsing::check(spec, "... a\n{ ... b c d ... e }", expected);
4467 spread_parsing::check(spec, "... a\n{... b c d ... e}", expected);
4468 spread_parsing::check(spec, " ... a { ... b c d ... e } ", expected);
4469 spread_parsing::check(spec, "...\na { ... b c d ... e }", expected);
4470 spread_parsing::check(spec, "...\na {...\nb\nc d ...\ne }", expected);
4471 assert_debug_snapshot!(selection!("...a{...b c d...e}", spec));
4472 }
4473
4474 #[test]
4475 fn should_parse_null_coalescing_in_connect_0_3() {
4476 assert!(JSONSelection::parse_with_spec("sum: $(a ?? b)", ConnectSpec::V0_3).is_ok());
4477 assert!(JSONSelection::parse_with_spec("sum: $(a ?! b)", ConnectSpec::V0_3).is_ok());
4478 }
4479
4480 #[test]
4481 fn should_not_parse_null_coalescing_in_connect_0_2() {
4482 assert!(JSONSelection::parse_with_spec("sum: $(a ?? b)", ConnectSpec::V0_2).is_err());
4483 assert!(JSONSelection::parse_with_spec("sum: $(a ?! b)", ConnectSpec::V0_2).is_err());
4484 }
4485
4486 #[test]
4487 fn should_not_parse_mixed_operators_in_same_expression() {
4488 let result = JSONSelection::parse_with_spec("sum: $(a ?? b ?! c)", ConnectSpec::V0_3);
4489
4490 let err = result.expect_err("Expected parse error for mixed operators ?? and ?!");
4491 assert_eq!(
4492 err.message,
4493 "Found mixed operators ?? and ?!. You can only chain operators of the same kind."
4494 );
4495
4496 let result2 = JSONSelection::parse_with_spec("sum: $(a ?! b ?? c)", ConnectSpec::V0_3);
4498 let err2 = result2.expect_err("Expected parse error for mixed operators ?! and ??");
4499 assert_eq!(
4500 err2.message,
4501 "Found mixed operators ?! and ??. You can only chain operators of the same kind."
4502 );
4503 }
4504
4505 #[test]
4506 fn should_parse_mixed_operators_in_nested_expression() {
4507 let result = JSONSelection::parse_with_spec("sum: $(a ?? $(b ?! c))", ConnectSpec::V0_3);
4508
4509 assert!(result.is_ok());
4510 }
4511
4512 #[test]
4513 fn should_parse_local_vars_as_such() {
4514 let spec = ConnectSpec::V0_3;
4515 let all_local = selection!("$->as($root, @.data)->echo([$root, $root])", spec);
4518 assert!(all_local.external_var_paths().is_empty());
4519 assert_debug_snapshot!(all_local);
4520
4521 let ext = selection!("$->as($root, @.data)->echo([$root, $ext])", spec);
4523 let external_vars = ext.external_var_paths();
4524 assert_eq!(external_vars.len(), 1);
4525
4526 for ext_var in &external_vars {
4527 match ext_var.path.as_ref() {
4528 PathList::Var(var, _) => match var.as_ref() {
4529 KnownVariable::External(var_name) => {
4530 assert_eq!(var_name, "$ext");
4531 }
4532 _ => panic!("Expected external variable, got: {var:?}"),
4533 },
4534 _ => panic!(
4535 "Expected variable at start of path, got: {:?}",
4536 &ext_var.path
4537 ),
4538 };
4539 }
4540
4541 assert_debug_snapshot!(ext);
4542 }
4543}