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