1use std::fmt::Display;
2use std::hash::Hash;
3use std::str::FromStr;
4
5use apollo_compiler::collections::IndexSet;
6use itertools::Itertools;
7use nom::IResult;
8use nom::Input;
9use nom::Parser;
10use nom::branch::alt;
11use nom::character::complete::char;
12use nom::character::complete::one_of;
13use nom::combinator::all_consuming;
14use nom::combinator::map;
15use nom::combinator::opt;
16use nom::combinator::recognize;
17use nom::error::ParseError;
18use nom::multi::many0;
19use nom::sequence::pair;
20use nom::sequence::preceded;
21use nom::sequence::terminated;
22use serde_json_bytes::Value as JSON;
23
24use super::helpers::spaces_or_comments;
25use super::helpers::vec_push;
26use super::known_var::KnownVariable;
27use super::lit_expr::LitExpr;
28use super::location::OffsetRange;
29use super::location::Ranged;
30use super::location::Span;
31use super::location::SpanExtra;
32use super::location::WithRange;
33use super::location::merge_ranges;
34use super::location::new_span_with_spec;
35use super::location::ranged_span;
36use crate::connectors::ConnectSpec;
37use crate::connectors::Namespace;
38use crate::connectors::json_selection::location::get_connect_spec;
39use crate::connectors::json_selection::methods::ArrowMethod;
40use crate::connectors::variable::VariableNamespace;
41use crate::connectors::variable::VariableReference;
42
43pub(super) type ParseResult<'a, T> = IResult<Span<'a>, T>;
51
52pub(super) fn nom_error_message(
55 suffix: Span,
56 message: impl Into<String>,
62) -> nom::Err<nom::error::Error<Span>> {
63 let offset = suffix.location_offset();
64 nom::Err::Error(nom::error::Error::from_error_kind(
65 suffix.map_extra(|extra| SpanExtra {
66 errors: vec_push(extra.errors, (message.into(), offset)),
67 ..extra
68 }),
69 nom::error::ErrorKind::IsNot,
70 ))
71}
72
73pub(super) fn nom_fail_message(
78 suffix: Span,
79 message: impl Into<String>,
80) -> nom::Err<nom::error::Error<Span>> {
81 let offset = suffix.location_offset();
82 nom::Err::Failure(nom::error::Error::from_error_kind(
83 suffix.map_extra(|extra| SpanExtra {
84 errors: vec_push(extra.errors, (message.into(), offset)),
85 ..extra
86 }),
87 nom::error::ErrorKind::IsNot,
88 ))
89}
90
91pub(crate) trait VarPaths {
92 fn var_paths(&self) -> Vec<&PathSelection>;
97
98 fn external_var_paths(&self) -> Vec<&PathSelection> {
99 self.var_paths()
100 .into_iter()
101 .filter(|var_path| {
102 if let PathList::Var(known_var, _) = var_path.path.as_ref() {
103 matches!(known_var.as_ref(), KnownVariable::External(_))
104 } else {
105 false
106 }
107 })
108 .collect()
109 }
110
111 fn local_var_names(&self) -> IndexSet<String> {
114 self.var_paths()
115 .into_iter()
116 .flat_map(|var_path| {
117 if let PathList::Var(known_var, _) = var_path.path.as_ref() {
118 match known_var.as_ref() {
119 KnownVariable::Local(var_name) => Some(var_name.to_string()),
120 _ => None,
121 }
122 } else {
123 None
124 }
125 })
126 .collect()
127 }
128}
129
130#[derive(Debug, PartialEq, Eq, Clone)]
134pub struct JSONSelection {
135 pub(crate) inner: TopLevelSelection,
136 pub spec: ConnectSpec,
137}
138
139#[derive(Debug, PartialEq, Eq, Clone)]
147pub(crate) enum TopLevelSelection {
148 Named(SubSelection),
154 Value(WithRange<LitExpr>),
160}
161
162#[derive(thiserror::Error, Debug, PartialEq, Eq, Clone)]
166#[error("{message}: {fragment}")]
167pub struct JSONSelectionParseError {
168 pub message: String,
173
174 pub fragment: String,
177
178 pub offset: usize,
183
184 pub spec: ConnectSpec,
186}
187
188impl JSONSelection {
189 pub fn spec(&self) -> ConnectSpec {
190 self.spec
191 }
192
193 pub fn named(sub: SubSelection) -> Self {
194 Self {
195 inner: TopLevelSelection::Named(sub),
196 spec: ConnectSpec::latest(),
197 }
198 }
199
200 pub fn path(path: PathSelection) -> Self {
201 let range = path.range();
202 Self {
203 inner: TopLevelSelection::Value(WithRange::new(LitExpr::Path(path), range)),
204 spec: ConnectSpec::latest(),
205 }
206 }
207
208 pub(crate) fn top_level(&self) -> &TopLevelSelection {
215 &self.inner
216 }
217
218 pub fn empty() -> Self {
219 Self {
220 inner: TopLevelSelection::Named(SubSelection::default()),
221 spec: ConnectSpec::latest(),
222 }
223 }
224
225 pub fn structural_eq(&self, other: &Self) -> bool {
231 self.inner == other.inner
232 }
233
234 pub fn is_empty(&self) -> bool {
235 match &self.inner {
236 TopLevelSelection::Named(subselect) => subselect.selections.is_empty(),
237 TopLevelSelection::Value(lit) => match lit.as_ref() {
238 LitExpr::Path(path) => *path.path == PathList::Empty,
239 _ => false,
243 },
244 }
245 }
246
247 pub fn parse(input: &str) -> Result<Self, JSONSelectionParseError> {
253 JSONSelection::parse_with_spec(input, ConnectSpec::latest())
254 }
255
256 pub fn parse_with_spec(
257 input: &str,
258 spec: ConnectSpec,
259 ) -> Result<Self, JSONSelectionParseError> {
260 let span = new_span_with_spec(input, spec);
261
262 match JSONSelection::parse_span(span) {
263 Ok((remainder, selection)) => {
264 let fragment = remainder.fragment();
265 let produced_errors = !remainder.extra.errors.is_empty();
266 if fragment.is_empty() && !produced_errors {
267 Ok(selection)
268 } else {
269 let mut message = remainder
270 .extra
271 .errors
272 .iter()
273 .map(|(msg, _offset)| msg.as_str())
274 .collect::<Vec<_>>()
275 .join("\n");
276
277 let (error_offset, error_fragment) =
279 if let Some((_, first_error_offset)) = remainder.extra.errors.first() {
280 let error_span =
281 new_span_with_spec(input, spec).take_from(*first_error_offset);
282 (
283 error_span.location_offset(),
284 error_span.fragment().to_string(),
285 )
286 } else {
287 (remainder.location_offset(), fragment.to_string())
288 };
289
290 if !fragment.is_empty() {
291 message
292 .push_str(&format!("\nUnexpected trailing characters: {}", fragment));
293 }
294 Err(JSONSelectionParseError {
295 message,
296 fragment: error_fragment,
297 offset: error_offset,
298 spec: remainder.extra.spec,
299 })
300 }
301 }
302
303 Err(e) => match e {
304 nom::Err::Error(e) | nom::Err::Failure(e) => Err(JSONSelectionParseError {
305 message: if e.input.extra.errors.is_empty() {
306 format!("nom::error::ErrorKind::{:?}", e.code)
307 } else {
308 e.input
309 .extra
310 .errors
311 .iter()
312 .map(|(msg, _offset)| msg.clone())
313 .join("\n")
314 },
315 fragment: e.input.fragment().to_string(),
316 offset: e.input.location_offset(),
317 spec: e.input.extra.spec,
318 }),
319
320 nom::Err::Incomplete(_) => unreachable!("nom::Err::Incomplete not expected here"),
321 },
322 }
323 }
324
325 fn parse_span(input: Span) -> ParseResult<Self> {
326 match get_connect_spec(&input) {
327 ConnectSpec::V0_1 | ConnectSpec::V0_2 => Self::parse_span_v0_2(input),
328 ConnectSpec::V0_3 => Self::parse_span_v0_3(input),
329 ConnectSpec::V0_4 => Self::parse_span_v0_4(input),
330 }
331 }
332
333 fn parse_span_v0_2(input: Span) -> ParseResult<Self> {
334 let spec = get_connect_spec(&input);
335
336 match alt((
337 all_consuming(terminated(
338 map(PathSelection::parse, |path| {
339 let range = path.range();
340 Self {
341 inner: TopLevelSelection::Value(WithRange::new(LitExpr::Path(path), range)),
342 spec,
343 }
344 }),
345 spaces_or_comments,
349 )),
350 all_consuming(terminated(
351 map(SubSelection::parse_naked, |sub| Self {
352 inner: TopLevelSelection::Named(sub),
353 spec,
354 }),
355 spaces_or_comments,
363 )),
364 ))
365 .parse(input)
366 {
367 Ok((remainder, selection)) => {
368 if remainder.fragment().is_empty() {
369 Ok((remainder, selection))
370 } else {
371 Err(nom_fail_message(
372 remainder,
378 "Unexpected trailing characters",
379 ))
380 }
381 }
382 Err(e) => Err(e),
383 }
384 }
385
386 fn parse_span_v0_3(input: Span) -> ParseResult<Self> {
387 let spec = get_connect_spec(&input);
388
389 match all_consuming(terminated(
390 map(SubSelection::parse_naked, |sub| {
391 if let (1, Some(only)) = (sub.selections.len(), sub.selections.first()) {
392 if only.is_anonymous() || matches!(only.prefix, NamingPrefix::Spread(None)) {
416 return Self {
417 inner: TopLevelSelection::Value(only.path.clone()),
418 spec,
419 };
420 }
421 }
422 Self {
423 inner: TopLevelSelection::Named(sub),
424 spec,
425 }
426 }),
427 spaces_or_comments,
432 ))
433 .parse(input)
434 {
435 Ok((remainder, selection)) => {
436 if remainder.fragment().is_empty() {
437 Ok((remainder, selection))
438 } else {
439 Err(nom_fail_message(
440 remainder,
446 "Unexpected trailing characters",
447 ))
448 }
449 }
450 Err(e) => Err(e),
451 }
452 }
453
454 fn parse_span_v0_4(input: Span) -> ParseResult<Self> {
470 let spec = get_connect_spec(&input);
471
472 fn lit_expr_top_level(input: Span) -> ParseResult<JSONSelection> {
473 let spec = get_connect_spec(&input);
474 let (input, _) = spaces_or_comments(input)?;
475 let (remainder, lit) = LitExpr::parse(input)?;
476 let (after_ws, _) = spaces_or_comments(remainder.clone())?;
481 if !after_ws.fragment().is_empty() {
482 return Err(nom::Err::Error(nom::error::Error::from_error_kind(
483 remainder,
484 nom::error::ErrorKind::Verify,
485 )));
486 }
487
488 if let LitExpr::Path(path) = lit.as_ref()
494 && path.is_single_key()
495 {
496 return Err(nom::Err::Error(nom::error::Error::from_error_kind(
497 remainder,
498 nom::error::ErrorKind::Verify,
499 )));
500 }
501
502 Ok((
508 remainder,
509 JSONSelection {
510 inner: TopLevelSelection::Value(lit),
511 spec,
512 },
513 ))
514 }
515
516 let naked_named = map(SubSelection::parse_naked, |sub| {
517 if let (1, Some(only)) = (sub.selections.len(), sub.selections.first())
518 && (only.is_anonymous() || matches!(only.prefix, NamingPrefix::Spread(None)))
519 {
520 return Self {
521 inner: TopLevelSelection::Value(only.path.clone()),
522 spec,
523 };
524 }
525 Self {
526 inner: TopLevelSelection::Named(sub),
527 spec,
528 }
529 });
530
531 match all_consuming(terminated(
532 alt((lit_expr_top_level, naked_named)),
533 spaces_or_comments,
534 ))
535 .parse(input)
536 {
537 Ok((remainder, selection)) => {
538 if remainder.fragment().is_empty() {
539 Ok((remainder, selection))
540 } else {
541 Err(nom_fail_message(
542 remainder,
543 "Unexpected trailing characters",
544 ))
545 }
546 }
547 Err(e) => Err(e),
548 }
549 }
550
551 pub(crate) fn next_subselection(&self) -> Option<&SubSelection> {
552 match &self.inner {
553 TopLevelSelection::Named(subselect) => Some(subselect),
554 TopLevelSelection::Value(lit) => lit.as_ref().next_subselection(),
555 }
556 }
557
558 #[allow(unused)]
559 pub(crate) fn next_mut_subselection(&mut self) -> Option<&mut SubSelection> {
560 match &mut self.inner {
561 TopLevelSelection::Named(subselect) => Some(subselect),
562 TopLevelSelection::Value(lit) => lit.as_mut().next_mut_subselection(),
563 }
564 }
565
566 pub fn variable_references(&self) -> impl Iterator<Item = VariableReference<Namespace>> + '_ {
567 self.external_var_paths()
568 .into_iter()
569 .flat_map(|var_path| var_path.variable_reference())
570 }
571}
572
573impl VarPaths for JSONSelection {
574 fn var_paths(&self) -> Vec<&PathSelection> {
575 match &self.inner {
576 TopLevelSelection::Named(subselect) => subselect.var_paths(),
577 TopLevelSelection::Value(lit) => lit.as_ref().var_paths(),
578 }
579 }
580}
581
582#[derive(Debug, PartialEq, Eq, Clone)]
586pub struct NamedSelection {
587 pub(super) prefix: NamingPrefix,
588 pub(crate) path: WithRange<LitExpr>,
596}
597
598#[derive(Debug, PartialEq, Eq, Clone)]
599pub(super) enum NamingPrefix {
600 Alias(Alias),
603 Spread(OffsetRange),
608 None,
618}
619
620impl Ranged for NamedSelection {
625 fn range(&self) -> OffsetRange {
626 let alias_or_spread_range = match &self.prefix {
627 NamingPrefix::None => None,
628 NamingPrefix::Alias(alias) => alias.range(),
629 NamingPrefix::Spread(range) => range.clone(),
630 };
631 merge_ranges(alias_or_spread_range, self.path.range())
632 }
633}
634
635fn path_value(path: PathSelection) -> WithRange<LitExpr> {
639 let range = path.range();
640 WithRange::new(LitExpr::Path(path), range)
641}
642
643impl NamedSelection {
644 pub(super) fn has_single_output_key(&self) -> bool {
645 self.get_single_key().is_some()
646 }
647
648 pub(super) fn get_single_key(&self) -> Option<&WithRange<Key>> {
649 match &self.prefix {
650 NamingPrefix::None => match self.path.as_ref() {
651 LitExpr::Path(path) => path.get_single_key(),
652 _ => None,
655 },
656 NamingPrefix::Spread(_) => None,
657 NamingPrefix::Alias(alias) => Some(&alias.name),
658 }
659 }
660
661 pub(super) fn is_anonymous(&self) -> bool {
662 match &self.prefix {
663 NamingPrefix::None => match self.path.as_ref() {
664 LitExpr::Path(path) => path.is_anonymous(),
665 _ => true,
669 },
670 NamingPrefix::Alias(_) => false,
671 NamingPrefix::Spread(_) => false,
672 }
673 }
674
675 pub(super) fn field(
676 alias: Option<Alias>,
677 name: WithRange<Key>,
678 selection: Option<SubSelection>,
679 ) -> Self {
680 let name_range = name.range();
681 let tail = if let Some(selection) = selection.as_ref() {
682 WithRange::new(PathList::Selection(selection.clone()), selection.range())
683 } else {
684 let empty_range = name_range.as_ref().map(|range| range.end..range.end);
687 WithRange::new(PathList::Empty, empty_range)
688 };
689 let tail_range = tail.range();
690 let name_tail_range = merge_ranges(name_range, tail_range);
691 let prefix = if let Some(alias) = alias {
692 NamingPrefix::Alias(alias)
693 } else {
694 NamingPrefix::None
695 };
696 Self {
697 prefix,
698 path: path_value(PathSelection {
699 path: WithRange::new(PathList::Key(name, tail), name_tail_range),
700 }),
701 }
702 }
703
704 pub(crate) fn parse(input: Span) -> ParseResult<Self> {
705 match get_connect_spec(&input) {
706 ConnectSpec::V0_1 | ConnectSpec::V0_2 => Self::parse_v0_2(input),
707 ConnectSpec::V0_3 => Self::parse_v0_3(input),
708 ConnectSpec::V0_4 => Self::parse_v0_4(input),
709 }
710 }
711
712 pub(crate) fn parse_v0_2(input: Span) -> ParseResult<Self> {
713 alt((
714 Self::parse_path,
723 Self::parse_field,
724 Self::parse_group,
725 ))
726 .parse(input)
727 }
728
729 fn parse_field(input: Span) -> ParseResult<Self> {
730 (
731 opt(Alias::parse),
732 Key::parse,
733 spaces_or_comments,
734 opt(SubSelection::parse),
735 )
736 .parse(input)
737 .map(|(remainder, (alias, name, _, selection))| {
738 (remainder, Self::field(alias, name, selection))
739 })
740 }
741
742 fn parse_path(input: Span) -> ParseResult<Self> {
744 if let Ok((remainder, alias)) = Alias::parse(input.clone()) {
745 match PathSelection::parse(remainder) {
746 Ok((remainder, path)) => Ok((
747 remainder,
748 Self {
749 prefix: NamingPrefix::Alias(alias),
750 path: path_value(path),
751 },
752 )),
753 Err(nom::Err::Failure(e)) => Err(nom::Err::Failure(e)),
754 Err(_) => Err(nom_error_message(
755 input.clone(),
756 "Path selection alias must be followed by a path",
757 )),
758 }
759 } else {
760 match PathSelection::parse(input.clone()) {
761 Ok((remainder, path)) => {
762 if path.is_anonymous() && path.has_subselection() {
763 Ok((
769 remainder,
770 Self {
771 prefix: NamingPrefix::Spread(None),
772 path: path_value(path),
773 },
774 ))
775 } else {
776 Err(nom_fail_message(
777 input.clone(),
778 "Named path selection must either begin with alias or ..., or end with subselection",
779 ))
780 }
781 }
782 Err(nom::Err::Failure(e)) => Err(nom::Err::Failure(e)),
783 Err(_) => Err(nom_error_message(
784 input.clone(),
785 "Path selection must either begin with alias or ..., or end with subselection",
786 )),
787 }
788 }
789 }
790
791 fn parse_group(input: Span) -> ParseResult<Self> {
792 (Alias::parse, SubSelection::parse)
793 .parse(input)
794 .map(|(input, (alias, group))| {
795 let group_range = group.range();
796 (
797 input,
798 NamedSelection {
799 prefix: NamingPrefix::Alias(alias),
800 path: path_value(PathSelection {
801 path: WithRange::new(PathList::Selection(group), group_range),
802 }),
803 },
804 )
805 })
806 }
807
808 fn parse_v0_3(input: Span) -> ParseResult<Self> {
811 let (after_alias, alias) = opt(Alias::parse).parse(input.clone())?;
812
813 if let Some(alias) = alias {
814 if let Ok((remainder, sub)) = SubSelection::parse(after_alias.clone()) {
815 let sub_range = sub.range();
816 return Ok((
817 remainder,
818 Self {
819 prefix: NamingPrefix::Alias(alias),
820 path: path_value(PathSelection {
831 path: WithRange::new(PathList::Selection(sub), sub_range),
832 }),
833 },
834 ));
835 }
836
837 PathSelection::parse(after_alias.clone()).map(|(remainder, path)| {
838 (
839 remainder,
840 Self {
841 prefix: NamingPrefix::Alias(alias),
842 path: path_value(path),
843 },
844 )
845 })
846 } else {
847 (
848 spaces_or_comments,
849 opt(ranged_span("...")),
850 PathSelection::parse,
851 )
852 .parse(input.clone())
853 .map(|(mut remainder, (_spaces, spread, path))| {
854 let prefix = if let Some(spread) = spread {
855 remainder.extra.errors.push((
857 "Spread syntax (...) is not supported in connect/v0.3 (use connect/v0.4)"
858 .to_string(),
859 input.location_offset(),
860 ));
861 NamingPrefix::Spread(spread.range())
867 } else if path.is_anonymous() && path.has_subselection() {
868 NamingPrefix::Spread(None)
878 } else {
879 NamingPrefix::None
886 };
887 (
888 remainder,
889 Self {
890 prefix,
891 path: path_value(path),
892 },
893 )
894 })
895 }
896 }
897
898 fn parse_v0_4(input: Span) -> ParseResult<Self> {
901 let (after_alias, alias) = opt(Alias::parse).parse(input.clone())?;
902
903 if let Some(alias) = alias {
904 if let Ok((remainder, sub)) = SubSelection::parse(after_alias.clone()) {
905 let sub_range = sub.range();
906 return Ok((
907 remainder,
908 Self {
909 prefix: NamingPrefix::Alias(alias),
910 path: path_value(PathSelection {
921 path: WithRange::new(PathList::Selection(sub), sub_range),
922 }),
923 },
924 ));
925 }
926
927 LitExpr::parse(after_alias.clone()).map(|(remainder, lit)| {
934 (
935 remainder,
936 Self {
937 prefix: NamingPrefix::Alias(alias),
938 path: lit,
939 },
940 )
941 })
942 } else {
943 (
944 spaces_or_comments,
945 opt(ranged_span("...")),
946 PathSelection::parse,
947 )
948 .parse(input.clone())
949 .map(|(remainder, (_spaces, spread, path))| {
950 let prefix = if let Some(spread) = spread {
951 NamingPrefix::Spread(spread.range())
953 } else if path.is_anonymous() && path.has_subselection() {
954 NamingPrefix::Spread(None)
964 } else {
965 NamingPrefix::None
972 };
973 (
974 remainder,
975 Self {
976 prefix,
977 path: path_value(path),
978 },
979 )
980 })
981 }
982 }
983
984 pub(crate) fn names(&self) -> Vec<&str> {
985 if let Some(single_key) = self.get_single_key() {
986 vec![single_key.as_str()]
987 } else if let Some(sub) = self.next_subselection() {
988 let mut name_set = IndexSet::default();
991 for selection in sub.selections_iter() {
992 name_set.extend(selection.names());
993 }
994 name_set.into_iter().collect()
995 } else {
996 Vec::new()
997 }
998 }
999
1000 pub(crate) fn next_subselection(&self) -> Option<&SubSelection> {
1003 self.path.as_ref().next_subselection()
1004 }
1005
1006 #[allow(unused)]
1008 pub(crate) fn next_mut_subselection(&mut self) -> Option<&mut SubSelection> {
1009 self.path.as_mut().next_mut_subselection()
1010 }
1011}
1012
1013impl VarPaths for NamedSelection {
1014 fn var_paths(&self) -> Vec<&PathSelection> {
1015 self.path.as_ref().var_paths()
1016 }
1017}
1018
1019#[derive(Debug, PartialEq, Eq, Clone)]
1029pub struct PathSelection {
1030 pub(crate) path: WithRange<PathList>,
1031}
1032
1033impl Ranged for PathSelection {
1038 fn range(&self) -> OffsetRange {
1039 self.path.range()
1040 }
1041}
1042
1043impl PathSelection {
1044 pub(crate) fn parse(input: Span) -> ParseResult<Self> {
1045 PathList::parse(input).map(|(input, path)| (input, Self { path }))
1046 }
1047
1048 pub(crate) fn variable_reference<N: FromStr + ToString>(&self) -> Option<VariableReference<N>> {
1049 match self.path.as_ref() {
1050 PathList::Var(var, tail) => match var.as_ref() {
1051 KnownVariable::External(namespace) => {
1052 let selection = tail.compute_consumption_trie(namespace);
1060 let full_range = merge_ranges(var.range(), tail.range());
1061 Some(VariableReference {
1062 namespace: VariableNamespace {
1063 namespace: N::from_str(namespace).ok()?,
1064 location: var.range(),
1065 },
1066 selection,
1067 location: full_range,
1068 })
1069 }
1070 _ => None,
1071 },
1072 _ => None,
1073 }
1074 }
1075
1076 pub(super) fn is_single_key(&self) -> bool {
1077 self.path.is_single_key()
1078 }
1079
1080 pub(super) fn get_single_key(&self) -> Option<&WithRange<Key>> {
1081 self.path.get_single_key()
1082 }
1083
1084 pub(super) fn is_anonymous(&self) -> bool {
1085 self.path.is_anonymous()
1086 }
1087
1088 #[allow(unused)]
1089 pub(super) fn from_slice(keys: &[Key], selection: Option<SubSelection>) -> Self {
1090 Self {
1091 path: WithRange::new(PathList::from_slice(keys, selection), None),
1092 }
1093 }
1094
1095 #[allow(unused)]
1096 pub(super) fn has_subselection(&self) -> bool {
1097 self.path.has_subselection()
1098 }
1099
1100 pub(super) fn next_subselection(&self) -> Option<&SubSelection> {
1101 self.path.next_subselection()
1102 }
1103
1104 #[allow(unused)]
1105 pub(super) fn next_mut_subselection(&mut self) -> Option<&mut SubSelection> {
1106 self.path.next_mut_subselection()
1107 }
1108}
1109
1110impl VarPaths for PathSelection {
1111 fn var_paths(&self) -> Vec<&PathSelection> {
1112 let mut paths = Vec::new();
1113 match self.path.as_ref() {
1114 PathList::Var(var_name, tail) => {
1115 if matches!(
1120 var_name.as_ref(),
1121 KnownVariable::External(_) | KnownVariable::Local(_)
1122 ) {
1123 paths.push(self);
1124 }
1125 paths.extend(tail.var_paths());
1126 }
1127 other => {
1128 paths.extend(other.var_paths());
1129 }
1130 };
1131 paths
1132 }
1133}
1134
1135impl From<PathList> for PathSelection {
1136 fn from(path: PathList) -> Self {
1137 Self {
1138 path: WithRange::new(path, None),
1139 }
1140 }
1141}
1142
1143#[derive(Debug, PartialEq, Eq, Clone)]
1144pub(crate) enum PathList {
1145 Var(WithRange<KnownVariable>, WithRange<PathList>),
1152
1153 Key(WithRange<Key>, WithRange<PathList>),
1157
1158 Expr(WithRange<LitExpr>, WithRange<PathList>),
1161
1162 Method(WithRange<String>, Option<MethodArgs>, WithRange<PathList>),
1165
1166 Question(WithRange<PathList>),
1174
1175 Selection(SubSelection),
1179
1180 Empty,
1183}
1184
1185impl PathList {
1186 pub(crate) fn is_empty(&self) -> bool {
1187 matches!(self, PathList::Empty)
1188 }
1189
1190 pub(super) fn parse(input: Span) -> ParseResult<WithRange<Self>> {
1191 match Self::parse_with_depth(input.clone(), 0) {
1192 Ok((_, parsed)) if matches!(*parsed, Self::Empty) => Err(nom_error_message(
1193 input.clone(),
1194 "Path selection cannot be empty",
1203 )),
1204 otherwise => otherwise,
1205 }
1206 }
1207
1208 #[cfg(test)]
1209 pub(super) fn into_with_range(self) -> WithRange<Self> {
1210 WithRange::new(self, None)
1211 }
1212
1213 pub(super) fn parse_with_depth(input: Span, depth: usize) -> ParseResult<WithRange<Self>> {
1214 let spec = get_connect_spec(&input);
1215
1216 let offset_if_empty = input.location_offset();
1222 let range_if_empty: OffsetRange = Some(offset_if_empty..offset_if_empty);
1223
1224 let (input, _spaces) = spaces_or_comments(input)?;
1226
1227 if depth == 0 {
1231 match (
1237 spaces_or_comments,
1238 ranged_span("$("),
1239 LitExpr::parse,
1240 spaces_or_comments,
1241 ranged_span(")"),
1242 )
1243 .parse(input.clone())
1244 {
1245 Ok((suffix, (_, dollar_open_paren, expr, close_paren, _))) => {
1246 let (remainder, rest) = Self::parse_with_depth(suffix, depth + 1)?;
1247 let expr_range = merge_ranges(dollar_open_paren.range(), close_paren.range());
1248 let full_range = merge_ranges(expr_range, rest.range());
1249 return Ok((
1250 remainder,
1251 WithRange::new(Self::Expr(expr, rest), full_range),
1252 ));
1253 }
1254 Err(nom::Err::Failure(err)) => {
1255 return Err(nom::Err::Failure(err));
1256 }
1257 Err(_) => {
1258 }
1260 }
1261
1262 if let Ok((suffix, (dollar, opt_var))) =
1263 (ranged_span("$"), opt(parse_identifier_no_space)).parse(input.clone())
1264 {
1265 let dollar_range = dollar.range();
1266 let (remainder, rest) = Self::parse_with_depth(suffix, depth + 1)?;
1267 let full_range = merge_ranges(dollar_range.clone(), rest.range());
1268 return if let Some(var) = opt_var {
1269 let full_name = format!("{}{}", dollar.as_ref(), var.as_str());
1270 let known_var = if input.extra.is_local_var(&full_name) {
1274 KnownVariable::Local(full_name)
1275 } else {
1276 KnownVariable::External(full_name)
1277 };
1278 let var_range = merge_ranges(dollar_range, var.range());
1279 let ranged_known_var = WithRange::new(known_var, var_range);
1280 Ok((
1281 remainder,
1282 WithRange::new(Self::Var(ranged_known_var, rest), full_range),
1283 ))
1284 } else {
1285 let ranged_dollar_var = WithRange::new(KnownVariable::Dollar, dollar_range);
1286 Ok((
1287 remainder,
1288 WithRange::new(Self::Var(ranged_dollar_var, rest), full_range),
1289 ))
1290 };
1291 }
1292
1293 if let Ok((suffix, at)) = ranged_span("@").parse(input.clone()) {
1294 let (remainder, rest) = Self::parse_with_depth(suffix, depth + 1)?;
1295 let full_range = merge_ranges(at.range(), rest.range());
1296 return Ok((
1297 remainder,
1298 WithRange::new(
1299 Self::Var(WithRange::new(KnownVariable::AtSign, at.range()), rest),
1300 full_range,
1301 ),
1302 ));
1303 }
1304
1305 if let Ok((suffix, key)) = Key::parse(input.clone()) {
1306 let (remainder, rest) = Self::parse_with_depth(suffix, depth + 1)?;
1307
1308 return match spec {
1309 ConnectSpec::V0_1 | ConnectSpec::V0_2 => match rest.as_ref() {
1310 Self::Empty | Self::Selection(_) => Err(nom_error_message(
1315 input.clone(),
1316 "Single-key path must be prefixed with $. to avoid ambiguity with field name",
1320 )),
1321 _ => {
1322 let full_range = merge_ranges(key.range(), rest.range());
1323 Ok((remainder, WithRange::new(Self::Key(key, rest), full_range)))
1324 }
1325 },
1326
1327 ConnectSpec::V0_3 | ConnectSpec::V0_4 => {
1332 let full_range = merge_ranges(key.range(), rest.range());
1333 Ok((remainder, WithRange::new(Self::Key(key, rest), full_range)))
1334 }
1335 };
1336 }
1337 }
1338
1339 if depth == 0 {
1340 if (ranged_span("."), Key::parse).parse(input.clone()).is_ok() {
1343 return Err(nom_fail_message(
1347 input.clone(),
1348 "Key paths cannot start with just .key (use $.key instead)",
1349 ));
1350 }
1351 return Err(nom_error_message(
1354 input.clone(),
1355 "Path selection must start with key, $variable, $, @, or $(expression)",
1356 ));
1357 }
1358
1359 if input.fragment().starts_with("??") || input.fragment().starts_with("?!") {
1365 return Ok((input, WithRange::new(Self::Empty, range_if_empty)));
1366 }
1367
1368 match spec {
1369 ConnectSpec::V0_1 | ConnectSpec::V0_2 => {
1370 }
1372 ConnectSpec::V0_3 | ConnectSpec::V0_4 => {
1373 if let Ok((suffix, question)) = ranged_span("?").parse(input.clone()) {
1374 let (remainder, rest) = Self::parse_with_depth(suffix.clone(), depth + 1)?;
1375
1376 return match rest.as_ref() {
1377 PathList::Question(_) => {
1382 let empty_range = question.range().map(|range| range.end..range.end);
1383 let empty = WithRange::new(Self::Empty, empty_range);
1384 Ok((
1385 suffix,
1386 WithRange::new(Self::Question(empty), question.range()),
1387 ))
1388 }
1389 _ => {
1390 let full_range = merge_ranges(question.range(), rest.range());
1391 Ok((remainder, WithRange::new(Self::Question(rest), full_range)))
1392 }
1393 };
1394 }
1395 }
1396 };
1397
1398 if let Ok((remainder, (dot, key))) = (ranged_span("."), Key::parse).parse(input.clone()) {
1412 let (remainder, rest) = Self::parse_with_depth(remainder, depth + 1)?;
1413 let dot_key_range = merge_ranges(dot.range(), key.range());
1414 let full_range = merge_ranges(dot_key_range, rest.range());
1415 return Ok((remainder, WithRange::new(Self::Key(key, rest), full_range)));
1416 }
1417
1418 if input.fragment().starts_with('.') && !input.fragment().starts_with("...") {
1421 return Err(nom_fail_message(
1422 input.clone(),
1423 "Path selection . must be followed by key (identifier or quoted string literal)",
1424 ));
1425 }
1426
1427 if let Ok((suffix, arrow)) = ranged_span("->").parse(input.clone()) {
1430 return match (parse_identifier, opt(MethodArgs::parse)).parse(suffix) {
1435 Ok((suffix, (method, args_opt))) => {
1436 let mut local_var_name = None;
1437
1438 let args = if let Some(args) = args_opt.as_ref()
1443 && ArrowMethod::lookup(method.as_ref()) == Some(ArrowMethod::As)
1444 {
1445 let new_args = if let Some(old_first_arg) = args.args.first()
1446 && let LitExpr::Path(path_selection) = old_first_arg.as_ref()
1447 && let PathList::Var(var_name, var_tail) = path_selection.path.as_ref()
1448 && let KnownVariable::External(var_str) | KnownVariable::Local(var_str) =
1449 var_name.as_ref()
1450 {
1451 let as_var = WithRange::new(
1452 KnownVariable::Local(var_str.clone()),
1454 var_name.range(),
1455 );
1456
1457 local_var_name = Some(var_str.clone());
1458
1459 let new_first_arg = WithRange::new(
1460 LitExpr::Path(PathSelection {
1461 path: WithRange::new(
1462 PathList::Var(as_var, var_tail.clone()),
1463 path_selection.range(),
1464 ),
1465 }),
1466 old_first_arg.range(),
1467 );
1468
1469 let mut new_args = vec![new_first_arg];
1470 new_args.extend(args.args.iter().skip(1).cloned());
1471 new_args
1472 } else {
1473 args.args.clone()
1474 };
1475
1476 Some(MethodArgs {
1477 args: new_args,
1478 range: args.range(),
1479 })
1480 } else {
1481 args_opt
1482 };
1483
1484 let suffix_with_local_var = if let Some(var_name) = local_var_name {
1485 suffix.map_extra(|extra| extra.with_local_var(var_name))
1486 } else {
1487 suffix
1488 };
1489
1490 let (remainder, rest) =
1491 Self::parse_with_depth(suffix_with_local_var, depth + 1)?;
1492 let full_range = merge_ranges(arrow.range(), rest.range());
1493
1494 Ok((
1495 remainder,
1496 WithRange::new(Self::Method(method, args, rest), full_range),
1497 ))
1498 }
1499 Err(_) => Err(nom_fail_message(
1500 input.clone(),
1501 "Method name must follow ->",
1502 )),
1503 };
1504 }
1505
1506 if let Ok((suffix, selection)) = SubSelection::parse(input.clone()) {
1512 let selection_range = selection.range();
1513 return Ok((
1514 suffix,
1515 WithRange::new(Self::Selection(selection), selection_range),
1516 ));
1517 }
1518
1519 Ok((input.clone(), WithRange::new(Self::Empty, range_if_empty)))
1522 }
1523
1524 pub(super) fn is_anonymous(&self) -> bool {
1525 self.get_single_key().is_none()
1526 }
1527
1528 pub(super) fn is_single_key(&self) -> bool {
1529 self.get_single_key().is_some()
1530 }
1531
1532 pub(super) fn get_single_key(&self) -> Option<&WithRange<Key>> {
1533 fn rest_is_empty_or_selection(rest: &WithRange<PathList>) -> bool {
1534 match rest.as_ref() {
1535 PathList::Selection(_) | PathList::Empty => true,
1536 PathList::Question(tail) => rest_is_empty_or_selection(tail),
1537 PathList::Var(_, _)
1543 | PathList::Key(_, _)
1544 | PathList::Expr(_, _)
1545 | PathList::Method(_, _, _) => false,
1546 }
1547 }
1548
1549 match self {
1550 Self::Key(key, key_rest) => {
1551 if rest_is_empty_or_selection(key_rest) {
1552 Some(key)
1553 } else {
1554 None
1555 }
1556 }
1557 _ => None,
1558 }
1559 }
1560
1561 pub(super) fn is_question(&self) -> bool {
1562 matches!(self, Self::Question(_))
1563 }
1564
1565 #[allow(unused)]
1566 pub(super) fn from_slice(properties: &[Key], selection: Option<SubSelection>) -> Self {
1567 match properties {
1568 [] => selection.map_or(Self::Empty, Self::Selection),
1569 [head, tail @ ..] => Self::Key(
1570 WithRange::new(head.clone(), None),
1571 WithRange::new(Self::from_slice(tail, selection), None),
1572 ),
1573 }
1574 }
1575
1576 pub(super) fn has_subselection(&self) -> bool {
1577 self.next_subselection().is_some()
1578 }
1579
1580 pub(super) fn next_subselection(&self) -> Option<&SubSelection> {
1582 match self {
1583 Self::Var(_, tail) => tail.next_subselection(),
1584 Self::Key(_, tail) => tail.next_subselection(),
1585 Self::Expr(_, tail) => tail.next_subselection(),
1586 Self::Method(_, _, tail) => tail.next_subselection(),
1587 Self::Question(tail) => tail.next_subselection(),
1588 Self::Selection(sub) => Some(sub),
1589 Self::Empty => None,
1590 }
1591 }
1592
1593 #[allow(unused)]
1594 pub(super) fn next_mut_subselection(&mut self) -> Option<&mut SubSelection> {
1596 match self {
1597 Self::Var(_, tail) => tail.next_mut_subselection(),
1598 Self::Key(_, tail) => tail.next_mut_subselection(),
1599 Self::Expr(_, tail) => tail.next_mut_subselection(),
1600 Self::Method(_, _, tail) => tail.next_mut_subselection(),
1601 Self::Question(tail) => tail.next_mut_subselection(),
1602 Self::Selection(sub) => Some(sub),
1603 Self::Empty => None,
1604 }
1605 }
1606}
1607
1608impl VarPaths for PathList {
1609 fn var_paths(&self) -> Vec<&PathSelection> {
1610 let mut paths = Vec::new();
1611 match self {
1612 PathList::Var(_, rest) | PathList::Key(_, rest) => {
1619 paths.extend(rest.var_paths());
1620 }
1621 PathList::Expr(expr, rest) => {
1622 paths.extend(expr.var_paths());
1623 paths.extend(rest.var_paths());
1624 }
1625 PathList::Method(_, opt_args, rest) => {
1626 if let Some(args) = opt_args {
1627 for lit_arg in &args.args {
1628 paths.extend(lit_arg.var_paths());
1629 }
1630 }
1631 paths.extend(rest.var_paths());
1632 }
1633 PathList::Question(rest) => {
1634 paths.extend(rest.var_paths());
1635 }
1636 PathList::Selection(sub) => paths.extend(sub.var_paths()),
1637 PathList::Empty => {}
1638 }
1639 paths
1640 }
1641}
1642
1643enum NamedSelectionSeparator {
1647 Undetermined,
1648 Comma,
1649 Space,
1650}
1651
1652#[derive(Debug, PartialEq, Eq, Clone, Default)]
1655pub struct SubSelection {
1656 pub(crate) selections: Vec<NamedSelection>,
1657 pub(super) range: OffsetRange,
1658}
1659
1660impl Ranged for SubSelection {
1661 fn range(&self) -> OffsetRange {
1665 self.range.clone()
1666 }
1667}
1668
1669impl SubSelection {
1670 pub(crate) fn parse(input: Span) -> ParseResult<Self> {
1671 match (
1672 spaces_or_comments,
1673 ranged_span("{"),
1674 Self::parse_naked,
1675 spaces_or_comments,
1676 ranged_span("}"),
1677 )
1678 .parse(input)
1679 {
1680 Ok((remainder, (_, open_brace, sub, _, close_brace))) => {
1681 let range = merge_ranges(open_brace.range(), close_brace.range());
1682 Ok((
1683 remainder,
1684 Self {
1685 selections: sub.selections,
1686 range,
1687 },
1688 ))
1689 }
1690 Err(e) => Err(e),
1691 }
1692 }
1693
1694 fn parse_naked(input: Span) -> ParseResult<Self> {
1695 let selections_result = match get_connect_spec(&input) {
1696 ConnectSpec::V0_1 | ConnectSpec::V0_2 | ConnectSpec::V0_3 => {
1697 Self::parse_naked_selections_legacy(input.clone())
1698 }
1699 ConnectSpec::V0_4 => Self::parse_naked_selections_v0_4(input.clone()),
1700 };
1701
1702 match selections_result {
1703 Ok((remainder, selections)) => {
1704 for sel in selections.iter() {
1708 if sel.is_anonymous() && selections.len() > 1 {
1709 return Err(nom_error_message(
1710 input.clone(),
1711 "SubSelection cannot contain multiple elements if it contains an anonymous NamedSelection",
1712 ));
1713 }
1714 }
1715
1716 let range = merge_ranges(
1717 selections.first().and_then(|first| first.range()),
1718 selections.last().and_then(|last| last.range()),
1719 );
1720
1721 Ok((remainder, Self { selections, range }))
1722 }
1723 Err(e) => Err(e),
1724 }
1725 }
1726
1727 fn parse_naked_selections_legacy(input: Span) -> ParseResult<Vec<NamedSelection>> {
1728 many0(NamedSelection::parse).parse(input)
1729 }
1730
1731 fn parse_naked_selections_v0_4(input: Span) -> ParseResult<Vec<NamedSelection>> {
1749 let mut selections: Vec<NamedSelection> = Vec::new();
1750
1751 let mut rest = match NamedSelection::parse(input.clone()) {
1753 Ok((r, sel)) => {
1754 selections.push(sel);
1755 r
1756 }
1757 Err(nom::Err::Error(_)) => return Ok((input, selections)),
1758 Err(e) => return Err(e),
1759 };
1760
1761 let mut mode = NamedSelectionSeparator::Undetermined;
1762
1763 loop {
1764 let (after_ws, _) = spaces_or_comments(rest.clone())?;
1765 let comma_attempt: ParseResult<char> = char(',').parse(after_ws.clone());
1766
1767 if let Ok((after_comma, _)) = comma_attempt {
1768 if matches!(mode, NamedSelectionSeparator::Space) {
1769 return Err(nom_fail_message(
1775 after_ws,
1776 "Items in this selection list are separated by whitespace, so commas \
1777 cannot be mixed in. Use commas or whitespace consistently throughout \
1778 the list. Unexpected comma",
1779 ));
1780 }
1781 mode = NamedSelectionSeparator::Comma;
1782 rest = after_comma;
1783
1784 match NamedSelection::parse(rest.clone()) {
1785 Ok((r, sel)) => {
1786 selections.push(sel);
1787 rest = r;
1788 }
1789 Err(nom::Err::Error(_)) => break, Err(e) => return Err(e),
1791 }
1792 } else {
1793 if matches!(mode, NamedSelectionSeparator::Comma) {
1794 if NamedSelection::parse(after_ws.clone()).is_ok() {
1799 return Err(nom_fail_message(
1803 after_ws,
1804 "Items in this selection list are separated by commas, so each item \
1805 after the first needs a preceding comma. Use commas or whitespace \
1806 consistently throughout the list. Missing comma before item",
1807 ));
1808 }
1809 break;
1810 }
1811 match NamedSelection::parse(after_ws.clone()) {
1812 Ok((r, sel)) => {
1813 selections.push(sel);
1814 rest = r;
1815 mode = NamedSelectionSeparator::Space;
1816 }
1817 Err(nom::Err::Error(_)) => break,
1818 Err(e) => return Err(e),
1819 }
1820 }
1821 }
1822
1823 Ok((rest, selections))
1824 }
1825
1826 pub fn selections_iter(&self) -> impl Iterator<Item = &NamedSelection> {
1831 let mut selections = Vec::new();
1834 for selection in &self.selections {
1835 if selection.has_single_output_key() {
1836 selections.push(selection);
1839 } else if let Some(sub) = selection.next_subselection() {
1840 selections.extend(sub.selections_iter());
1846 } else {
1847 debug_assert!(false, "PathSelection without Alias or SubSelection");
1850 }
1851 }
1852 selections.into_iter()
1853 }
1854
1855 pub fn append_selection(&mut self, selection: NamedSelection) {
1856 self.selections.push(selection);
1857 }
1858
1859 pub fn last_selection_mut(&mut self) -> Option<&mut NamedSelection> {
1860 self.selections.last_mut()
1861 }
1862}
1863
1864impl VarPaths for SubSelection {
1865 fn var_paths(&self) -> Vec<&PathSelection> {
1866 let mut paths = Vec::new();
1867 for selection in &self.selections {
1868 paths.extend(selection.var_paths());
1869 }
1870 paths
1871 }
1872}
1873
1874#[derive(Debug, PartialEq, Eq, Clone)]
1877pub(crate) struct Alias {
1878 pub(super) name: WithRange<Key>,
1879 pub(super) range: OffsetRange,
1880}
1881
1882impl Ranged for Alias {
1883 fn range(&self) -> OffsetRange {
1884 self.range.clone()
1885 }
1886}
1887
1888impl Alias {
1889 pub(crate) fn new(name: &str) -> Self {
1890 if is_identifier(name) {
1891 Self::field(name)
1892 } else {
1893 Self::quoted(name)
1894 }
1895 }
1896
1897 pub(crate) fn field(name: &str) -> Self {
1898 Self {
1899 name: WithRange::new(Key::field(name), None),
1900 range: None,
1901 }
1902 }
1903
1904 pub(crate) fn quoted(name: &str) -> Self {
1905 Self {
1906 name: WithRange::new(Key::quoted(name), None),
1907 range: None,
1908 }
1909 }
1910
1911 pub(crate) fn parse(input: Span) -> ParseResult<Self> {
1912 (Key::parse, spaces_or_comments, ranged_span(":"))
1913 .parse(input)
1914 .map(|(input, (name, _, colon))| {
1915 let range = merge_ranges(name.range(), colon.range());
1916 (input, Self { name, range })
1917 })
1918 }
1919}
1920
1921#[derive(Debug, PartialEq, Eq, Clone, Hash)]
1924pub enum Key {
1925 Field(String),
1926 Quoted(String),
1927}
1928
1929impl Key {
1930 pub(crate) fn parse(input: Span) -> ParseResult<WithRange<Self>> {
1931 alt((
1932 map(parse_identifier, |id| id.take_as(Key::Field)),
1933 map(parse_string_literal, |s| s.take_as(Key::Quoted)),
1934 ))
1935 .parse(input)
1936 }
1937
1938 pub fn field(name: &str) -> Self {
1939 Self::Field(name.to_string())
1940 }
1941
1942 pub fn quoted(name: &str) -> Self {
1943 Self::Quoted(name.to_string())
1944 }
1945
1946 pub fn into_with_range(self) -> WithRange<Self> {
1947 WithRange::new(self, None)
1948 }
1949
1950 pub fn is_quoted(&self) -> bool {
1951 matches!(self, Self::Quoted(_))
1952 }
1953
1954 pub fn to_json(&self) -> JSON {
1955 match self {
1956 Key::Field(name) => JSON::String(name.clone().into()),
1957 Key::Quoted(name) => JSON::String(name.clone().into()),
1958 }
1959 }
1960
1961 pub fn as_string(&self) -> String {
1965 match self {
1966 Key::Field(name) => name.clone(),
1967 Key::Quoted(name) => name.clone(),
1968 }
1969 }
1970 pub fn as_str(&self) -> &str {
1973 match self {
1974 Key::Field(name) => name.as_str(),
1975 Key::Quoted(name) => name.as_str(),
1976 }
1977 }
1978
1979 pub fn dotted(&self) -> String {
1984 match self {
1985 Key::Field(field) => format!(".{field}"),
1986 Key::Quoted(field) => {
1987 let quoted = serde_json_bytes::Value::String(field.clone().into()).to_string();
1991 format!(".{quoted}")
1992 }
1993 }
1994 }
1995}
1996
1997impl Display for Key {
1998 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1999 let dotted = self.dotted();
2000 write!(f, "{dotted}")
2001 }
2002}
2003
2004pub(super) fn is_identifier(input: &str) -> bool {
2007 all_consuming(parse_identifier_no_space)
2009 .parse(new_span_with_spec(input, ConnectSpec::latest()))
2010 .is_ok()
2011}
2012
2013fn parse_identifier(input: Span) -> ParseResult<WithRange<String>> {
2014 preceded(spaces_or_comments, parse_identifier_no_space).parse(input)
2015}
2016
2017fn parse_identifier_no_space(input: Span) -> ParseResult<WithRange<String>> {
2018 recognize(pair(
2019 one_of("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_"),
2020 many0(one_of(
2021 "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_0123456789",
2022 )),
2023 ))
2024 .parse(input)
2025 .map(|(remainder, name)| {
2026 let range = Some(name.location_offset()..remainder.location_offset());
2027 (remainder, WithRange::new(name.to_string(), range))
2028 })
2029}
2030
2031pub(crate) fn parse_string_literal(input: Span) -> ParseResult<WithRange<String>> {
2036 let input = spaces_or_comments(input)?.0;
2037 let start = input.location_offset();
2038 let mut input_char_indices = input.char_indices();
2039
2040 match input_char_indices.next() {
2041 Some((0, quote @ '\'')) | Some((0, quote @ '"')) => {
2042 let mut escape_next = false;
2043 let mut chars: Vec<char> = Vec::new();
2044 let mut remainder_opt: Option<Span> = None;
2045
2046 for (i, c) in input_char_indices {
2047 if escape_next {
2048 match c {
2049 'n' => chars.push('\n'),
2050 _ => chars.push(c),
2051 }
2052 escape_next = false;
2053 continue;
2054 }
2055 if c == '\\' {
2056 escape_next = true;
2057 continue;
2058 }
2059 if c == quote {
2060 remainder_opt = Some(input.take_from(i + 1));
2061 break;
2062 }
2063 chars.push(c);
2064 }
2065
2066 remainder_opt
2067 .ok_or_else(|| nom_fail_message(input, "Unterminated string literal"))
2068 .map(|remainder| {
2069 let range = Some(start..remainder.location_offset());
2070 (
2071 remainder,
2072 WithRange::new(chars.iter().collect::<String>(), range),
2073 )
2074 })
2075 }
2076
2077 _ => Err(nom_error_message(input, "Not a string literal")),
2078 }
2079}
2080
2081#[derive(Debug, PartialEq, Eq, Clone, Default)]
2082pub(crate) struct MethodArgs {
2083 pub(crate) args: Vec<WithRange<LitExpr>>,
2084 pub(super) range: OffsetRange,
2085}
2086
2087impl Ranged for MethodArgs {
2088 fn range(&self) -> OffsetRange {
2089 self.range.clone()
2090 }
2091}
2092
2093impl MethodArgs {
2098 fn parse(input: Span) -> ParseResult<Self> {
2099 let input = spaces_or_comments(input)?.0;
2100 let (mut input, open_paren) = ranged_span("(").parse(input)?;
2101 input = spaces_or_comments(input)?.0;
2102
2103 let mut args = Vec::new();
2104 if let Ok((remainder, first)) = LitExpr::parse(input.clone()) {
2105 args.push(first);
2106 input = remainder;
2107
2108 while let Ok((remainder, _)) = (spaces_or_comments, char(',')).parse(input.clone()) {
2109 input = spaces_or_comments(remainder)?.0;
2110 if let Ok((remainder, arg)) = LitExpr::parse(input.clone()) {
2111 args.push(arg);
2112 input = remainder;
2113 } else {
2114 break;
2115 }
2116 }
2117 }
2118
2119 input = spaces_or_comments(input.clone())?.0;
2120 let (input, close_paren) = ranged_span(")").parse(input.clone())?;
2121
2122 let range = merge_ranges(open_paren.range(), close_paren.range());
2123 Ok((input, Self { args, range }))
2124 }
2125}
2126
2127#[cfg(test)]
2128mod tests {
2129 use apollo_compiler::collections::IndexMap;
2130 use rstest::rstest;
2131
2132 use super::super::location::strip_ranges::StripRanges;
2133 use super::*;
2134 use crate::assert_debug_snapshot;
2135 use crate::connectors::json_selection::PrettyPrintable;
2136 use crate::connectors::json_selection::SelectionTrie;
2137 use crate::connectors::json_selection::fixtures::Namespace;
2138 use crate::connectors::json_selection::helpers::span_is_all_spaces_or_comments;
2139 use crate::connectors::json_selection::location::new_span;
2140 use crate::selection;
2141
2142 #[test]
2143 fn test_identifier() {
2144 fn check(input: &str, expected_name: &str) {
2145 let (remainder, name) = parse_identifier(new_span(input)).unwrap();
2146 assert!(
2147 span_is_all_spaces_or_comments(remainder.clone()),
2148 "remainder is `{:?}`",
2149 remainder.clone(),
2150 );
2151 assert_eq!(name.as_ref(), expected_name);
2152 }
2153
2154 check("hello", "hello");
2155 check("hello_world", "hello_world");
2156 check(" hello_world ", "hello_world");
2157 check("hello_world_123", "hello_world_123");
2158 check(" hello ", "hello");
2159
2160 fn check_no_space(input: &str, expected_name: &str) {
2161 let name = parse_identifier_no_space(new_span(input)).unwrap().1;
2162 assert_eq!(name.as_ref(), expected_name);
2163 }
2164
2165 check_no_space("oyez", "oyez");
2166 check_no_space("oyez ", "oyez");
2167
2168 {
2169 let identifier_with_leading_space = new_span(" oyez ");
2170 assert_eq!(
2171 parse_identifier_no_space(identifier_with_leading_space.clone()),
2172 Err(nom::Err::Error(nom::error::Error::from_error_kind(
2173 identifier_with_leading_space.clone(),
2177 nom::error::ErrorKind::OneOf,
2178 ))),
2179 );
2180 }
2181 }
2182
2183 #[test]
2184 fn test_string_literal() {
2185 fn check(input: &str, expected: &str) {
2186 let (remainder, lit) = parse_string_literal(new_span(input)).unwrap();
2187 assert!(
2188 span_is_all_spaces_or_comments(remainder.clone()),
2189 "remainder is `{:?}`",
2190 remainder.clone(),
2191 );
2192 assert_eq!(lit.as_ref(), expected);
2193 }
2194 check("'hello world'", "hello world");
2195 check("\"hello world\"", "hello world");
2196 check("'hello \"world\"'", "hello \"world\"");
2197 check("\"hello \\\"world\\\"\"", "hello \"world\"");
2198 check("'hello \\'world\\''", "hello 'world'");
2199 }
2200
2201 #[test]
2202 fn test_key() {
2203 fn check(input: &str, expected: &Key) {
2204 let (remainder, key) = Key::parse(new_span(input)).unwrap();
2205 assert!(
2206 span_is_all_spaces_or_comments(remainder.clone()),
2207 "remainder is `{:?}`",
2208 remainder.clone(),
2209 );
2210 assert_eq!(key.as_ref(), expected);
2211 }
2212
2213 check("hello", &Key::field("hello"));
2214 check("'hello'", &Key::quoted("hello"));
2215 check(" hello ", &Key::field("hello"));
2216 check("\"hello\"", &Key::quoted("hello"));
2217 check(" \"hello\" ", &Key::quoted("hello"));
2218 }
2219
2220 #[test]
2221 fn test_alias() {
2222 fn check(input: &str, alias: &str) {
2223 let (remainder, parsed) = Alias::parse(new_span(input)).unwrap();
2224 assert!(
2225 span_is_all_spaces_or_comments(remainder.clone()),
2226 "remainder is `{:?}`",
2227 remainder.clone(),
2228 );
2229 assert_eq!(parsed.name.as_str(), alias);
2230 }
2231
2232 check("hello:", "hello");
2233 check("hello :", "hello");
2234 check("hello : ", "hello");
2235 check(" hello :", "hello");
2236 check("hello: ", "hello");
2237 }
2238
2239 #[test]
2240 fn test_named_selection() {
2241 #[track_caller]
2242 fn assert_result_and_names(input: &str, expected: NamedSelection, names: &[&str]) {
2243 let (remainder, selection) = NamedSelection::parse(new_span(input)).unwrap();
2244 assert!(
2245 span_is_all_spaces_or_comments(remainder.clone()),
2246 "remainder is `{:?}`",
2247 remainder.clone(),
2248 );
2249 let selection = selection.strip_ranges();
2250 assert_eq!(selection, expected);
2251 assert_eq!(selection.names(), names);
2252 assert_eq!(
2253 selection!(input).strip_ranges(),
2254 JSONSelection::named(SubSelection {
2255 selections: vec![expected],
2256 ..Default::default()
2257 },),
2258 );
2259 }
2260
2261 assert_result_and_names(
2262 "hello",
2263 NamedSelection::field(None, Key::field("hello").into_with_range(), None),
2264 &["hello"],
2265 );
2266
2267 assert_result_and_names(
2268 "hello { world }",
2269 NamedSelection::field(
2270 None,
2271 Key::field("hello").into_with_range(),
2272 Some(SubSelection {
2273 selections: vec![NamedSelection::field(
2274 None,
2275 Key::field("world").into_with_range(),
2276 None,
2277 )],
2278 ..Default::default()
2279 }),
2280 ),
2281 &["hello"],
2282 );
2283
2284 assert_result_and_names(
2285 "hi: hello",
2286 NamedSelection::field(
2287 Some(Alias::new("hi")),
2288 Key::field("hello").into_with_range(),
2289 None,
2290 ),
2291 &["hi"],
2292 );
2293
2294 assert_result_and_names(
2295 "hi: 'hello world'",
2296 NamedSelection::field(
2297 Some(Alias::new("hi")),
2298 Key::quoted("hello world").into_with_range(),
2299 None,
2300 ),
2301 &["hi"],
2302 );
2303
2304 assert_result_and_names(
2305 "hi: hello { world }",
2306 NamedSelection::field(
2307 Some(Alias::new("hi")),
2308 Key::field("hello").into_with_range(),
2309 Some(SubSelection {
2310 selections: vec![NamedSelection::field(
2311 None,
2312 Key::field("world").into_with_range(),
2313 None,
2314 )],
2315 ..Default::default()
2316 }),
2317 ),
2318 &["hi"],
2319 );
2320
2321 assert_result_and_names(
2322 "hey: hello { world again }",
2323 NamedSelection::field(
2324 Some(Alias::new("hey")),
2325 Key::field("hello").into_with_range(),
2326 Some(SubSelection {
2327 selections: vec![
2328 NamedSelection::field(None, Key::field("world").into_with_range(), None),
2329 NamedSelection::field(None, Key::field("again").into_with_range(), None),
2330 ],
2331 ..Default::default()
2332 }),
2333 ),
2334 &["hey"],
2335 );
2336
2337 assert_result_and_names(
2338 "hey: 'hello world' { again }",
2339 NamedSelection::field(
2340 Some(Alias::new("hey")),
2341 Key::quoted("hello world").into_with_range(),
2342 Some(SubSelection {
2343 selections: vec![NamedSelection::field(
2344 None,
2345 Key::field("again").into_with_range(),
2346 None,
2347 )],
2348 ..Default::default()
2349 }),
2350 ),
2351 &["hey"],
2352 );
2353
2354 assert_result_and_names(
2355 "leggo: 'my ego'",
2356 NamedSelection::field(
2357 Some(Alias::new("leggo")),
2358 Key::quoted("my ego").into_with_range(),
2359 None,
2360 ),
2361 &["leggo"],
2362 );
2363
2364 assert_result_and_names(
2365 "'let go': 'my ego'",
2366 NamedSelection::field(
2367 Some(Alias::quoted("let go")),
2368 Key::quoted("my ego").into_with_range(),
2369 None,
2370 ),
2371 &["let go"],
2372 );
2373 }
2374
2375 #[test]
2376 fn test_selection() {
2377 assert_eq!(
2378 selection!("").strip_ranges(),
2379 JSONSelection::named(SubSelection {
2380 selections: vec![],
2381 ..Default::default()
2382 }),
2383 );
2384
2385 assert_eq!(
2386 selection!(" ").strip_ranges(),
2387 JSONSelection::named(SubSelection {
2388 selections: vec![],
2389 ..Default::default()
2390 }),
2391 );
2392
2393 assert_eq!(
2394 selection!("hello").strip_ranges(),
2395 JSONSelection::named(SubSelection {
2396 selections: vec![NamedSelection::field(
2397 None,
2398 Key::field("hello").into_with_range(),
2399 None
2400 )],
2401 ..Default::default()
2402 }),
2403 );
2404
2405 assert_eq!(
2406 selection!("$.hello").strip_ranges(),
2407 JSONSelection::path(PathSelection {
2408 path: PathList::Var(
2409 KnownVariable::Dollar.into_with_range(),
2410 PathList::Key(
2411 Key::field("hello").into_with_range(),
2412 PathList::Empty.into_with_range()
2413 )
2414 .into_with_range(),
2415 )
2416 .into_with_range(),
2417 }),
2418 );
2419
2420 {
2421 let expected = JSONSelection::named(SubSelection {
2422 selections: vec![NamedSelection {
2423 prefix: NamingPrefix::Alias(Alias::new("hi")),
2424 path: path_value(PathSelection::from_slice(
2425 &[
2426 Key::Field("hello".to_string()),
2427 Key::Field("world".to_string()),
2428 ],
2429 None,
2430 )),
2431 }],
2432 ..Default::default()
2433 });
2434
2435 assert_eq!(selection!("hi: hello.world").strip_ranges(), expected);
2436 assert_eq!(selection!("hi: hello .world").strip_ranges(), expected);
2437 assert_eq!(selection!("hi: hello. world").strip_ranges(), expected);
2438 assert_eq!(selection!("hi: hello . world").strip_ranges(), expected);
2439 assert_eq!(selection!("hi: hello.world").strip_ranges(), expected);
2440 assert_eq!(selection!("hi: hello. world").strip_ranges(), expected);
2441 assert_eq!(selection!("hi: hello .world").strip_ranges(), expected);
2442 assert_eq!(selection!("hi: hello . world ").strip_ranges(), expected);
2443 }
2444
2445 {
2446 let expected = JSONSelection::named(SubSelection {
2447 selections: vec![
2448 NamedSelection::field(None, Key::field("before").into_with_range(), None),
2449 NamedSelection {
2450 prefix: NamingPrefix::Alias(Alias::new("hi")),
2451 path: path_value(PathSelection::from_slice(
2452 &[
2453 Key::Field("hello".to_string()),
2454 Key::Field("world".to_string()),
2455 ],
2456 None,
2457 )),
2458 },
2459 NamedSelection::field(None, Key::field("after").into_with_range(), None),
2460 ],
2461 ..Default::default()
2462 });
2463
2464 assert_eq!(
2465 selection!("before hi: hello.world after").strip_ranges(),
2466 expected
2467 );
2468 assert_eq!(
2469 selection!("before hi: hello .world after").strip_ranges(),
2470 expected
2471 );
2472 assert_eq!(
2473 selection!("before hi: hello. world after").strip_ranges(),
2474 expected
2475 );
2476 assert_eq!(
2477 selection!("before hi: hello . world after").strip_ranges(),
2478 expected
2479 );
2480 assert_eq!(
2481 selection!("before hi: hello.world after").strip_ranges(),
2482 expected
2483 );
2484 assert_eq!(
2485 selection!("before hi: hello .world after").strip_ranges(),
2486 expected
2487 );
2488 assert_eq!(
2489 selection!("before hi: hello. world after").strip_ranges(),
2490 expected
2491 );
2492 assert_eq!(
2493 selection!("before hi: hello . world after").strip_ranges(),
2494 expected
2495 );
2496 }
2497
2498 {
2499 let expected = JSONSelection::named(SubSelection {
2500 selections: vec![
2501 NamedSelection::field(None, Key::field("before").into_with_range(), None),
2502 NamedSelection {
2503 prefix: NamingPrefix::Alias(Alias::new("hi")),
2504 path: path_value(PathSelection::from_slice(
2505 &[
2506 Key::Field("hello".to_string()),
2507 Key::Field("world".to_string()),
2508 ],
2509 Some(SubSelection {
2510 selections: vec![
2511 NamedSelection::field(
2512 None,
2513 Key::field("nested").into_with_range(),
2514 None,
2515 ),
2516 NamedSelection::field(
2517 None,
2518 Key::field("names").into_with_range(),
2519 None,
2520 ),
2521 ],
2522 ..Default::default()
2523 }),
2524 )),
2525 },
2526 NamedSelection::field(None, Key::field("after").into_with_range(), None),
2527 ],
2528 ..Default::default()
2529 });
2530
2531 assert_eq!(
2532 selection!("before hi: hello.world { nested names } after").strip_ranges(),
2533 expected
2534 );
2535 assert_eq!(
2536 selection!("before hi:hello.world{nested names}after").strip_ranges(),
2537 expected
2538 );
2539 assert_eq!(
2540 selection!(" before hi : hello . world { nested names } after ").strip_ranges(),
2541 expected
2542 );
2543 }
2544
2545 assert_debug_snapshot!(selection!(
2546 "
2547 # Comments are supported because we parse them as whitespace
2548 topLevelAlias: topLevelField {
2549 identifier: 'property name with spaces'
2550 'unaliased non-identifier property'
2551 'non-identifier alias': identifier
2552
2553 # This extracts the value located at the given path and applies a
2554 # selection set to it before renaming the result to pathSelection
2555 pathSelection: some.nested.path {
2556 still: yet
2557 more
2558 properties
2559 }
2560
2561 # An aliased SubSelection of fields nests the fields together
2562 # under the given alias
2563 siblingGroup: { brother sister }
2564 }"
2565 ));
2566 }
2567
2568 #[track_caller]
2569 fn check_path_selection(spec: ConnectSpec, input: &str, expected: PathSelection) {
2570 let (remainder, path_selection) =
2571 PathSelection::parse(new_span_with_spec(input, spec)).unwrap();
2572 assert!(
2573 span_is_all_spaces_or_comments(remainder.clone()),
2574 "remainder is `{:?}`",
2575 remainder.clone(),
2576 );
2577 let path_without_ranges = path_selection.strip_ranges();
2578 assert_eq!(&path_without_ranges, &expected);
2579 assert_eq!(
2580 selection!(input, spec).strip_ranges(),
2581 JSONSelection {
2582 inner: TopLevelSelection::Value(WithRange::new(
2583 LitExpr::Path(path_without_ranges),
2584 None,
2585 )),
2586 spec,
2587 },
2588 );
2589 }
2590
2591 #[rstest]
2592 #[case::v0_2(ConnectSpec::V0_2)]
2593 #[case::v0_3(ConnectSpec::V0_3)]
2594 #[case::v0_4(ConnectSpec::V0_4)]
2595 fn test_path_selection(#[case] spec: ConnectSpec) {
2596 check_path_selection(
2597 spec,
2598 "$.hello",
2599 PathSelection {
2600 path: PathList::Var(
2601 KnownVariable::Dollar.into_with_range(),
2602 PathList::Key(
2603 Key::field("hello").into_with_range(),
2604 PathList::Empty.into_with_range(),
2605 )
2606 .into_with_range(),
2607 )
2608 .into_with_range(),
2609 },
2610 );
2611
2612 {
2613 let expected = PathSelection {
2614 path: PathList::Var(
2615 KnownVariable::Dollar.into_with_range(),
2616 PathList::Key(
2617 Key::field("hello").into_with_range(),
2618 PathList::Key(
2619 Key::field("world").into_with_range(),
2620 PathList::Empty.into_with_range(),
2621 )
2622 .into_with_range(),
2623 )
2624 .into_with_range(),
2625 )
2626 .into_with_range(),
2627 };
2628 check_path_selection(spec, "$.hello.world", expected.clone());
2629 check_path_selection(spec, "$.hello .world", expected.clone());
2630 check_path_selection(spec, "$.hello. world", expected.clone());
2631 check_path_selection(spec, "$.hello . world", expected.clone());
2632 check_path_selection(spec, "$ . hello . world", expected.clone());
2633 check_path_selection(spec, " $ . hello . world ", expected);
2634 }
2635
2636 {
2637 let expected = PathSelection::from_slice(
2638 &[
2639 Key::Field("hello".to_string()),
2640 Key::Field("world".to_string()),
2641 ],
2642 None,
2643 );
2644 check_path_selection(spec, "hello.world", expected.clone());
2645 check_path_selection(spec, "hello .world", expected.clone());
2646 check_path_selection(spec, "hello. world", expected.clone());
2647 check_path_selection(spec, "hello . world", expected.clone());
2648 check_path_selection(spec, " hello . world ", expected);
2649 }
2650
2651 {
2652 let expected = PathSelection::from_slice(
2653 &[
2654 Key::Field("hello".to_string()),
2655 Key::Field("world".to_string()),
2656 ],
2657 Some(SubSelection {
2658 selections: vec![NamedSelection::field(
2659 None,
2660 Key::field("hello").into_with_range(),
2661 None,
2662 )],
2663 ..Default::default()
2664 }),
2665 );
2666 check_path_selection(spec, "hello.world{hello}", expected.clone());
2667 check_path_selection(spec, "hello.world { hello }", expected.clone());
2668 check_path_selection(spec, "hello .world { hello }", expected.clone());
2669 check_path_selection(spec, "hello. world { hello }", expected.clone());
2670 check_path_selection(spec, "hello . world { hello }", expected.clone());
2671 check_path_selection(spec, " hello . world { hello } ", expected);
2672 }
2673
2674 {
2675 let expected = PathSelection::from_slice(
2676 &[
2677 Key::Field("nested".to_string()),
2678 Key::Quoted("string literal".to_string()),
2679 Key::Quoted("property".to_string()),
2680 Key::Field("name".to_string()),
2681 ],
2682 None,
2683 );
2684 check_path_selection(
2685 spec,
2686 "nested.'string literal'.\"property\".name",
2687 expected.clone(),
2688 );
2689 check_path_selection(
2690 spec,
2691 "nested. 'string literal'.\"property\".name",
2692 expected.clone(),
2693 );
2694 check_path_selection(
2695 spec,
2696 "nested.'string literal'. \"property\".name",
2697 expected.clone(),
2698 );
2699 check_path_selection(
2700 spec,
2701 "nested.'string literal'.\"property\" .name",
2702 expected.clone(),
2703 );
2704 check_path_selection(
2705 spec,
2706 "nested.'string literal'.\"property\". name",
2707 expected.clone(),
2708 );
2709 check_path_selection(
2710 spec,
2711 " nested . 'string literal' . \"property\" . name ",
2712 expected,
2713 );
2714 }
2715
2716 {
2717 let leggo_selection = if matches!(spec, ConnectSpec::V0_4) {
2725 NamedSelection {
2726 prefix: NamingPrefix::Alias(Alias::new("leggo")),
2727 path: WithRange::new(LitExpr::String("my ego".to_string()), None),
2728 }
2729 } else {
2730 NamedSelection::field(
2731 Some(Alias::new("leggo")),
2732 Key::quoted("my ego").into_with_range(),
2733 None,
2734 )
2735 };
2736 let expected = PathSelection::from_slice(
2737 &[
2738 Key::Field("nested".to_string()),
2739 Key::Quoted("string literal".to_string()),
2740 ],
2741 Some(SubSelection {
2742 selections: vec![leggo_selection],
2743 ..Default::default()
2744 }),
2745 );
2746
2747 check_path_selection(
2748 spec,
2749 "nested.'string literal' { leggo: 'my ego' }",
2750 expected.clone(),
2751 );
2752
2753 check_path_selection(
2754 spec,
2755 " nested . 'string literal' { leggo : 'my ego' } ",
2756 expected.clone(),
2757 );
2758
2759 check_path_selection(
2760 spec,
2761 "nested. 'string literal' { leggo: 'my ego' }",
2762 expected.clone(),
2763 );
2764
2765 check_path_selection(
2766 spec,
2767 "nested . 'string literal' { leggo: 'my ego' }",
2768 expected.clone(),
2769 );
2770 check_path_selection(
2771 spec,
2772 " nested . \"string literal\" { leggo: 'my ego' } ",
2773 expected,
2774 );
2775 }
2776
2777 {
2778 let expected = PathSelection {
2779 path: PathList::Var(
2780 KnownVariable::Dollar.into_with_range(),
2781 PathList::Key(
2782 Key::field("results").into_with_range(),
2783 PathList::Selection(SubSelection {
2784 selections: vec![NamedSelection::field(
2785 None,
2786 Key::quoted("quoted without alias").into_with_range(),
2787 Some(SubSelection {
2788 selections: vec![
2789 NamedSelection::field(
2790 None,
2791 Key::field("id").into_with_range(),
2792 None,
2793 ),
2794 NamedSelection::field(
2795 None,
2796 Key::quoted("n a m e").into_with_range(),
2797 None,
2798 ),
2799 ],
2800 ..Default::default()
2801 }),
2802 )],
2803 ..Default::default()
2804 })
2805 .into_with_range(),
2806 )
2807 .into_with_range(),
2808 )
2809 .into_with_range(),
2810 };
2811 check_path_selection(
2812 spec,
2813 "$.results{'quoted without alias'{id'n a m e'}}",
2814 expected.clone(),
2815 );
2816 check_path_selection(
2817 spec,
2818 " $ . results { 'quoted without alias' { id 'n a m e' } } ",
2819 expected,
2820 );
2821 }
2822
2823 {
2824 let quoted_with_alias_value: WithRange<LitExpr> = path_value(PathSelection {
2832 path: WithRange::new(
2833 PathList::Key(
2834 Key::quoted("quoted with alias").into_with_range(),
2835 WithRange::new(
2836 PathList::Selection(SubSelection {
2837 selections: vec![
2838 NamedSelection::field(
2839 None,
2840 Key::field("id").into_with_range(),
2841 None,
2842 ),
2843 NamedSelection::field(
2844 Some(Alias::quoted("n a m e")),
2845 Key::field("name").into_with_range(),
2846 None,
2847 ),
2848 ],
2849 ..Default::default()
2850 }),
2851 None,
2852 ),
2853 ),
2854 None,
2855 ),
2856 });
2857 let expected = PathSelection {
2858 path: PathList::Var(
2859 KnownVariable::Dollar.into_with_range(),
2860 PathList::Key(
2861 Key::field("results").into_with_range(),
2862 PathList::Selection(SubSelection {
2863 selections: vec![NamedSelection {
2864 prefix: NamingPrefix::Alias(Alias::quoted("non-identifier alias")),
2865 path: quoted_with_alias_value,
2866 }],
2867 ..Default::default()
2868 })
2869 .into_with_range(),
2870 )
2871 .into_with_range(),
2872 )
2873 .into_with_range(),
2874 };
2875 check_path_selection(
2876 spec,
2877 "$.results{'non-identifier alias':'quoted with alias'{id'n a m e':name}}",
2878 expected.clone(),
2879 );
2880 check_path_selection(
2881 spec,
2882 " $ . results { 'non-identifier alias' : 'quoted with alias' { id 'n a m e': name } } ",
2883 expected,
2884 );
2885 }
2886 }
2887
2888 #[rstest]
2889 #[case::v0_2(ConnectSpec::V0_2)]
2890 #[case::v0_3(ConnectSpec::V0_3)]
2891 #[case::v0_4(ConnectSpec::V0_4)]
2892 fn test_path_selection_vars(#[case] spec: ConnectSpec) {
2893 check_path_selection(
2894 spec,
2895 "$this",
2896 PathSelection {
2897 path: PathList::Var(
2898 KnownVariable::External(Namespace::This.to_string()).into_with_range(),
2899 PathList::Empty.into_with_range(),
2900 )
2901 .into_with_range(),
2902 },
2903 );
2904
2905 check_path_selection(
2906 spec,
2907 "$",
2908 PathSelection {
2909 path: PathList::Var(
2910 KnownVariable::Dollar.into_with_range(),
2911 PathList::Empty.into_with_range(),
2912 )
2913 .into_with_range(),
2914 },
2915 );
2916
2917 check_path_selection(
2918 spec,
2919 "$this { hello }",
2920 PathSelection {
2921 path: PathList::Var(
2922 KnownVariable::External(Namespace::This.to_string()).into_with_range(),
2923 PathList::Selection(SubSelection {
2924 selections: vec![NamedSelection::field(
2925 None,
2926 Key::field("hello").into_with_range(),
2927 None,
2928 )],
2929 ..Default::default()
2930 })
2931 .into_with_range(),
2932 )
2933 .into_with_range(),
2934 },
2935 );
2936
2937 check_path_selection(
2938 spec,
2939 "$ { hello }",
2940 PathSelection {
2941 path: PathList::Var(
2942 KnownVariable::Dollar.into_with_range(),
2943 PathList::Selection(SubSelection {
2944 selections: vec![NamedSelection::field(
2945 None,
2946 Key::field("hello").into_with_range(),
2947 None,
2948 )],
2949 ..Default::default()
2950 })
2951 .into_with_range(),
2952 )
2953 .into_with_range(),
2954 },
2955 );
2956
2957 check_path_selection(
2958 spec,
2959 "$this { before alias: $args.arg after }",
2960 PathList::Var(
2961 KnownVariable::External(Namespace::This.to_string()).into_with_range(),
2962 PathList::Selection(SubSelection {
2963 selections: vec![
2964 NamedSelection::field(None, Key::field("before").into_with_range(), None),
2965 NamedSelection {
2966 prefix: NamingPrefix::Alias(Alias::new("alias")),
2967 path: path_value(PathSelection {
2968 path: PathList::Var(
2969 KnownVariable::External(Namespace::Args.to_string())
2970 .into_with_range(),
2971 PathList::Key(
2972 Key::field("arg").into_with_range(),
2973 PathList::Empty.into_with_range(),
2974 )
2975 .into_with_range(),
2976 )
2977 .into_with_range(),
2978 }),
2979 },
2980 NamedSelection::field(None, Key::field("after").into_with_range(), None),
2981 ],
2982 ..Default::default()
2983 })
2984 .into_with_range(),
2985 )
2986 .into(),
2987 );
2988
2989 check_path_selection(
2990 spec,
2991 "$.nested { key injected: $args.arg }",
2992 PathSelection {
2993 path: PathList::Var(
2994 KnownVariable::Dollar.into_with_range(),
2995 PathList::Key(
2996 Key::field("nested").into_with_range(),
2997 PathList::Selection(SubSelection {
2998 selections: vec![
2999 NamedSelection::field(
3000 None,
3001 Key::field("key").into_with_range(),
3002 None,
3003 ),
3004 NamedSelection {
3005 prefix: NamingPrefix::Alias(Alias::new("injected")),
3006 path: path_value(PathSelection {
3007 path: PathList::Var(
3008 KnownVariable::External(Namespace::Args.to_string())
3009 .into_with_range(),
3010 PathList::Key(
3011 Key::field("arg").into_with_range(),
3012 PathList::Empty.into_with_range(),
3013 )
3014 .into_with_range(),
3015 )
3016 .into_with_range(),
3017 }),
3018 },
3019 ],
3020 ..Default::default()
3021 })
3022 .into_with_range(),
3023 )
3024 .into_with_range(),
3025 )
3026 .into_with_range(),
3027 },
3028 );
3029
3030 check_path_selection(
3031 spec,
3032 "$args.a.b.c",
3033 PathSelection {
3034 path: PathList::Var(
3035 KnownVariable::External(Namespace::Args.to_string()).into_with_range(),
3036 PathList::from_slice(
3037 &[
3038 Key::Field("a".to_string()),
3039 Key::Field("b".to_string()),
3040 Key::Field("c".to_string()),
3041 ],
3042 None,
3043 )
3044 .into_with_range(),
3045 )
3046 .into_with_range(),
3047 },
3048 );
3049
3050 check_path_selection(
3051 spec,
3052 "root.x.y.z",
3053 PathSelection::from_slice(
3054 &[
3055 Key::Field("root".to_string()),
3056 Key::Field("x".to_string()),
3057 Key::Field("y".to_string()),
3058 Key::Field("z".to_string()),
3059 ],
3060 None,
3061 ),
3062 );
3063
3064 check_path_selection(
3065 spec,
3066 "$.data",
3067 PathSelection {
3068 path: PathList::Var(
3069 KnownVariable::Dollar.into_with_range(),
3070 PathList::Key(
3071 Key::field("data").into_with_range(),
3072 PathList::Empty.into_with_range(),
3073 )
3074 .into_with_range(),
3075 )
3076 .into_with_range(),
3077 },
3078 );
3079
3080 check_path_selection(
3081 spec,
3082 "$.data.'quoted property'.nested",
3083 PathSelection {
3084 path: PathList::Var(
3085 KnownVariable::Dollar.into_with_range(),
3086 PathList::Key(
3087 Key::field("data").into_with_range(),
3088 PathList::Key(
3089 Key::quoted("quoted property").into_with_range(),
3090 PathList::Key(
3091 Key::field("nested").into_with_range(),
3092 PathList::Empty.into_with_range(),
3093 )
3094 .into_with_range(),
3095 )
3096 .into_with_range(),
3097 )
3098 .into_with_range(),
3099 )
3100 .into_with_range(),
3101 },
3102 );
3103
3104 #[track_caller]
3105 fn check_path_parse_error(
3106 spec: ConnectSpec,
3107 input: &str,
3108 expected_offset: usize,
3109 expected_message: impl Into<String>,
3110 ) {
3111 let expected_message: String = expected_message.into();
3112 match PathSelection::parse(new_span_with_spec(input, spec)) {
3113 Ok((remainder, path)) => {
3114 panic!(
3115 "Expected error at offset {expected_offset} with message '{expected_message}', but got path {path:?} and remainder {remainder:?}",
3116 );
3117 }
3118 Err(nom::Err::Error(e) | nom::Err::Failure(e)) => {
3119 assert_eq!(&input[expected_offset..], *e.input.fragment());
3120 assert_eq!(
3124 e.input.extra,
3125 SpanExtra {
3126 spec,
3127 errors: vec![(expected_message, expected_offset)],
3128 local_vars: Vec::new(),
3129 }
3130 );
3131 }
3132 Err(e) => {
3133 panic!("Unexpected error {e:?}");
3134 }
3135 }
3136 }
3137
3138 let single_key_path_error_message =
3141 "Single-key path must be prefixed with $. to avoid ambiguity with field name";
3142 check_path_parse_error(
3143 ConnectSpec::V0_2,
3144 new_span("naked").fragment(),
3145 0,
3146 single_key_path_error_message,
3147 );
3148 check_path_parse_error(
3149 ConnectSpec::V0_2,
3150 new_span("naked { hi }").fragment(),
3151 0,
3152 single_key_path_error_message,
3153 );
3154 check_path_parse_error(
3155 ConnectSpec::V0_2,
3156 new_span(" naked { hi }").fragment(),
3157 2,
3158 single_key_path_error_message,
3159 );
3160
3161 let path_key_ambiguity_error_message =
3162 "Path selection . must be followed by key (identifier or quoted string literal)";
3163 check_path_parse_error(
3164 ConnectSpec::latest(),
3165 new_span("valid.$invalid").fragment(),
3166 5,
3167 path_key_ambiguity_error_message,
3168 );
3169 check_path_parse_error(
3170 ConnectSpec::latest(),
3171 new_span(" valid.$invalid").fragment(),
3172 7,
3173 path_key_ambiguity_error_message,
3174 );
3175 check_path_parse_error(
3176 ConnectSpec::latest(),
3177 new_span(" valid . $invalid").fragment(),
3178 8,
3179 path_key_ambiguity_error_message,
3180 );
3181
3182 assert_eq!(
3183 selection!("$").strip_ranges(),
3184 JSONSelection::path(PathSelection {
3185 path: PathList::Var(
3186 KnownVariable::Dollar.into_with_range(),
3187 PathList::Empty.into_with_range()
3188 )
3189 .into_with_range(),
3190 }),
3191 );
3192
3193 assert_eq!(
3194 selection!("$this").strip_ranges(),
3195 JSONSelection::path(PathSelection {
3196 path: PathList::Var(
3197 KnownVariable::External(Namespace::This.to_string()).into_with_range(),
3198 PathList::Empty.into_with_range()
3199 )
3200 .into_with_range(),
3201 }),
3202 );
3203
3204 assert_eq!(
3205 selection!("value: $ a { b c }").strip_ranges(),
3206 JSONSelection::named(SubSelection {
3207 selections: vec![
3208 NamedSelection {
3209 prefix: NamingPrefix::Alias(Alias::new("value")),
3210 path: path_value(PathSelection {
3211 path: PathList::Var(
3212 KnownVariable::Dollar.into_with_range(),
3213 PathList::Empty.into_with_range()
3214 )
3215 .into_with_range(),
3216 }),
3217 },
3218 NamedSelection::field(
3219 None,
3220 Key::field("a").into_with_range(),
3221 Some(SubSelection {
3222 selections: vec![
3223 NamedSelection::field(
3224 None,
3225 Key::field("b").into_with_range(),
3226 None
3227 ),
3228 NamedSelection::field(
3229 None,
3230 Key::field("c").into_with_range(),
3231 None
3232 ),
3233 ],
3234 ..Default::default()
3235 }),
3236 ),
3237 ],
3238 ..Default::default()
3239 }),
3240 );
3241 assert_eq!(
3242 selection!("value: $this { b c }").strip_ranges(),
3243 JSONSelection::named(SubSelection {
3244 selections: vec![NamedSelection {
3245 prefix: NamingPrefix::Alias(Alias::new("value")),
3246 path: path_value(PathSelection {
3247 path: PathList::Var(
3248 KnownVariable::External(Namespace::This.to_string()).into_with_range(),
3249 PathList::Selection(SubSelection {
3250 selections: vec![
3251 NamedSelection::field(
3252 None,
3253 Key::field("b").into_with_range(),
3254 None
3255 ),
3256 NamedSelection::field(
3257 None,
3258 Key::field("c").into_with_range(),
3259 None
3260 ),
3261 ],
3262 ..Default::default()
3263 })
3264 .into_with_range(),
3265 )
3266 .into_with_range(),
3267 }),
3268 }],
3269 ..Default::default()
3270 }),
3271 );
3272 }
3273
3274 #[test]
3275 fn test_error_snapshots_v0_2() {
3276 let spec = ConnectSpec::V0_2;
3277
3278 assert_debug_snapshot!(JSONSelection::parse_with_spec(".data", spec));
3282
3283 assert_debug_snapshot!(JSONSelection::parse_with_spec("id $.object", spec));
3288 }
3289
3290 #[test]
3291 fn test_error_snapshots_v0_3() {
3292 let spec = ConnectSpec::V0_3;
3293
3294 assert_debug_snapshot!(JSONSelection::parse_with_spec(".data", spec));
3298
3299 assert_debug_snapshot!(JSONSelection::parse_with_spec("id $.object", spec));
3304 }
3305
3306 #[test]
3307 fn test_error_snapshots_v0_4() {
3308 let spec = ConnectSpec::V0_4;
3309
3310 assert_eq!(spec, ConnectSpec::next());
3314
3315 assert_debug_snapshot!(JSONSelection::parse_with_spec(".data", spec));
3319
3320 assert_debug_snapshot!(JSONSelection::parse_with_spec("id $.object", spec));
3325 }
3326
3327 #[rstest]
3328 #[case::v0_2(ConnectSpec::V0_2)]
3329 #[case::v0_3(ConnectSpec::V0_3)]
3330 #[case::v0_4(ConnectSpec::V0_4)]
3331 fn test_path_selection_at(#[case] spec: ConnectSpec) {
3332 check_path_selection(
3333 spec,
3334 "@",
3335 PathSelection {
3336 path: PathList::Var(
3337 KnownVariable::AtSign.into_with_range(),
3338 PathList::Empty.into_with_range(),
3339 )
3340 .into_with_range(),
3341 },
3342 );
3343
3344 check_path_selection(
3345 spec,
3346 "@.a.b.c",
3347 PathSelection {
3348 path: PathList::Var(
3349 KnownVariable::AtSign.into_with_range(),
3350 PathList::from_slice(
3351 &[
3352 Key::Field("a".to_string()),
3353 Key::Field("b".to_string()),
3354 Key::Field("c".to_string()),
3355 ],
3356 None,
3357 )
3358 .into_with_range(),
3359 )
3360 .into_with_range(),
3361 },
3362 );
3363
3364 check_path_selection(
3365 spec,
3366 "@.items->first",
3367 PathSelection {
3368 path: PathList::Var(
3369 KnownVariable::AtSign.into_with_range(),
3370 PathList::Key(
3371 Key::field("items").into_with_range(),
3372 PathList::Method(
3373 WithRange::new("first".to_string(), None),
3374 None,
3375 PathList::Empty.into_with_range(),
3376 )
3377 .into_with_range(),
3378 )
3379 .into_with_range(),
3380 )
3381 .into_with_range(),
3382 },
3383 );
3384 }
3385
3386 #[rstest]
3387 #[case::v0_2(ConnectSpec::V0_2)]
3388 #[case::v0_3(ConnectSpec::V0_3)]
3389 #[case::v0_4(ConnectSpec::V0_4)]
3390 fn test_expr_path_selections(#[case] spec: ConnectSpec) {
3391 fn check_simple_lit_expr(spec: ConnectSpec, input: &str, expected: LitExpr) {
3392 check_path_selection(
3393 spec,
3394 input,
3395 PathSelection {
3396 path: PathList::Expr(
3397 expected.into_with_range(),
3398 PathList::Empty.into_with_range(),
3399 )
3400 .into_with_range(),
3401 },
3402 );
3403 }
3404
3405 check_simple_lit_expr(spec, "$(null)", LitExpr::Null);
3406
3407 check_simple_lit_expr(spec, "$(true)", LitExpr::Bool(true));
3408 check_simple_lit_expr(spec, "$(false)", LitExpr::Bool(false));
3409
3410 check_simple_lit_expr(
3411 spec,
3412 "$(1234)",
3413 LitExpr::Number("1234".parse().expect("serde_json::Number parse error")),
3414 );
3415 check_simple_lit_expr(
3416 spec,
3417 "$(1234.5678)",
3418 LitExpr::Number("1234.5678".parse().expect("serde_json::Number parse error")),
3419 );
3420
3421 check_simple_lit_expr(
3422 spec,
3423 "$('hello world')",
3424 LitExpr::String("hello world".to_string()),
3425 );
3426 check_simple_lit_expr(
3427 spec,
3428 "$(\"hello world\")",
3429 LitExpr::String("hello world".to_string()),
3430 );
3431 check_simple_lit_expr(
3432 spec,
3433 "$(\"hello \\\"world\\\"\")",
3434 LitExpr::String("hello \"world\"".to_string()),
3435 );
3436
3437 check_simple_lit_expr(
3438 spec,
3439 "$([1, 2, 3])",
3440 LitExpr::Array(
3441 vec!["1".parse(), "2".parse(), "3".parse()]
3442 .into_iter()
3443 .map(|n| {
3444 LitExpr::Number(n.expect("serde_json::Number parse error"))
3445 .into_with_range()
3446 })
3447 .collect(),
3448 ),
3449 );
3450
3451 if matches!(
3456 spec,
3457 ConnectSpec::V0_1 | ConnectSpec::V0_2 | ConnectSpec::V0_3
3458 ) {
3459 check_simple_lit_expr(spec, "$({})", LitExpr::LegacyObject(IndexMap::default()));
3460 check_simple_lit_expr(
3461 spec,
3462 "$({ a: 1, b: 2, c: 3 })",
3463 LitExpr::LegacyObject({
3464 let mut map = IndexMap::default();
3465 for (key, value) in &[("a", "1"), ("b", "2"), ("c", "3")] {
3466 map.insert(
3467 Key::field(key).into_with_range(),
3468 LitExpr::Number(value.parse().expect("serde_json::Number parse error"))
3469 .into_with_range(),
3470 );
3471 }
3472 map
3473 }),
3474 );
3475 }
3476 }
3477
3478 #[test]
3479 fn test_path_expr_with_spaces_v0_2() {
3480 assert_debug_snapshot!(selection!(
3481 " suffix : results -> slice ( $( - 1 ) -> mul ( $args . suffixLength ) ) ",
3482 ConnectSpec::V0_2
3487 ));
3488 }
3489
3490 #[test]
3491 fn test_path_expr_with_spaces_v0_3() {
3492 assert_debug_snapshot!(selection!(
3493 " suffix : results -> slice ( $( - 1 ) -> mul ( $args . suffixLength ) ) ",
3494 ConnectSpec::V0_3
3495 ));
3496 }
3497
3498 #[rstest]
3499 #[case::v0_2(ConnectSpec::V0_2)]
3500 #[case::v0_3(ConnectSpec::V0_3)]
3501 #[case::v0_4(ConnectSpec::V0_4)]
3502 fn test_path_methods(#[case] spec: ConnectSpec) {
3503 check_path_selection(
3504 spec,
3505 "data.x->or(data.y)",
3506 PathSelection {
3507 path: PathList::Key(
3508 Key::field("data").into_with_range(),
3509 PathList::Key(
3510 Key::field("x").into_with_range(),
3511 PathList::Method(
3512 WithRange::new("or".to_string(), None),
3513 Some(MethodArgs {
3514 args: vec![
3515 LitExpr::Path(PathSelection::from_slice(
3516 &[Key::field("data"), Key::field("y")],
3517 None,
3518 ))
3519 .into_with_range(),
3520 ],
3521 ..Default::default()
3522 }),
3523 PathList::Empty.into_with_range(),
3524 )
3525 .into_with_range(),
3526 )
3527 .into_with_range(),
3528 )
3529 .into_with_range(),
3530 },
3531 );
3532
3533 {
3534 fn make_dollar_key_expr(key: &str) -> WithRange<LitExpr> {
3535 WithRange::new(
3536 LitExpr::Path(PathSelection {
3537 path: PathList::Var(
3538 KnownVariable::Dollar.into_with_range(),
3539 PathList::Key(
3540 Key::field(key).into_with_range(),
3541 PathList::Empty.into_with_range(),
3542 )
3543 .into_with_range(),
3544 )
3545 .into_with_range(),
3546 }),
3547 None,
3548 )
3549 }
3550
3551 let expected = PathSelection {
3552 path: PathList::Key(
3553 Key::field("data").into_with_range(),
3554 PathList::Method(
3555 WithRange::new("query".to_string(), None),
3556 Some(MethodArgs {
3557 args: vec![
3558 make_dollar_key_expr("a"),
3559 make_dollar_key_expr("b"),
3560 make_dollar_key_expr("c"),
3561 ],
3562 ..Default::default()
3563 }),
3564 PathList::Empty.into_with_range(),
3565 )
3566 .into_with_range(),
3567 )
3568 .into_with_range(),
3569 };
3570 check_path_selection(spec, "data->query($.a, $.b, $.c)", expected.clone());
3571 check_path_selection(spec, "data->query($.a, $.b, $.c )", expected.clone());
3572 check_path_selection(spec, "data->query($.a, $.b, $.c,)", expected.clone());
3573 check_path_selection(spec, "data->query($.a, $.b, $.c ,)", expected.clone());
3574 check_path_selection(spec, "data->query($.a, $.b, $.c , )", expected);
3575 }
3576
3577 {
3578 let expected = PathSelection {
3579 path: PathList::Key(
3580 Key::field("data").into_with_range(),
3581 PathList::Key(
3582 Key::field("x").into_with_range(),
3583 PathList::Method(
3584 WithRange::new("concat".to_string(), None),
3585 Some(MethodArgs {
3586 args: vec![
3587 LitExpr::Array(vec![
3588 LitExpr::Path(PathSelection::from_slice(
3589 &[Key::field("data"), Key::field("y")],
3590 None,
3591 ))
3592 .into_with_range(),
3593 LitExpr::Path(PathSelection::from_slice(
3594 &[Key::field("data"), Key::field("z")],
3595 None,
3596 ))
3597 .into_with_range(),
3598 ])
3599 .into_with_range(),
3600 ],
3601 ..Default::default()
3602 }),
3603 PathList::Empty.into_with_range(),
3604 )
3605 .into_with_range(),
3606 )
3607 .into_with_range(),
3608 )
3609 .into_with_range(),
3610 };
3611 check_path_selection(spec, "data.x->concat([data.y, data.z])", expected.clone());
3612 check_path_selection(spec, "data.x->concat([ data.y, data.z ])", expected.clone());
3613 check_path_selection(spec, "data.x->concat([data.y, data.z,])", expected.clone());
3614 check_path_selection(
3615 spec,
3616 "data.x->concat([data.y, data.z , ])",
3617 expected.clone(),
3618 );
3619 check_path_selection(spec, "data.x->concat([data.y, data.z,],)", expected.clone());
3620 check_path_selection(spec, "data.x->concat([data.y, data.z , ] , )", expected);
3621 }
3622
3623 check_path_selection(
3624 spec,
3625 "data->method([$ { x2: x->times(2) }, $ { y2: y->times(2) }])",
3626 PathSelection {
3627 path: PathList::Key(
3628 Key::field("data").into_with_range(),
3629 PathList::Method(
3630 WithRange::new("method".to_string(), None),
3631 Some(MethodArgs {
3632 args: vec![LitExpr::Array(vec![
3633 LitExpr::Path(PathSelection {
3634 path: PathList::Var(
3635 KnownVariable::Dollar.into_with_range(),
3636 PathList::Selection(
3637 SubSelection {
3638 selections: vec![NamedSelection {
3639 prefix: NamingPrefix::Alias(Alias::new("x2")),
3640 path: path_value(PathSelection {
3641 path: PathList::Key(
3642 Key::field("x").into_with_range(),
3643 PathList::Method(
3644 WithRange::new(
3645 "times".to_string(),
3646 None,
3647 ),
3648 Some(MethodArgs {
3649 args: vec![LitExpr::Number(
3650 "2".parse().expect(
3651 "serde_json::Number parse error",
3652 ),
3653 ).into_with_range()],
3654 ..Default::default()
3655 }),
3656 PathList::Empty.into_with_range(),
3657 )
3658 .into_with_range(),
3659 )
3660 .into_with_range(),
3661 }),
3662 }],
3663 ..Default::default()
3664 },
3665 )
3666 .into_with_range(),
3667 )
3668 .into_with_range(),
3669 })
3670 .into_with_range(),
3671 LitExpr::Path(PathSelection {
3672 path: PathList::Var(
3673 KnownVariable::Dollar.into_with_range(),
3674 PathList::Selection(
3675 SubSelection {
3676 selections: vec![NamedSelection {
3677 prefix: NamingPrefix::Alias(Alias::new("y2")),
3678 path: path_value(PathSelection {
3679 path: PathList::Key(
3680 Key::field("y").into_with_range(),
3681 PathList::Method(
3682 WithRange::new(
3683 "times".to_string(),
3684 None,
3685 ),
3686 Some(
3687 MethodArgs {
3688 args: vec![LitExpr::Number(
3689 "2".parse().expect(
3690 "serde_json::Number parse error",
3691 ),
3692 ).into_with_range()],
3693 ..Default::default()
3694 },
3695 ),
3696 PathList::Empty.into_with_range(),
3697 )
3698 .into_with_range(),
3699 )
3700 .into_with_range(),
3701 }),
3702 }],
3703 ..Default::default()
3704 },
3705 )
3706 .into_with_range(),
3707 )
3708 .into_with_range(),
3709 })
3710 .into_with_range(),
3711 ])
3712 .into_with_range()],
3713 ..Default::default()
3714 }),
3715 PathList::Empty.into_with_range(),
3716 )
3717 .into_with_range(),
3718 )
3719 .into_with_range(),
3720 },
3721 );
3722 }
3723
3724 #[test]
3725 fn test_path_with_subselection() {
3726 assert_debug_snapshot!(selection!(
3727 r#"
3728 choices->first.message { content role }
3729 "#
3730 ));
3731
3732 assert_debug_snapshot!(selection!(
3733 r#"
3734 id
3735 created
3736 choices->first.message { content role }
3737 model
3738 "#
3739 ));
3740
3741 assert_debug_snapshot!(selection!(
3742 r#"
3743 id
3744 created
3745 choices->first.message { content role }
3746 model
3747 choices->last.message { lastContent: content }
3748 "#
3749 ));
3750
3751 assert_debug_snapshot!(JSONSelection::parse(
3752 r#"
3753 id
3754 created
3755 choices->first.message
3756 model
3757 "#
3758 ));
3759
3760 assert_debug_snapshot!(JSONSelection::parse(
3761 r#"
3762 id: $this.id
3763 $args.input {
3764 title
3765 body
3766 }
3767 "#
3768 ));
3769
3770 assert_debug_snapshot!(JSONSelection::parse(
3773 r#"
3774 $this { id }
3775 $args { $.input { title body } }
3776 "#
3777 ));
3778
3779 assert_debug_snapshot!(JSONSelection::parse(
3780 r#"
3781 # Equivalent to id: $this.id
3782 $this { id }
3783
3784 $args {
3785 __typename: $("Args")
3786
3787 # Using $. instead of just . prevents .input from
3788 # parsing as a key applied to the $("Args") string.
3789 $.input { title body }
3790
3791 extra
3792 }
3793
3794 from: $.from
3795 "#
3796 ));
3797 }
3798
3799 #[test]
3800 fn test_subselection() {
3801 fn check_parsed(input: &str, expected: SubSelection) {
3802 let (remainder, parsed) = SubSelection::parse(new_span(input)).unwrap();
3803 assert!(
3804 span_is_all_spaces_or_comments(remainder.clone()),
3805 "remainder is `{:?}`",
3806 remainder.clone(),
3807 );
3808 assert_eq!(parsed.strip_ranges(), expected);
3809 }
3810
3811 check_parsed(
3812 " { \n } ",
3813 SubSelection {
3814 selections: vec![],
3815 ..Default::default()
3816 },
3817 );
3818
3819 check_parsed(
3820 "{hello}",
3821 SubSelection {
3822 selections: vec![NamedSelection::field(
3823 None,
3824 Key::field("hello").into_with_range(),
3825 None,
3826 )],
3827 ..Default::default()
3828 },
3829 );
3830
3831 check_parsed(
3832 "{ hello }",
3833 SubSelection {
3834 selections: vec![NamedSelection::field(
3835 None,
3836 Key::field("hello").into_with_range(),
3837 None,
3838 )],
3839 ..Default::default()
3840 },
3841 );
3842
3843 check_parsed(
3844 " { padded } ",
3845 SubSelection {
3846 selections: vec![NamedSelection::field(
3847 None,
3848 Key::field("padded").into_with_range(),
3849 None,
3850 )],
3851 ..Default::default()
3852 },
3853 );
3854
3855 check_parsed(
3856 "{ hello world }",
3857 SubSelection {
3858 selections: vec![
3859 NamedSelection::field(None, Key::field("hello").into_with_range(), None),
3860 NamedSelection::field(None, Key::field("world").into_with_range(), None),
3861 ],
3862 ..Default::default()
3863 },
3864 );
3865
3866 check_parsed(
3867 "{ hello { world } }",
3868 SubSelection {
3869 selections: vec![NamedSelection::field(
3870 None,
3871 Key::field("hello").into_with_range(),
3872 Some(SubSelection {
3873 selections: vec![NamedSelection::field(
3874 None,
3875 Key::field("world").into_with_range(),
3876 None,
3877 )],
3878 ..Default::default()
3879 }),
3880 )],
3881 ..Default::default()
3882 },
3883 );
3884 }
3885
3886 #[test]
3887 fn test_external_var_paths() {
3888 fn parse(input: &str) -> PathSelection {
3889 PathSelection::parse(new_span(input))
3890 .unwrap()
3891 .1
3892 .strip_ranges()
3893 }
3894
3895 {
3896 let sel = selection!(
3897 r#"
3898 $->echo([$args.arg1, $args.arg2, @.items->first])
3899 "#
3900 )
3901 .strip_ranges();
3902 let args_arg1_path = parse("$args.arg1");
3903 let args_arg2_path = parse("$args.arg2");
3904 assert_eq!(
3905 sel.external_var_paths(),
3906 vec![&args_arg1_path, &args_arg2_path]
3907 );
3908 }
3909 {
3910 let sel = selection!(
3911 r#"
3912 $this.kind->match(
3913 ["A", $this.a],
3914 ["B", $this.b],
3915 ["C", $this.c],
3916 [@, @->to_lower_case],
3917 )
3918 "#
3919 )
3920 .strip_ranges();
3921 let this_kind_path = match &sel.inner {
3922 TopLevelSelection::Value(lit) => match lit.as_ref() {
3923 LitExpr::Path(path) => path,
3924 _ => panic!("Expected PathSelection"),
3925 },
3926 _ => panic!("Expected PathSelection"),
3927 };
3928 let this_a_path = parse("$this.a");
3929 let this_b_path = parse("$this.b");
3930 let this_c_path = parse("$this.c");
3931 assert_eq!(
3932 sel.external_var_paths(),
3933 vec![this_kind_path, &this_a_path, &this_b_path, &this_c_path,]
3934 );
3935 }
3936 {
3937 let sel = selection!(
3938 r#"
3939 data.results->slice($args.start, $args.end) {
3940 id
3941 __typename: $args.type
3942 }
3943 "#
3944 )
3945 .strip_ranges();
3946 let start_path = parse("$args.start");
3947 let end_path = parse("$args.end");
3948 let args_type_path = parse("$args.type");
3949 assert_eq!(
3950 sel.external_var_paths(),
3951 vec![&start_path, &end_path, &args_type_path]
3952 );
3953 }
3954 }
3955
3956 #[test]
3957 fn test_local_var_paths() {
3958 let spec = ConnectSpec::V0_3;
3959 let name_selection = selection!(
3960 "person->as($name, @.name)->as($stray, 123)->echo({ hello: $name })",
3961 spec
3962 );
3963 let local_var_names = name_selection.local_var_names();
3964 assert_eq!(local_var_names.len(), 2);
3965 assert!(local_var_names.contains("$name"));
3966 assert!(local_var_names.contains("$stray"));
3967 }
3968
3969 #[test]
3970 fn test_ranged_locations() {
3971 fn check(input: &str, expected: JSONSelection) {
3972 let parsed = JSONSelection::parse(input).unwrap();
3973 assert_eq!(parsed, expected);
3974 }
3975
3976 check(
3977 "hello",
3978 JSONSelection::named(SubSelection {
3979 selections: vec![NamedSelection::field(
3980 None,
3981 WithRange::new(Key::field("hello"), Some(0..5)),
3982 None,
3983 )],
3984 range: Some(0..5),
3985 }),
3986 );
3987
3988 check(
3989 " hello ",
3990 JSONSelection::named(SubSelection {
3991 selections: vec![NamedSelection::field(
3992 None,
3993 WithRange::new(Key::field("hello"), Some(2..7)),
3994 None,
3995 )],
3996 range: Some(2..7),
3997 }),
3998 );
3999
4000 check(
4001 " hello { hi name }",
4002 JSONSelection::named(SubSelection {
4003 selections: vec![NamedSelection::field(
4004 None,
4005 WithRange::new(Key::field("hello"), Some(2..7)),
4006 Some(SubSelection {
4007 selections: vec![
4008 NamedSelection::field(
4009 None,
4010 WithRange::new(Key::field("hi"), Some(11..13)),
4011 None,
4012 ),
4013 NamedSelection::field(
4014 None,
4015 WithRange::new(Key::field("name"), Some(14..18)),
4016 None,
4017 ),
4018 ],
4019 range: Some(9..20),
4020 }),
4021 )],
4022 range: Some(2..20),
4023 }),
4024 );
4025
4026 check(
4027 "$args.product.id",
4028 JSONSelection::path(PathSelection {
4029 path: WithRange::new(
4030 PathList::Var(
4031 WithRange::new(
4032 KnownVariable::External(Namespace::Args.to_string()),
4033 Some(0..5),
4034 ),
4035 WithRange::new(
4036 PathList::Key(
4037 WithRange::new(Key::field("product"), Some(6..13)),
4038 WithRange::new(
4039 PathList::Key(
4040 WithRange::new(Key::field("id"), Some(14..16)),
4041 WithRange::new(PathList::Empty, Some(16..16)),
4042 ),
4043 Some(13..16),
4044 ),
4045 ),
4046 Some(5..16),
4047 ),
4048 ),
4049 Some(0..16),
4050 ),
4051 }),
4052 );
4053
4054 check(
4055 " $args . product . id ",
4056 JSONSelection::path(PathSelection {
4057 path: WithRange::new(
4058 PathList::Var(
4059 WithRange::new(
4060 KnownVariable::External(Namespace::Args.to_string()),
4061 Some(1..6),
4062 ),
4063 WithRange::new(
4064 PathList::Key(
4065 WithRange::new(Key::field("product"), Some(9..16)),
4066 WithRange::new(
4067 PathList::Key(
4068 WithRange::new(Key::field("id"), Some(19..21)),
4069 WithRange::new(PathList::Empty, Some(21..21)),
4070 ),
4071 Some(17..21),
4072 ),
4073 ),
4074 Some(7..21),
4075 ),
4076 ),
4077 Some(1..21),
4078 ),
4079 }),
4080 );
4081
4082 check(
4083 "before product:$args.product{id name}after",
4084 JSONSelection::named(SubSelection {
4085 selections: vec![
4086 NamedSelection::field(
4087 None,
4088 WithRange::new(Key::field("before"), Some(0..6)),
4089 None,
4090 ),
4091 NamedSelection {
4092 prefix: NamingPrefix::Alias(Alias {
4093 name: WithRange::new(Key::field("product"), Some(7..14)),
4094 range: Some(7..15),
4095 }),
4096 path: path_value(PathSelection {
4097 path: WithRange::new(
4098 PathList::Var(
4099 WithRange::new(
4100 KnownVariable::External(Namespace::Args.to_string()),
4101 Some(15..20),
4102 ),
4103 WithRange::new(
4104 PathList::Key(
4105 WithRange::new(Key::field("product"), Some(21..28)),
4106 WithRange::new(
4107 PathList::Selection(SubSelection {
4108 selections: vec![
4109 NamedSelection::field(
4110 None,
4111 WithRange::new(
4112 Key::field("id"),
4113 Some(29..31),
4114 ),
4115 None,
4116 ),
4117 NamedSelection::field(
4118 None,
4119 WithRange::new(
4120 Key::field("name"),
4121 Some(32..36),
4122 ),
4123 None,
4124 ),
4125 ],
4126 range: Some(28..37),
4127 }),
4128 Some(28..37),
4129 ),
4130 ),
4131 Some(20..37),
4132 ),
4133 ),
4134 Some(15..37),
4135 ),
4136 }),
4137 },
4138 NamedSelection::field(
4139 None,
4140 WithRange::new(Key::field("after"), Some(37..42)),
4141 None,
4142 ),
4143 ],
4144 range: Some(0..42),
4145 }),
4146 );
4147 }
4148
4149 #[test]
4150 fn test_variable_reference_no_path() {
4151 let selection = JSONSelection::parse("$this").unwrap();
4152 let var_paths = selection.external_var_paths();
4153 assert_eq!(var_paths.len(), 1);
4154 assert_eq!(
4155 var_paths[0].variable_reference(),
4156 Some(VariableReference {
4157 namespace: VariableNamespace {
4158 namespace: Namespace::This,
4159 location: Some(0..5),
4160 },
4161 selection: {
4162 let mut selection = SelectionTrie::new();
4163 selection.add_str_path([]);
4164 selection
4165 },
4166 location: Some(0..5),
4167 })
4168 );
4169 }
4170
4171 #[test]
4172 fn test_variable_reference_with_path() {
4173 let selection = JSONSelection::parse("$this.a.b.c").unwrap();
4174 let var_paths = selection.external_var_paths();
4175 assert_eq!(var_paths.len(), 1);
4176
4177 let var_ref = var_paths[0].variable_reference().unwrap();
4178 assert_eq!(
4179 var_ref.namespace,
4180 VariableNamespace {
4181 namespace: Namespace::This,
4182 location: Some(0..5)
4183 }
4184 );
4185 assert_eq!(var_ref.selection.to_string(), "a { b { c } }");
4186 assert_eq!(var_ref.location, Some(0..11));
4187
4188 assert_eq!(
4189 var_ref.selection.key_ranges("a").collect::<Vec<_>>(),
4190 vec![6..7]
4191 );
4192 let a_trie = var_ref.selection.get("a").unwrap();
4193 assert_eq!(a_trie.key_ranges("b").collect::<Vec<_>>(), vec![8..9]);
4194 let b_trie = a_trie.get("b").unwrap();
4195 assert_eq!(b_trie.key_ranges("c").collect::<Vec<_>>(), vec![10..11]);
4196 }
4197
4198 #[test]
4199 fn test_variable_reference_nested() {
4200 let selection = JSONSelection::parse("a b { c: $this.x.y.z { d } }").unwrap();
4201 let var_paths = selection.external_var_paths();
4202 assert_eq!(var_paths.len(), 1);
4203
4204 let var_ref = var_paths[0].variable_reference().unwrap();
4205 assert_eq!(
4206 var_ref.namespace,
4207 VariableNamespace {
4208 namespace: Namespace::This,
4209 location: Some(9..14),
4210 }
4211 );
4212 assert_eq!(var_ref.selection.to_string(), "x { y { z { d } } }");
4213 assert_eq!(var_ref.location, Some(9..26));
4214
4215 assert_eq!(
4216 var_ref.selection.key_ranges("x").collect::<Vec<_>>(),
4217 vec![15..16]
4218 );
4219 let x_trie = var_ref.selection.get("x").unwrap();
4220 assert_eq!(x_trie.key_ranges("y").collect::<Vec<_>>(), vec![17..18]);
4221 let y_trie = x_trie.get("y").unwrap();
4222 assert_eq!(y_trie.key_ranges("z").collect::<Vec<_>>(), vec![19..20]);
4223 let z_trie = y_trie.get("z").unwrap();
4224 assert_eq!(z_trie.key_ranges("d").collect::<Vec<_>>(), vec![23..24]);
4225 }
4226
4227 #[test]
4241 fn test_variable_reference_through_filter_subselection() {
4242 let selection = JSONSelection::parse_with_spec(
4243 "$this.items->filter(@.product) { id name }",
4244 ConnectSpec::V0_4,
4245 )
4246 .unwrap();
4247 let var_paths = selection.external_var_paths();
4248 assert_eq!(var_paths.len(), 1);
4249
4250 let var_ref = var_paths[0]
4251 .variable_reference::<crate::connectors::Namespace>()
4252 .unwrap();
4253 assert_eq!(
4254 var_ref.namespace.namespace,
4255 crate::connectors::Namespace::This
4256 );
4257 assert_eq!(var_ref.selection.to_string(), "items { id name product }");
4258 }
4259
4260 #[test]
4261 fn test_external_var_paths_no_variable() {
4262 let selection = JSONSelection::parse("a.b.c").unwrap();
4263 let var_paths = selection.external_var_paths();
4264 assert_eq!(var_paths.len(), 0);
4265 }
4266
4267 #[test]
4268 fn test_naked_literal_path_for_connect_v0_2() {
4269 let spec = ConnectSpec::V0_2;
4270
4271 let selection_null_stringify_v0_2 = selection!("$(null->jsonStringify)", spec);
4272 assert_eq!(
4273 selection_null_stringify_v0_2.pretty_print(),
4274 "$(null->jsonStringify)"
4275 );
4276
4277 let selection_hello_slice_v0_2 = selection!("sliced: $('hello'->slice(1, 3))", spec);
4278 assert_eq!(
4279 selection_hello_slice_v0_2.pretty_print(),
4280 "sliced: $(\"hello\"->slice(1, 3))"
4281 );
4282
4283 let selection_true_not_v0_2 = selection!("true->not", spec);
4284 assert_eq!(selection_true_not_v0_2.pretty_print(), "true->not");
4285
4286 let selection_false_not_v0_2 = selection!("false->not", spec);
4287 assert_eq!(selection_false_not_v0_2.pretty_print(), "false->not");
4288
4289 let selection_object_path_v0_2 = selection!("$({ a: 123 }.a)", spec);
4290 assert_eq!(
4291 selection_object_path_v0_2.pretty_print_with_indentation(true, 0),
4292 "$({ a: 123 }.a)"
4293 );
4294
4295 let selection_array_path_v0_2 = selection!("$([1, 2, 3]->get(1))", spec);
4296 assert_eq!(
4297 selection_array_path_v0_2.pretty_print(),
4298 "$([1, 2, 3]->get(1))"
4299 );
4300
4301 assert_debug_snapshot!(selection_null_stringify_v0_2);
4302 assert_debug_snapshot!(selection_hello_slice_v0_2);
4303 assert_debug_snapshot!(selection_true_not_v0_2);
4304 assert_debug_snapshot!(selection_false_not_v0_2);
4305 assert_debug_snapshot!(selection_object_path_v0_2);
4306 assert_debug_snapshot!(selection_array_path_v0_2);
4307 }
4308
4309 #[test]
4310 fn test_optional_key_access() {
4311 let spec = ConnectSpec::V0_3;
4312
4313 check_path_selection(
4314 spec,
4315 "$.foo?.bar",
4316 PathSelection {
4317 path: PathList::Var(
4318 KnownVariable::Dollar.into_with_range(),
4319 PathList::Key(
4320 Key::field("foo").into_with_range(),
4321 PathList::Question(
4322 PathList::Key(
4323 Key::field("bar").into_with_range(),
4324 PathList::Empty.into_with_range(),
4325 )
4326 .into_with_range(),
4327 )
4328 .into_with_range(),
4329 )
4330 .into_with_range(),
4331 )
4332 .into_with_range(),
4333 },
4334 );
4335 }
4336
4337 #[test]
4338 fn test_unambiguous_single_key_paths_v0_2() {
4339 let spec = ConnectSpec::V0_2;
4340
4341 let mul_with_dollars = selection!("a->mul($.b, $.c)", spec);
4342 match mul_with_dollars.top_level() {
4343 TopLevelSelection::Value(lit) => match lit.as_ref() {
4344 LitExpr::Path(path) => {
4345 assert_eq!(path.get_single_key(), None);
4346 assert_eq!(path.pretty_print(), "a->mul($.b, $.c)");
4347 }
4348 other => panic!("Expected a path selection, got {other:?}"),
4349 },
4350 other => panic!("Expected a path selection, got {other:?}"),
4351 }
4352
4353 assert_debug_snapshot!(mul_with_dollars);
4354 }
4355
4356 #[test]
4357 fn test_invalid_single_key_paths_v0_2() {
4358 let spec = ConnectSpec::V0_2;
4359
4360 let a_plus_b_plus_c = JSONSelection::parse_with_spec("a->add(b, c)", spec);
4361 assert_eq!(a_plus_b_plus_c, Err(JSONSelectionParseError {
4362 message: "Named path selection must either begin with alias or ..., or end with subselection".to_string(),
4363 fragment: "a->add(b, c)".to_string(),
4364 offset: 0,
4365 spec: ConnectSpec::V0_2,
4366 }));
4367
4368 let sum_a_plus_b_plus_c = JSONSelection::parse_with_spec("sum: a->add(b, c)", spec);
4369 assert_eq!(
4370 sum_a_plus_b_plus_c,
4371 Err(JSONSelectionParseError {
4372 message: "nom::error::ErrorKind::Eof".to_string(),
4373 fragment: "(b, c)".to_string(),
4374 offset: 11,
4375 spec: ConnectSpec::V0_2,
4376 })
4377 );
4378 }
4379
4380 #[test]
4381 fn test_optional_method_call() {
4382 let spec = ConnectSpec::V0_3;
4383
4384 check_path_selection(
4385 spec,
4386 "$.foo?->method",
4387 PathSelection {
4388 path: PathList::Var(
4389 KnownVariable::Dollar.into_with_range(),
4390 PathList::Key(
4391 Key::field("foo").into_with_range(),
4392 PathList::Question(
4393 PathList::Method(
4394 WithRange::new("method".to_string(), None),
4395 None,
4396 PathList::Empty.into_with_range(),
4397 )
4398 .into_with_range(),
4399 )
4400 .into_with_range(),
4401 )
4402 .into_with_range(),
4403 )
4404 .into_with_range(),
4405 },
4406 );
4407 }
4408
4409 #[test]
4410 fn test_chained_optional_accesses() {
4411 let spec = ConnectSpec::V0_3;
4412
4413 check_path_selection(
4414 spec,
4415 "$.foo?.bar?.baz",
4416 PathSelection {
4417 path: PathList::Var(
4418 KnownVariable::Dollar.into_with_range(),
4419 PathList::Key(
4420 Key::field("foo").into_with_range(),
4421 PathList::Question(
4422 PathList::Key(
4423 Key::field("bar").into_with_range(),
4424 PathList::Question(
4425 PathList::Key(
4426 Key::field("baz").into_with_range(),
4427 PathList::Empty.into_with_range(),
4428 )
4429 .into_with_range(),
4430 )
4431 .into_with_range(),
4432 )
4433 .into_with_range(),
4434 )
4435 .into_with_range(),
4436 )
4437 .into_with_range(),
4438 )
4439 .into_with_range(),
4440 },
4441 );
4442 }
4443
4444 #[test]
4445 fn test_mixed_regular_and_optional_access() {
4446 let spec = ConnectSpec::V0_3;
4447
4448 check_path_selection(
4449 spec,
4450 "$.foo.bar?.baz",
4451 PathSelection {
4452 path: PathList::Var(
4453 KnownVariable::Dollar.into_with_range(),
4454 PathList::Key(
4455 Key::field("foo").into_with_range(),
4456 PathList::Key(
4457 Key::field("bar").into_with_range(),
4458 PathList::Question(
4459 PathList::Key(
4460 Key::field("baz").into_with_range(),
4461 PathList::Empty.into_with_range(),
4462 )
4463 .into_with_range(),
4464 )
4465 .into_with_range(),
4466 )
4467 .into_with_range(),
4468 )
4469 .into_with_range(),
4470 )
4471 .into_with_range(),
4472 },
4473 );
4474 }
4475
4476 #[test]
4477 fn test_invalid_sequential_question_marks() {
4478 let spec = ConnectSpec::V0_3;
4479
4480 assert_eq!(
4481 JSONSelection::parse_with_spec("baz: $.foo??.bar", spec),
4482 Err(JSONSelectionParseError {
4483 message: "nom::error::ErrorKind::Eof".to_string(),
4484 fragment: "??.bar".to_string(),
4485 offset: 10,
4486 spec,
4487 }),
4488 );
4489
4490 assert_eq!(
4491 JSONSelection::parse_with_spec("baz: $.foo?->echo(null)??.bar", spec),
4492 Err(JSONSelectionParseError {
4493 message: "nom::error::ErrorKind::Eof".to_string(),
4494 fragment: "??.bar".to_string(),
4495 offset: 23,
4496 spec,
4497 }),
4498 );
4499 }
4500
4501 #[test]
4502 fn test_invalid_infix_operator_parsing() {
4503 let spec = ConnectSpec::V0_2;
4504
4505 assert_eq!(
4506 JSONSelection::parse_with_spec("aOrB: $($.a ?? $.b)", spec),
4507 Err(JSONSelectionParseError {
4508 message: "nom::error::ErrorKind::Eof".to_string(),
4509 fragment: "($.a ?? $.b)".to_string(),
4510 offset: 7,
4511 spec,
4512 }),
4513 );
4514
4515 assert_eq!(
4516 JSONSelection::parse_with_spec("aOrB: $($.a ?! $.b)", spec),
4517 Err(JSONSelectionParseError {
4518 message: "nom::error::ErrorKind::Eof".to_string(),
4519 fragment: "($.a ?! $.b)".to_string(),
4520 offset: 7,
4521 spec,
4522 }),
4523 );
4524 }
4525
4526 #[test]
4527 fn test_optional_chaining_with_subselection() {
4528 let spec = ConnectSpec::V0_3;
4529
4530 check_path_selection(
4531 spec,
4532 "$.foo?.bar { id name }",
4533 PathSelection {
4534 path: PathList::Var(
4535 KnownVariable::Dollar.into_with_range(),
4536 PathList::Key(
4537 Key::field("foo").into_with_range(),
4538 PathList::Question(
4539 PathList::Key(
4540 Key::field("bar").into_with_range(),
4541 PathList::Selection(SubSelection {
4542 selections: vec![
4543 NamedSelection::field(
4544 None,
4545 Key::field("id").into_with_range(),
4546 None,
4547 ),
4548 NamedSelection::field(
4549 None,
4550 Key::field("name").into_with_range(),
4551 None,
4552 ),
4553 ],
4554 ..Default::default()
4555 })
4556 .into_with_range(),
4557 )
4558 .into_with_range(),
4559 )
4560 .into_with_range(),
4561 )
4562 .into_with_range(),
4563 )
4564 .into_with_range(),
4565 },
4566 );
4567 }
4568
4569 #[test]
4570 fn test_optional_method_with_arguments() {
4571 let spec = ConnectSpec::V0_3;
4572
4573 check_path_selection(
4574 spec,
4575 "$.foo?->filter('active')",
4576 PathSelection {
4577 path: PathList::Var(
4578 KnownVariable::Dollar.into_with_range(),
4579 PathList::Key(
4580 Key::field("foo").into_with_range(),
4581 PathList::Question(
4582 PathList::Method(
4583 WithRange::new("filter".to_string(), None),
4584 Some(MethodArgs {
4585 args: vec![
4586 LitExpr::String("active".to_string()).into_with_range(),
4587 ],
4588 ..Default::default()
4589 }),
4590 PathList::Empty.into_with_range(),
4591 )
4592 .into_with_range(),
4593 )
4594 .into_with_range(),
4595 )
4596 .into_with_range(),
4597 )
4598 .into_with_range(),
4599 },
4600 );
4601 }
4602
4603 #[test]
4604 fn test_unambiguous_single_key_paths_v0_3() {
4605 let spec = ConnectSpec::V0_3;
4606
4607 let mul_with_dollars = selection!("a->mul($.b, $.c)", spec);
4608 match mul_with_dollars.top_level() {
4609 TopLevelSelection::Value(lit) => match lit.as_ref() {
4610 LitExpr::Path(path) => {
4611 assert_eq!(path.get_single_key(), None);
4612 assert_eq!(path.pretty_print(), "a->mul($.b, $.c)");
4613 }
4614 other => panic!("Expected a path selection, got {other:?}"),
4615 },
4616 other => panic!("Expected a path selection, got {other:?}"),
4617 }
4618
4619 assert_debug_snapshot!(mul_with_dollars);
4620 }
4621
4622 #[test]
4623 fn test_valid_single_key_path_v0_3() {
4624 let spec = ConnectSpec::V0_3;
4625
4626 let a_plus_b_plus_c = JSONSelection::parse_with_spec("a->add(b, c)", spec);
4627 if let Ok(selection) = a_plus_b_plus_c {
4628 match selection.top_level() {
4629 TopLevelSelection::Value(lit) => match lit.as_ref() {
4630 LitExpr::Path(path) => {
4631 assert_eq!(path.pretty_print(), "a->add(b, c)");
4632 assert_eq!(path.get_single_key(), None);
4633 }
4634 other => panic!("Expected a path selection, got {other:?}"),
4635 },
4636 other => panic!("Expected a path selection, got {other:?}"),
4637 }
4638 assert_debug_snapshot!(selection);
4639 } else {
4640 panic!("Expected a valid selection, got error: {a_plus_b_plus_c:?}");
4641 }
4642 }
4643
4644 #[test]
4645 fn test_valid_single_key_path_with_alias_v0_3() {
4646 let spec = ConnectSpec::V0_3;
4647
4648 let sum_a_plus_b_plus_c = JSONSelection::parse_with_spec("sum: a->add(b, c)", spec);
4649 if let Ok(selection) = sum_a_plus_b_plus_c {
4650 match selection.top_level() {
4651 TopLevelSelection::Named(named) => {
4652 for selection in named.selections_iter() {
4653 assert_eq!(selection.pretty_print(), "sum: a->add(b, c)");
4654 assert_eq!(
4655 selection.get_single_key().map(|key| key.as_str()),
4656 Some("sum")
4657 );
4658 }
4659 }
4660 other => panic!("Expected any number of named selections, got {other:?}"),
4661 }
4662 assert_debug_snapshot!(selection);
4663 } else {
4664 panic!("Expected a valid selection, got error: {sum_a_plus_b_plus_c:?}");
4665 }
4666 }
4667
4668 #[test]
4669 fn test_disallowed_spread_syntax_error() {
4670 assert_eq!(
4671 JSONSelection::parse_with_spec("id ...names", ConnectSpec::V0_2),
4672 Err(JSONSelectionParseError {
4673 message: "nom::error::ErrorKind::Eof".to_string(),
4674 fragment: "...names".to_string(),
4675 offset: 3,
4676 spec: ConnectSpec::V0_2,
4677 }),
4678 );
4679
4680 assert_eq!(
4681 JSONSelection::parse_with_spec("id ...names", ConnectSpec::V0_3),
4682 Err(JSONSelectionParseError {
4683 message: "Spread syntax (...) is not supported in connect/v0.3 (use connect/v0.4)"
4684 .to_string(),
4685 fragment: "...names".to_string(),
4689 offset: 3,
4690 spec: ConnectSpec::V0_3,
4691 }),
4692 );
4693
4694 }
4697
4698 #[cfg(test)]
4700 mod spread_parsing {
4701 use crate::connectors::ConnectSpec;
4702 use crate::connectors::json_selection::PrettyPrintable;
4703 use crate::selection;
4704
4705 #[track_caller]
4706 pub(super) fn check(spec: ConnectSpec, input: &str, expected_pretty: &str) {
4707 let selection = selection!(input, spec);
4708 assert_eq!(selection.pretty_print(), expected_pretty);
4709 }
4710 }
4711
4712 #[test]
4713 fn test_basic_spread_parsing_one_field() {
4714 let spec = ConnectSpec::V0_4;
4715 let expected = "... a";
4716 spread_parsing::check(spec, "...a", expected);
4717 spread_parsing::check(spec, "... a", expected);
4718 spread_parsing::check(spec, "...a ", expected);
4719 spread_parsing::check(spec, "... a ", expected);
4720 spread_parsing::check(spec, " ... a ", expected);
4721 spread_parsing::check(spec, "...\na", expected);
4722 assert_debug_snapshot!(selection!("...a", spec));
4723 }
4724
4725 #[test]
4726 fn test_spread_parsing_spread_a_spread_b() {
4727 let spec = ConnectSpec::V0_4;
4728 let expected = "... a\n... b";
4729 spread_parsing::check(spec, "...a...b", expected);
4730 spread_parsing::check(spec, "... a ... b", expected);
4731 spread_parsing::check(spec, "... a ...b", expected);
4732 spread_parsing::check(spec, "... a ... b ", expected);
4733 spread_parsing::check(spec, " ... a ... b ", expected);
4734 assert_debug_snapshot!(selection!("...a...b", spec));
4735 }
4736
4737 #[test]
4738 fn test_spread_parsing_a_spread_b() {
4739 let spec = ConnectSpec::V0_4;
4740 let expected = "a\n... b";
4741 spread_parsing::check(spec, "a...b", expected);
4742 spread_parsing::check(spec, "a ... b", expected);
4743 spread_parsing::check(spec, "a\n...b", expected);
4744 spread_parsing::check(spec, "a\n...\nb", expected);
4745 spread_parsing::check(spec, "a...\nb", expected);
4746 spread_parsing::check(spec, " a ... b", expected);
4747 spread_parsing::check(spec, " a ...b", expected);
4748 spread_parsing::check(spec, " a ... b ", expected);
4749 assert_debug_snapshot!(selection!("a...b", spec));
4750 }
4751
4752 #[test]
4753 fn test_spread_parsing_spread_a_b() {
4754 let spec = ConnectSpec::V0_4;
4755 let expected = "... a\nb";
4756 spread_parsing::check(spec, "...a b", expected);
4757 spread_parsing::check(spec, "... a b", expected);
4758 spread_parsing::check(spec, "... a b ", expected);
4759 spread_parsing::check(spec, "... a\nb", expected);
4760 spread_parsing::check(spec, "... a\n b", expected);
4761 spread_parsing::check(spec, " ... a b ", expected);
4762 assert_debug_snapshot!(selection!("...a b", spec));
4763 }
4764
4765 #[test]
4766 fn test_spread_parsing_spread_a_b_c() {
4767 let spec = ConnectSpec::V0_4;
4768 let expected = "... a\nb\nc";
4769 spread_parsing::check(spec, "...a b c", expected);
4770 spread_parsing::check(spec, "... a b c", expected);
4771 spread_parsing::check(spec, "... a b c ", expected);
4772 spread_parsing::check(spec, "... a\nb\nc", expected);
4773 spread_parsing::check(spec, "... a\nb\n c", expected);
4774 spread_parsing::check(spec, " ... a b c ", expected);
4775 spread_parsing::check(spec, "...\na b c", expected);
4776 assert_debug_snapshot!(selection!("...a b c", spec));
4777 }
4778
4779 #[test]
4780 fn test_spread_parsing_spread_spread_a_sub_b() {
4781 let spec = ConnectSpec::V0_4;
4782 let expected = "... a {\n b\n}";
4783 spread_parsing::check(spec, "...a{b}", expected);
4784 spread_parsing::check(spec, "... a { b }", expected);
4785 spread_parsing::check(spec, "...a { b }", expected);
4786 spread_parsing::check(spec, "... a { b } ", expected);
4787 spread_parsing::check(spec, "... a\n{ b }", expected);
4788 spread_parsing::check(spec, "... a\n{b}", expected);
4789 spread_parsing::check(spec, " ... a { b } ", expected);
4790 spread_parsing::check(spec, "...\na { b }", expected);
4791 assert_debug_snapshot!(selection!("...a{b}", spec));
4792 }
4793
4794 #[test]
4795 fn test_spread_parsing_spread_a_sub_b_c() {
4796 let spec = ConnectSpec::V0_4;
4797 let expected = "... a {\n b\n c\n}";
4798 spread_parsing::check(spec, "...a{b c}", expected);
4799 spread_parsing::check(spec, "... a { b c }", expected);
4800 spread_parsing::check(spec, "...a { b c }", expected);
4801 spread_parsing::check(spec, "... a { b c } ", expected);
4802 spread_parsing::check(spec, "... a\n{ b c }", expected);
4803 spread_parsing::check(spec, "... a\n{b c}", expected);
4804 spread_parsing::check(spec, " ... a { b c } ", expected);
4805 spread_parsing::check(spec, "...\na { b c }", expected);
4806 spread_parsing::check(spec, "...\na { b\nc }", expected);
4807 assert_debug_snapshot!(selection!("...a{b c}", spec));
4808 }
4809
4810 #[test]
4811 fn test_spread_parsing_spread_a_sub_b_spread_c() {
4812 let spec = ConnectSpec::V0_4;
4813 let expected = "... a {\n b\n ... c\n}";
4814 spread_parsing::check(spec, "...a{b...c}", expected);
4815 spread_parsing::check(spec, "... a { b ... c }", expected);
4816 spread_parsing::check(spec, "...a { b ... c }", expected);
4817 spread_parsing::check(spec, "... a { b ... c } ", expected);
4818 spread_parsing::check(spec, "... a\n{ b ... c }", expected);
4819 spread_parsing::check(spec, "... a\n{b ... c}", expected);
4820 spread_parsing::check(spec, " ... a { b ... c } ", expected);
4821 spread_parsing::check(spec, "...\na { b ... c }", expected);
4822 spread_parsing::check(spec, "...\na {b ...\nc }", expected);
4823 assert_debug_snapshot!(selection!("...a{b...c}", spec));
4824 }
4825
4826 #[test]
4827 fn test_spread_parsing_spread_a_sub_b_spread_c_d() {
4828 let spec = ConnectSpec::V0_4;
4829 let expected = "... a {\n b\n ... c\n d\n}";
4830 spread_parsing::check(spec, "...a{b...c d}", expected);
4831 spread_parsing::check(spec, "... a { b ... c d }", expected);
4832 spread_parsing::check(spec, "...a { b ... c d }", expected);
4833 spread_parsing::check(spec, "... a { b ... c d } ", expected);
4834 spread_parsing::check(spec, "... a\n{ b ... c d }", expected);
4835 spread_parsing::check(spec, "... a\n{b ... c d}", expected);
4836 spread_parsing::check(spec, " ... a { b ... c d } ", expected);
4837 spread_parsing::check(spec, "...\na { b ... c d }", expected);
4838 spread_parsing::check(spec, "...\na {b ...\nc d }", expected);
4839 assert_debug_snapshot!(selection!("...a{b...c d}", spec));
4840 }
4841
4842 #[test]
4843 fn test_spread_parsing_spread_a_sub_spread_b_c_d_spread_e() {
4844 let spec = ConnectSpec::V0_4;
4845 let expected = "... a {\n ... b\n c\n d\n ... e\n}";
4846 spread_parsing::check(spec, "...a{...b c d...e}", expected);
4847 spread_parsing::check(spec, "... a { ... b c d ... e }", expected);
4848 spread_parsing::check(spec, "...a { ... b c d ... e }", expected);
4849 spread_parsing::check(spec, "... a { ... b c d ... e } ", expected);
4850 spread_parsing::check(spec, "... a\n{ ... b c d ... e }", expected);
4851 spread_parsing::check(spec, "... a\n{... b c d ... e}", expected);
4852 spread_parsing::check(spec, " ... a { ... b c d ... e } ", expected);
4853 spread_parsing::check(spec, "...\na { ... b c d ... e }", expected);
4854 spread_parsing::check(spec, "...\na {...\nb\nc d ...\ne }", expected);
4855 assert_debug_snapshot!(selection!("...a{...b c d...e}", spec));
4856 }
4857
4858 #[test]
4859 fn should_parse_null_coalescing_in_connect_0_3() {
4860 assert!(JSONSelection::parse_with_spec("sum: $(a ?? b)", ConnectSpec::V0_3).is_ok());
4861 assert!(JSONSelection::parse_with_spec("sum: $(a ?! b)", ConnectSpec::V0_3).is_ok());
4862 }
4863
4864 #[test]
4865 fn should_not_parse_null_coalescing_in_connect_0_2() {
4866 assert!(JSONSelection::parse_with_spec("sum: $(a ?? b)", ConnectSpec::V0_2).is_err());
4867 assert!(JSONSelection::parse_with_spec("sum: $(a ?! b)", ConnectSpec::V0_2).is_err());
4868 }
4869
4870 #[test]
4871 fn should_not_parse_mixed_operators_in_same_expression() {
4872 let result = JSONSelection::parse_with_spec("sum: $(a ?? b ?! c)", ConnectSpec::V0_3);
4873
4874 let err = result.expect_err("Expected parse error for mixed operators ?? and ?!");
4875 assert_eq!(
4876 err.message,
4877 "Found mixed operators ?? and ?!. You can only chain operators of the same kind."
4878 );
4879
4880 let result2 = JSONSelection::parse_with_spec("sum: $(a ?! b ?? c)", ConnectSpec::V0_3);
4882 let err2 = result2.expect_err("Expected parse error for mixed operators ?! and ??");
4883 assert_eq!(
4884 err2.message,
4885 "Found mixed operators ?! and ??. You can only chain operators of the same kind."
4886 );
4887 }
4888
4889 #[test]
4890 fn should_parse_mixed_operators_in_nested_expression() {
4891 let result = JSONSelection::parse_with_spec("sum: $(a ?? $(b ?! c))", ConnectSpec::V0_3);
4892
4893 assert!(result.is_ok());
4894 }
4895
4896 #[test]
4897 fn should_parse_local_vars_as_such() {
4898 let spec = ConnectSpec::V0_3;
4899 let all_local = selection!("$->as($root, @.data)->echo([$root, $root])", spec);
4902 assert!(all_local.external_var_paths().is_empty());
4903 assert_debug_snapshot!(all_local);
4904
4905 let ext = selection!("$->as($root, @.data)->echo([$root, $ext])", spec);
4907 let external_vars = ext.external_var_paths();
4908 assert_eq!(external_vars.len(), 1);
4909
4910 for ext_var in &external_vars {
4911 match ext_var.path.as_ref() {
4912 PathList::Var(var, _) => match var.as_ref() {
4913 KnownVariable::External(var_name) => {
4914 assert_eq!(var_name, "$ext");
4915 }
4916 _ => panic!("Expected external variable, got: {var:?}"),
4917 },
4918 _ => panic!(
4919 "Expected variable at start of path, got: {:?}",
4920 &ext_var.path
4921 ),
4922 };
4923 }
4924
4925 assert_debug_snapshot!(ext);
4926 }
4927
4928 #[test]
4929 fn top_level_braced_subselection_v0_4() {
4930 let braced = JSONSelection::parse_with_spec("{ id name }", ConnectSpec::V0_4).unwrap();
4936 let naked = JSONSelection::parse_with_spec("id name", ConnectSpec::V0_4).unwrap();
4937 assert!(
4938 matches!(
4939 &braced.inner,
4940 TopLevelSelection::Value(lit) if matches!(lit.as_ref(), LitExpr::Object(_)),
4941 ),
4942 "expected Value(LitExpr::Object(_)), got {:?}",
4943 braced.inner,
4944 );
4945 assert!(matches!(naked.inner, TopLevelSelection::Named(_)));
4946
4947 let empty_braced = JSONSelection::parse_with_spec("{}", ConnectSpec::V0_4).unwrap();
4949 assert!(matches!(
4950 &empty_braced.inner,
4951 TopLevelSelection::Value(lit) if matches!(lit.as_ref(), LitExpr::Object(_)),
4952 ));
4953 }
4954
4955 #[test]
4956 fn top_level_literal_array_v0_4() {
4957 let array = JSONSelection::parse_with_spec("[1, 2, 3]", ConnectSpec::V0_4).unwrap();
4961 match &array.inner {
4962 TopLevelSelection::Value(lit) => {
4963 assert!(matches!(lit.as_ref(), LitExpr::Array(_)));
4964 }
4965 other => panic!("Expected TopLevelSelection::Value, got: {other:?}"),
4966 }
4967
4968 let empty = JSONSelection::parse_with_spec("[]", ConnectSpec::V0_4).unwrap();
4970 assert!(matches!(
4971 &empty.inner,
4972 TopLevelSelection::Value(lit) if matches!(lit.as_ref(), LitExpr::Array(_)),
4973 ));
4974
4975 let mixed =
4977 JSONSelection::parse_with_spec(r#"[$.foo, 42, "x"]"#, ConnectSpec::V0_4).unwrap();
4978 assert!(matches!(
4979 &mixed.inner,
4980 TopLevelSelection::Value(lit) if matches!(lit.as_ref(), LitExpr::Array(_)),
4981 ));
4982 }
4983
4984 #[test]
4985 fn top_level_braces_and_brackets_rejected_in_v0_3() {
4986 assert!(JSONSelection::parse_with_spec("{ id name }", ConnectSpec::V0_3).is_err());
4989 assert!(JSONSelection::parse_with_spec("[1, 2, 3]", ConnectSpec::V0_3).is_err());
4990 }
4991}