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