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