Skip to main content

apollo_federation/connectors/json_selection/
parser.rs

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
43// ParseResult is the internal type returned by most ::parse methods, as it is
44// convenient to use with nom's combinators. The top-level JSONSelection::parse
45// method returns a slightly different IResult type that hides implementation
46// details of the nom-specific types.
47//
48// TODO Consider switching the third IResult type parameter to VerboseError
49// here, if error messages can be improved with additional context.
50pub(super) type ParseResult<'a, T> = IResult<Span<'a>, T>;
51
52// Generates a non-fatal error with the given suffix and message, allowing the
53// parser to recover and continue.
54pub(super) fn nom_error_message(
55    suffix: Span,
56    // This message type forbids computing error messages with format!, which
57    // might be worthwhile in the future. For now, it's convenient to avoid
58    // String messages so the Span type can remain Copy, so we don't have to
59    // clone spans frequently in the parsing code. In most cases, the suffix
60    // provides the dynamic context needed to interpret the static message.
61    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
73// Generates a fatal error with the given suffix Span and message, causing the
74// parser to abort with the given error message, which is useful after
75// recognizing syntax that completely constrains what follows (like the -> token
76// before a method name), and what follows does not parse as required.
77pub(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    /// Implementers of `VarPaths` must implement this `var_paths` method, which
93    /// should return all variable-referencing paths where the variable is a
94    /// `KnownVariable::External(String)` or `KnownVariable::Local(String)`
95    /// (that is, not internal variable references like `$` or `@`).
96    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    /// Returns all locally bound variable names in the selection, without
112    /// regard for which ones are available where.
113    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// JSONSelection     ::= PathSelection | NakedSubSelection
131// NakedSubSelection ::= NamedSelection* StarSelection?
132
133#[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    // Although we reuse the SubSelection type for the JSONSelection::Named
142    // case, we parse it as a sequence of NamedSelection items without the
143    // {...} curly braces that SubSelection::parse expects.
144    Named(SubSelection),
145    Path(PathSelection),
146}
147
148// To keep JSONSelection::parse consumers from depending on details of the nom
149// error types, JSONSelection::parse reports this custom error type. Other
150// ::parse methods still internally report nom::error::Error for the most part.
151#[derive(thiserror::Error, Debug, PartialEq, Eq, Clone)]
152#[error("{message}: {fragment}")]
153pub struct JSONSelectionParseError {
154    // The message will be a meaningful error message in many cases, but may
155    // fall back to a formatted nom::error::ErrorKind in some cases, e.g. when
156    // an alt(...) runs out of options and we can't determine which underlying
157    // error was "most" responsible.
158    pub message: String,
159
160    // Since we are not exposing the nom_locate-specific Span type, we report
161    // span.fragment() and span.location_offset() here.
162    pub fragment: String,
163
164    // While it might be nice to report a range rather than just an offset, not
165    // all parsing errors have an unambiguous end offset, so the best we can do
166    // is point to the suffix of the input that failed to parse (which
167    // corresponds to where the fragment starts).
168    pub offset: usize,
169
170    // The ConnectSpec version used to parse and apply the selection.
171    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    // JSONSelection::parse is possibly the "most public" method in the entire
219    // file, so it's important that the method signature can remain stable even
220    // if we drastically change implementation details. That's why we use &str
221    // as the input type and a custom JSONSelectionParseError type as the error
222    // type, rather than using Span or nom::error::Error directly.
223    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                    // Use offset and fragment from first error if available
253                    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                // By convention, most ::parse methods do not consume trailing
317                // spaces_or_comments, so we need to consume them here in order
318                // to satisfy the all_consuming requirement.
319                spaces_or_comments,
320            )),
321            all_consuming(terminated(
322                map(SubSelection::parse_naked, |sub| Self {
323                    inner: TopLevelSelection::Named(sub),
324                    spec,
325                }),
326                // It's tempting to hoist the all_consuming(terminated(...))
327                // checks outside the alt((...)) so we only need to handle
328                // trailing spaces_or_comments once, but that won't work because
329                // the Self::Path case should fail when a single PathSelection
330                // cannot be parsed, and that failure typically happens because
331                // the PathSelection::parse method does not consume the entire
332                // input, which is caught by the first all_consuming above.
333                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                        // Usually our nom errors report the original input that
343                        // failed to parse, but that's not helpful here, since
344                        // input corresponds to the entire string, whereas this
345                        // error message is reporting junk at the end of the
346                        // string that should not be there.
347                        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                    // SubSelection::parse_naked already enforces that there
363                    // cannot be more than one NamedSelection if that
364                    // NamedSelection is anonymous, and here's where we divert
365                    // that case into TopLevelSelection::Path rather than
366                    // TopLevelSelection::Named for easier processing later.
367                    //
368                    // The SubSelection may contain multiple inlined selections
369                    // with NamingPrefix::Spread(None) (that is, an anonymous
370                    // path with a trailing SubSelection), which are not
371                    // considered anonymous in that context (because they may
372                    // have zero or more output properties, which they spread
373                    // into the larger result). However, if there is only one
374                    // such ::Spread(None) selection in sub, then "spreading"
375                    // its value into the larger SubSelection is equivalent to
376                    // using its value as the entire output, so we can treat the
377                    // whole thing as a TopLevelSelection::Path selection.
378                    //
379                    // Putting ... first causes NamingPrefix::Spread(Some(_)) to
380                    // be used instead, so the whole selection remains a
381                    // TopLevelSelection::Named, with the additional restriction
382                    // that the argument of the ... must be an object or null
383                    // (not an array). Eventually, we should deprecate spread
384                    // selections without ..., and this complexity will go away.
385                    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            // Most ::parse methods do not consume trailing spaces_or_comments,
398            // but here (at the top level) we need to make sure anything left at
399            // the end of the string is inconsequential, in order to satisfy the
400            // all_consuming combinator above.
401            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                        // Usually our nom errors report the original input that
410                        // failed to parse, but that's not helpful here, since
411                        // input corresponds to the entire string, whereas this
412                        // error message is reporting junk at the end of the
413                        // string that should not be there.
414                        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// NamedSelection       ::= (Alias | "...")? PathSelection | Alias SubSelection
455// PathSelection        ::= Path SubSelection?
456
457#[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    // When a NamedSelection has an Alias, it fully determines the output key,
466    // and any applied values from the path will be assigned to that key.
467    Alias(Alias),
468    // A path can be spread without an explicit ... token, provided it has a
469    // trailing SubSelection (guaranteeing it outputs a static set of object
470    // properties). In those cases, the OffsetRange will be None. When there is
471    // an actual ... token, the OffsetRange will be Some(token_range).
472    Spread(OffsetRange),
473    // When there is no Alias or ... spread token, and the path is not inlined
474    // implicitly due to a trailing SubSelection (which would be represented by
475    // ::Spread(None)), the NamingPrefix is ::None. The NamedSelection may still
476    // produce a single output key if self.path.get_single_key() returns
477    // Some(key), but otherwise it's an anonymous path, which produces only a
478    // JSON value. Singular anonymous paths are allowed at the top level, where
479    // any value they produce directly determines the output of the selection,
480    // but anonymous NamedSelections cannot be mixed together with other
481    // NamedSelections that produce names (in a SubSelection or anywhere else).
482    None,
483}
484
485// Like PathSelection, NamedSelection is an AST structure that takes its range
486// entirely from its children, so NamedSelection itself does not need to provide
487// separate storage for its own range, and therefore does not need to be wrapped
488// as WithRange<NamedSelection>, but merely needs to implement the Ranged trait.
489impl 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            // The empty range is a collapsed range at the end of the
531            // preceding path, i.e. at the end of the field name.
532            let empty_range = name_range.as_ref().map(|range| range.end..range.end);
533            WithRange::new(PathList::Empty, empty_range)
534        };
535        let tail_range = tail.range();
536        let name_tail_range = merge_ranges(name_range, tail_range);
537        let prefix = if let Some(alias) = alias {
538            NamingPrefix::Alias(alias)
539        } else {
540            NamingPrefix::None
541        };
542        Self {
543            prefix,
544            path: PathSelection {
545                path: WithRange::new(PathList::Key(name, tail), name_tail_range),
546            },
547        }
548    }
549
550    pub(crate) fn parse(input: Span) -> ParseResult<Self> {
551        match get_connect_spec(&input) {
552            ConnectSpec::V0_1 | ConnectSpec::V0_2 => Self::parse_v0_2(input),
553            ConnectSpec::V0_3 => Self::parse_v0_3(input),
554            ConnectSpec::V0_4 => Self::parse_v0_4(input),
555        }
556    }
557
558    pub(crate) fn parse_v0_2(input: Span) -> ParseResult<Self> {
559        alt((
560            // We must try parsing NamedPathSelection before NamedFieldSelection
561            // and NamedQuotedSelection because a NamedPathSelection without a
562            // leading `.`, such as `alias: some.nested.path` has a prefix that
563            // can be parsed as a NamedFieldSelection: `alias: some`. Parsing
564            // then fails when it finds the remaining `.nested.path` text. Some
565            // parsers would solve this by forbidding `.` in the "lookahead" for
566            // Named{Field,Quoted}Selection, but negative lookahead is tricky in
567            // nom, so instead we greedily parse NamedPathSelection first.
568            Self::parse_path,
569            Self::parse_field,
570            Self::parse_group,
571        ))(input)
572    }
573
574    fn parse_field(input: Span) -> ParseResult<Self> {
575        tuple((
576            opt(Alias::parse),
577            Key::parse,
578            spaces_or_comments,
579            opt(SubSelection::parse),
580        ))(input)
581        .map(|(remainder, (alias, name, _, selection))| {
582            (remainder, Self::field(alias, name, selection))
583        })
584    }
585
586    // Parses either NamedPathSelection or PathWithSubSelection.
587    fn parse_path(input: Span) -> ParseResult<Self> {
588        if let Ok((remainder, alias)) = Alias::parse(input.clone()) {
589            match PathSelection::parse(remainder) {
590                Ok((remainder, path)) => Ok((
591                    remainder,
592                    Self {
593                        prefix: NamingPrefix::Alias(alias),
594                        path,
595                    },
596                )),
597                Err(nom::Err::Failure(e)) => Err(nom::Err::Failure(e)),
598                Err(_) => Err(nom_error_message(
599                    input.clone(),
600                    "Path selection alias must be followed by a path",
601                )),
602            }
603        } else {
604            match PathSelection::parse(input.clone()) {
605                Ok((remainder, path)) => {
606                    if path.is_anonymous() && path.has_subselection() {
607                        // This covers the old PathWithSubSelection syntax,
608                        // which is like ... in behavior (object properties
609                        // spread into larger object) but without the explicit
610                        // ... token. This syntax still works, provided the path
611                        // is both anonymous and has a trailing SubSelection.
612                        Ok((
613                            remainder,
614                            Self {
615                                prefix: NamingPrefix::Spread(None),
616                                path,
617                            },
618                        ))
619                    } else {
620                        Err(nom_fail_message(
621                            input.clone(),
622                            "Named path selection must either begin with alias or ..., or end with subselection",
623                        ))
624                    }
625                }
626                Err(nom::Err::Failure(e)) => Err(nom::Err::Failure(e)),
627                Err(_) => Err(nom_error_message(
628                    input.clone(),
629                    "Path selection must either begin with alias or ..., or end with subselection",
630                )),
631            }
632        }
633    }
634
635    fn parse_group(input: Span) -> ParseResult<Self> {
636        tuple((Alias::parse, SubSelection::parse))(input).map(|(input, (alias, group))| {
637            let group_range = group.range();
638            (
639                input,
640                NamedSelection {
641                    prefix: NamingPrefix::Alias(alias),
642                    path: PathSelection {
643                        path: WithRange::new(PathList::Selection(group), group_range),
644                    },
645                },
646            )
647        })
648    }
649
650    // NamedSelection ::= (Alias | "...")? PathSelection | Alias SubSelection
651    // V0_3 version: Spread syntax (...) is NOT supported. Preserved for backwards compatibility.
652    fn parse_v0_3(input: Span) -> ParseResult<Self> {
653        let (after_alias, alias) = opt(Alias::parse)(input.clone())?;
654
655        if let Some(alias) = alias {
656            if let Ok((remainder, sub)) = SubSelection::parse(after_alias.clone()) {
657                let sub_range = sub.range();
658                return Ok((
659                    remainder,
660                    Self {
661                        prefix: NamingPrefix::Alias(alias),
662                        // This is what used to be called a NamedGroupSelection
663                        // in the grammar, where an Alias SubSelection can be
664                        // used to assign a nested name (the Alias) to a
665                        // selection of fields from the current object.
666                        // Logically, this corresponds to an Alias followed by a
667                        // PathSelection with an empty/missing Path. While there
668                        // is no way to write such a PathSelection normally, we
669                        // can construct a PathList consisting of only a
670                        // SubSelection here, for the sake of using the same
671                        // machinery to process all NamedSelection nodes.
672                        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                    // V0_3 does NOT support spread syntax - always add error
697                    remainder.extra.errors.push((
698                        "Spread syntax (...) is not supported in connect/v0.3 (use connect/v0.4)"
699                            .to_string(),
700                        input.location_offset(),
701                    ));
702                    // An explicit ... spread token was used, so we record
703                    // NamingPrefix::Spread(Some(_)). If the path produces
704                    // something other than an object or null, we will catch
705                    // that in apply_to_path and compute_output_shape (not a
706                    // parsing concern).
707                    NamingPrefix::Spread(spread.range())
708                } else if path.is_anonymous() && path.has_subselection() {
709                    // If there is no Alias or ... and the path is anonymous and
710                    // it has a trailing SubSelection, then it should be spread
711                    // into the larger SubSelection. This is an older syntax
712                    // (PathWithSubSelection) that provided some of the benefits
713                    // of ..., before ... was supported (in connect/v0.3). It's
714                    // important the path is anonymous, since regular field
715                    // selections like `user { id name }` meet all the criteria
716                    // above but should not be spread because they do produce an
717                    // output key.
718                    NamingPrefix::Spread(None)
719                } else {
720                    // Otherwise, the path has no prefix, so it either produces
721                    // a single Key according to path.get_single_key(), or this
722                    // is an anonymous NamedSelection, which are only allowed at
723                    // the top level. However, since we don't know about other
724                    // NamedSelections here, these rules have to be enforced at
725                    // a higher level.
726                    NamingPrefix::None
727                };
728                (remainder, Self { prefix, path })
729            })
730        }
731    }
732
733    // NamedSelection ::= (Alias | "...")? PathSelection | Alias SubSelection
734    // This version enables spread syntax (...) for abstract types support.
735    fn parse_v0_4(input: Span) -> ParseResult<Self> {
736        let (after_alias, alias) = opt(Alias::parse)(input.clone())?;
737
738        if let Some(alias) = alias {
739            if let Ok((remainder, sub)) = SubSelection::parse(after_alias.clone()) {
740                let sub_range = sub.range();
741                return Ok((
742                    remainder,
743                    Self {
744                        prefix: NamingPrefix::Alias(alias),
745                        // This is what used to be called a NamedGroupSelection
746                        // in the grammar, where an Alias SubSelection can be
747                        // used to assign a nested name (the Alias) to a
748                        // selection of fields from the current object.
749                        // Logically, this corresponds to an Alias followed by a
750                        // PathSelection with an empty/missing Path. While there
751                        // is no way to write such a PathSelection normally, we
752                        // can construct a PathList consisting of only a
753                        // SubSelection here, for the sake of using the same
754                        // machinery to process all NamedSelection nodes.
755                        path: PathSelection {
756                            path: WithRange::new(PathList::Selection(sub), sub_range),
757                        },
758                    },
759                ));
760            }
761
762            PathSelection::parse(after_alias.clone()).map(|(remainder, path)| {
763                (
764                    remainder,
765                    Self {
766                        prefix: NamingPrefix::Alias(alias),
767                        path,
768                    },
769                )
770            })
771        } else {
772            tuple((
773                spaces_or_comments,
774                opt(ranged_span("...")),
775                PathSelection::parse,
776            ))(input.clone())
777            .map(|(remainder, (_spaces, spread, path))| {
778                let prefix = if let Some(spread) = spread {
779                    // Spread syntax is fully supported in V0_4
780                    NamingPrefix::Spread(spread.range())
781                } else if path.is_anonymous() && path.has_subselection() {
782                    // If there is no Alias or ... and the path is anonymous and
783                    // it has a trailing SubSelection, then it should be spread
784                    // into the larger SubSelection. This is an older syntax
785                    // (PathWithSubSelection) that provided some of the benefits
786                    // of ..., before ... was supported (in connect/v0.3). It's
787                    // important the path is anonymous, since regular field
788                    // selections like `user { id name }` meet all the criteria
789                    // above but should not be spread because they do produce an
790                    // output key.
791                    NamingPrefix::Spread(None)
792                } else {
793                    // Otherwise, the path has no prefix, so it either produces
794                    // a single Key according to path.get_single_key(), or this
795                    // is an anonymous NamedSelection, which are only allowed at
796                    // the top level. However, since we don't know about other
797                    // NamedSelections here, these rules have to be enforced at
798                    // a higher level.
799                    NamingPrefix::None
800                };
801                (remainder, Self { prefix, path })
802            })
803        }
804    }
805
806    pub(crate) fn names(&self) -> Vec<&str> {
807        if let Some(single_key) = self.get_single_key() {
808            vec![single_key.as_str()]
809        } else if let Some(sub) = self.path.next_subselection() {
810            // Flatten and deduplicate the names of the NamedSelection
811            // items in the SubSelection.
812            let mut name_set = IndexSet::default();
813            for selection in sub.selections_iter() {
814                name_set.extend(selection.names());
815            }
816            name_set.into_iter().collect()
817        } else {
818            Vec::new()
819        }
820    }
821
822    /// Find the next subselection, if present
823    pub(crate) fn next_subselection(&self) -> Option<&SubSelection> {
824        self.path.next_subselection()
825    }
826}
827
828impl VarPaths for NamedSelection {
829    fn var_paths(&self) -> Vec<&PathSelection> {
830        self.path.var_paths()
831    }
832}
833
834// Path                 ::= VarPath | KeyPath | AtPath | ExprPath
835// PathSelection        ::= Path SubSelection?
836// VarPath              ::= "$" (NO_SPACE Identifier)? PathTail
837// KeyPath              ::= Key PathTail
838// AtPath               ::= "@" PathTail
839// ExprPath             ::= "$(" LitExpr ")" PathTail
840// PathTail             ::= "?"? (PathStep "?"?)*
841// PathStep             ::= "." Key | "->" Identifier MethodArgs?
842
843#[derive(Debug, PartialEq, Eq, Clone)]
844pub struct PathSelection {
845    pub(super) path: WithRange<PathList>,
846}
847
848// Like NamedSelection, PathSelection is an AST structure that takes its range
849// entirely from self.path (a WithRange<PathList>), so PathSelection itself does
850// not need to be wrapped as WithRange<PathSelection>, but merely needs to
851// implement the Ranged trait.
852impl Ranged for PathSelection {
853    fn range(&self) -> OffsetRange {
854        self.path.range()
855    }
856}
857
858impl PathSelection {
859    pub(crate) fn parse(input: Span) -> ParseResult<Self> {
860        PathList::parse(input).map(|(input, path)| (input, Self { path }))
861    }
862
863    pub(crate) fn variable_reference<N: FromStr + ToString>(&self) -> Option<VariableReference<N>> {
864        match self.path.as_ref() {
865            PathList::Var(var, tail) => match var.as_ref() {
866                KnownVariable::External(namespace) => {
867                    let selection = tail.compute_selection_trie();
868                    let full_range = merge_ranges(var.range(), tail.range());
869                    Some(VariableReference {
870                        namespace: VariableNamespace {
871                            namespace: N::from_str(namespace).ok()?,
872                            location: var.range(),
873                        },
874                        selection,
875                        location: full_range,
876                    })
877                }
878                _ => None,
879            },
880            _ => None,
881        }
882    }
883
884    #[allow(unused)]
885    pub(super) fn is_single_key(&self) -> bool {
886        self.path.is_single_key()
887    }
888
889    pub(super) fn get_single_key(&self) -> Option<&WithRange<Key>> {
890        self.path.get_single_key()
891    }
892
893    pub(super) fn is_anonymous(&self) -> bool {
894        self.path.is_anonymous()
895    }
896
897    #[allow(unused)]
898    pub(super) fn from_slice(keys: &[Key], selection: Option<SubSelection>) -> Self {
899        Self {
900            path: WithRange::new(PathList::from_slice(keys, selection), None),
901        }
902    }
903
904    #[allow(unused)]
905    pub(super) fn has_subselection(&self) -> bool {
906        self.path.has_subselection()
907    }
908
909    pub(super) fn next_subselection(&self) -> Option<&SubSelection> {
910        self.path.next_subselection()
911    }
912
913    #[allow(unused)]
914    pub(super) fn next_mut_subselection(&mut self) -> Option<&mut SubSelection> {
915        self.path.next_mut_subselection()
916    }
917}
918
919impl VarPaths for PathSelection {
920    fn var_paths(&self) -> Vec<&PathSelection> {
921        let mut paths = Vec::new();
922        match self.path.as_ref() {
923            PathList::Var(var_name, tail) => {
924                // At this point, we're collecting both external and local
925                // variable references (but not references to internal variables
926                // like $ and @). These mixed variables will be filtered in
927                // VarPaths::external_var_paths and ::local_var_paths.
928                if matches!(
929                    var_name.as_ref(),
930                    KnownVariable::External(_) | KnownVariable::Local(_)
931                ) {
932                    paths.push(self);
933                }
934                paths.extend(tail.var_paths());
935            }
936            other => {
937                paths.extend(other.var_paths());
938            }
939        };
940        paths
941    }
942}
943
944impl From<PathList> for PathSelection {
945    fn from(path: PathList) -> Self {
946        Self {
947            path: WithRange::new(path, None),
948        }
949    }
950}
951
952#[derive(Debug, PartialEq, Eq, Clone)]
953pub(crate) enum PathList {
954    // A VarPath must start with a variable (either $identifier, $, or @),
955    // followed by any number of PathStep items (the WithRange<PathList>).
956    // Because we represent the @ quasi-variable using PathList::Var, this
957    // variant handles both VarPath and AtPath from the grammar. The
958    // PathList::Var variant may only appear at the beginning of a
959    // PathSelection's PathList, not in the middle.
960    Var(WithRange<KnownVariable>, WithRange<PathList>),
961
962    // A PathSelection that starts with a PathList::Key is a KeyPath, but a
963    // PathList::Key also counts as PathStep item, so it may also appear in the
964    // middle/tail of a PathList.
965    Key(WithRange<Key>, WithRange<PathList>),
966
967    // An ExprPath, which begins with a LitExpr enclosed by $(...). Must appear
968    // only at the beginning of a PathSelection, like PathList::Var.
969    Expr(WithRange<LitExpr>, WithRange<PathList>),
970
971    // A PathList::Method is a PathStep item that may appear only in the
972    // middle/tail (not the beginning) of a PathSelection.
973    Method(WithRange<String>, Option<MethodArgs>, WithRange<PathList>),
974
975    // Represents the ? syntax used for some.path?->method(...) optional
976    // chaining. If the preceding some.path value is missing (None) or null,
977    // some.path? evaluates to None, terminating path evaluation without an
978    // error. All other (non-null) values are passed along without change.
979    //
980    // The WithRange<PathList> parameter represents the rest of the path
981    // following the `?` token.
982    Question(WithRange<PathList>),
983
984    // Optionally, a PathList may end with a SubSelection, which applies a set
985    // of named selections to the final value of the path. PathList::Selection
986    // by itself is not a valid PathList.
987    Selection(SubSelection),
988
989    // Every PathList must be terminated by either PathList::Selection or
990    // PathList::Empty. PathList::Empty by itself is not a valid PathList.
991    Empty,
992}
993
994impl PathList {
995    pub(crate) fn is_empty(&self) -> bool {
996        matches!(self, PathList::Empty)
997    }
998
999    pub(super) fn parse(input: Span) -> ParseResult<WithRange<Self>> {
1000        match Self::parse_with_depth(input.clone(), 0) {
1001            Ok((_, parsed)) if matches!(*parsed, Self::Empty) => Err(nom_error_message(
1002                input.clone(),
1003                // As a small technical note, you could consider
1004                // NamedGroupSelection (an Alias followed by a SubSelection) as
1005                // a kind of NamedPathSelection where the path is empty, but
1006                // it's still useful to distinguish groups in the grammar so we
1007                // can forbid empty paths in general. In fact, when parsing a
1008                // NamedGroupSelection, this error message is likely to be the
1009                // reason we abandon parsing NamedPathSelection and correctly
1010                // fall back to NamedGroupSelection.
1011                "Path selection cannot be empty",
1012            )),
1013            otherwise => otherwise,
1014        }
1015    }
1016
1017    #[cfg(test)]
1018    pub(super) fn into_with_range(self) -> WithRange<Self> {
1019        WithRange::new(self, None)
1020    }
1021
1022    pub(super) fn parse_with_depth(input: Span, depth: usize) -> ParseResult<WithRange<Self>> {
1023        let spec = get_connect_spec(&input);
1024
1025        // If the input is empty (i.e. this method will end up returning
1026        // PathList::Empty), we want the OffsetRange to be an empty range at the
1027        // end of the previously parsed PathList elements, not separated from
1028        // them by trailing spaces or comments, so we need to capture the empty
1029        // range before consuming leading spaces_or_comments.
1030        let offset_if_empty = input.location_offset();
1031        let range_if_empty: OffsetRange = Some(offset_if_empty..offset_if_empty);
1032
1033        // Consume leading spaces_or_comments for all cases below.
1034        let (input, _spaces) = spaces_or_comments(input)?;
1035
1036        // Variable references (including @ references), $(...) literals, and
1037        // key references without a leading . are accepted only at depth 0, or
1038        // at the beginning of the PathSelection.
1039        if depth == 0 {
1040            // The $(...) syntax allows embedding LitExpr values within
1041            // JSONSelection syntax (when not already parsing a LitExpr). This
1042            // case needs to come before the $ (and $var) case, because $( looks
1043            // like the $ variable followed by a parse error in the variable
1044            // case, unless we add some complicated lookahead logic there.
1045            match tuple((
1046                spaces_or_comments,
1047                ranged_span("$("),
1048                LitExpr::parse,
1049                spaces_or_comments,
1050                ranged_span(")"),
1051            ))(input.clone())
1052            {
1053                Ok((suffix, (_, dollar_open_paren, expr, close_paren, _))) => {
1054                    let (remainder, rest) = Self::parse_with_depth(suffix, depth + 1)?;
1055                    let expr_range = merge_ranges(dollar_open_paren.range(), close_paren.range());
1056                    let full_range = merge_ranges(expr_range, rest.range());
1057                    return Ok((
1058                        remainder,
1059                        WithRange::new(Self::Expr(expr, rest), full_range),
1060                    ));
1061                }
1062                Err(nom::Err::Failure(err)) => {
1063                    return Err(nom::Err::Failure(err));
1064                }
1065                Err(_) => {
1066                    // We can otherwise continue for non-fatal errors
1067                }
1068            }
1069
1070            if let Ok((suffix, (dollar, opt_var))) =
1071                tuple((ranged_span("$"), opt(parse_identifier_no_space)))(input.clone())
1072            {
1073                let dollar_range = dollar.range();
1074                let (remainder, rest) = Self::parse_with_depth(suffix, depth + 1)?;
1075                let full_range = merge_ranges(dollar_range.clone(), rest.range());
1076                return if let Some(var) = opt_var {
1077                    let full_name = format!("{}{}", dollar.as_ref(), var.as_str());
1078                    // This KnownVariable::External variant may get remapped to
1079                    // KnownVariable::Local if the variable was parsed as the
1080                    // first argument of an input->as($var) method call.
1081                    let known_var = if input.extra.is_local_var(&full_name) {
1082                        KnownVariable::Local(full_name)
1083                    } else {
1084                        KnownVariable::External(full_name)
1085                    };
1086                    let var_range = merge_ranges(dollar_range, var.range());
1087                    let ranged_known_var = WithRange::new(known_var, var_range);
1088                    Ok((
1089                        remainder,
1090                        WithRange::new(Self::Var(ranged_known_var, rest), full_range),
1091                    ))
1092                } else {
1093                    let ranged_dollar_var = WithRange::new(KnownVariable::Dollar, dollar_range);
1094                    Ok((
1095                        remainder,
1096                        WithRange::new(Self::Var(ranged_dollar_var, rest), full_range),
1097                    ))
1098                };
1099            }
1100
1101            if let Ok((suffix, at)) = ranged_span("@")(input.clone()) {
1102                let (remainder, rest) = Self::parse_with_depth(suffix, depth + 1)?;
1103                let full_range = merge_ranges(at.range(), rest.range());
1104                return Ok((
1105                    remainder,
1106                    WithRange::new(
1107                        Self::Var(WithRange::new(KnownVariable::AtSign, at.range()), rest),
1108                        full_range,
1109                    ),
1110                ));
1111            }
1112
1113            if let Ok((suffix, key)) = Key::parse(input.clone()) {
1114                let (remainder, rest) = Self::parse_with_depth(suffix, depth + 1)?;
1115
1116                return match spec {
1117                    ConnectSpec::V0_1 | ConnectSpec::V0_2 => match rest.as_ref() {
1118                        // We use nom_error_message rather than nom_fail_message
1119                        // here because the key might actually be a field selection,
1120                        // which means we want to unwind parsing the path and fall
1121                        // back to parsing other kinds of NamedSelection.
1122                        Self::Empty | Self::Selection(_) => Err(nom_error_message(
1123                            input.clone(),
1124                            // Another place where format! might be useful to
1125                            // suggest .{key}, which would require storing error
1126                            // messages as owned Strings.
1127                            "Single-key path must be prefixed with $. to avoid ambiguity with field name",
1128                        )),
1129                        _ => {
1130                            let full_range = merge_ranges(key.range(), rest.range());
1131                            Ok((remainder, WithRange::new(Self::Key(key, rest), full_range)))
1132                        }
1133                    },
1134
1135                    // With the unification of NamedSelection enum variants into
1136                    // a single struct in connect/v0.3, the ambiguity between
1137                    // single-key paths and field selections is no longer a
1138                    // problem, since they are now represented the same way.
1139                    ConnectSpec::V0_3 | ConnectSpec::V0_4 => {
1140                        let full_range = merge_ranges(key.range(), rest.range());
1141                        Ok((remainder, WithRange::new(Self::Key(key, rest), full_range)))
1142                    }
1143                };
1144            }
1145        }
1146
1147        if depth == 0 {
1148            // If the PathSelection does not start with a $var (or $ or @), a
1149            // key., or $(expr), it is not a valid PathSelection.
1150            if tuple((ranged_span("."), Key::parse))(input.clone()).is_ok() {
1151                // Since we previously allowed starting key paths with .key but
1152                // now forbid that syntax (because it can be ambiguous), suggest
1153                // the unambiguous $.key syntax instead.
1154                return Err(nom_fail_message(
1155                    input.clone(),
1156                    "Key paths cannot start with just .key (use $.key instead)",
1157                ));
1158            }
1159            // This error technically covers the case above, but doesn't suggest
1160            // a helpful solution.
1161            return Err(nom_error_message(
1162                input.clone(),
1163                "Path selection must start with key, $variable, $, @, or $(expression)",
1164            ));
1165        }
1166
1167        // At any depth, if the next token is ? but not the PathList::Question
1168        // kind, we terminate path parsing so the hypothetical ?? or ?! tokens
1169        // have a chance to be parsed as infix operators. This is not
1170        // version-gated to connect/v0.3, because we want to begin forbidding
1171        // these tokens as continuations of a Path as early as we can.
1172        if input.fragment().starts_with("??") || input.fragment().starts_with("?!") {
1173            return Ok((input, WithRange::new(Self::Empty, range_if_empty)));
1174        }
1175
1176        match spec {
1177            ConnectSpec::V0_1 | ConnectSpec::V0_2 => {
1178                // The ? token was not introduced until connect/v0.3.
1179            }
1180            ConnectSpec::V0_3 | ConnectSpec::V0_4 => {
1181                if let Ok((suffix, question)) = ranged_span("?")(input.clone()) {
1182                    let (remainder, rest) = Self::parse_with_depth(suffix.clone(), depth + 1)?;
1183
1184                    return match rest.as_ref() {
1185                        // The ? cannot be repeated sequentially, so if rest starts with
1186                        // another PathList::Question, we terminate the current path,
1187                        // probably (but not necessarily) leading to a parse error for
1188                        // the upcoming ?.
1189                        PathList::Question(_) => {
1190                            let empty_range = question.range().map(|range| range.end..range.end);
1191                            let empty = WithRange::new(Self::Empty, empty_range);
1192                            Ok((
1193                                suffix,
1194                                WithRange::new(Self::Question(empty), question.range()),
1195                            ))
1196                        }
1197                        _ => {
1198                            let full_range = merge_ranges(question.range(), rest.range());
1199                            Ok((remainder, WithRange::new(Self::Question(rest), full_range)))
1200                        }
1201                    };
1202                }
1203            }
1204        };
1205
1206        // In previous versions of this code, a .key could appear at depth 0 (at
1207        // the beginning of a path), which was useful to disambiguate a KeyPath
1208        // consisting of a single key from a field selection.
1209        //
1210        // Now that key paths can appear alongside/after named selections within
1211        // a SubSelection, the .key syntax is potentially unsafe because it may
1212        // be parsed as a continuation of a previous field selection, since we
1213        // ignore spaces/newlines/comments between keys in a path.
1214        //
1215        // In order to prevent this ambiguity, we now require that a single .key
1216        // be written as a subproperty of the $ variable, e.g. $.key, which is
1217        // equivalent to the old behavior, but parses unambiguously. In terms of
1218        // this code, that means we allow a .key only at depths > 0.
1219        if let Ok((remainder, (dot, key))) = tuple((ranged_span("."), Key::parse))(input.clone()) {
1220            let (remainder, rest) = Self::parse_with_depth(remainder, depth + 1)?;
1221            let dot_key_range = merge_ranges(dot.range(), key.range());
1222            let full_range = merge_ranges(dot_key_range, rest.range());
1223            return Ok((remainder, WithRange::new(Self::Key(key, rest), full_range)));
1224        }
1225
1226        // If we failed to parse "." Key above, but the input starts with a '.'
1227        // character, it's an error unless it's the beginning of a ... token.
1228        if input.fragment().starts_with('.') && !input.fragment().starts_with("...") {
1229            return Err(nom_fail_message(
1230                input.clone(),
1231                "Path selection . must be followed by key (identifier or quoted string literal)",
1232            ));
1233        }
1234
1235        // PathSelection can never start with a naked ->method (instead, use
1236        // $->method or @->method if you want to operate on the current value).
1237        if let Ok((suffix, arrow)) = ranged_span("->")(input.clone()) {
1238            // As soon as we see a -> token, we know what follows must be a
1239            // method name, so we can unconditionally return based on what
1240            // parse_identifier tells us. since MethodArgs::parse is optional,
1241            // the absence of args will never trigger the error case.
1242            return match tuple((parse_identifier, opt(MethodArgs::parse)))(suffix) {
1243                Ok((suffix, (method, args_opt))) => {
1244                    let mut local_var_name = None;
1245
1246                    // Convert the first argument of input->as($var) from
1247                    // KnownVariable::External (the default for parsed named
1248                    // variable references) to KnownVariable::Local, when we know
1249                    // we're parsing an ->as($var) method invocation.
1250                    let args = if let Some(args) = args_opt.as_ref()
1251                        && ArrowMethod::lookup(method.as_ref()) == Some(ArrowMethod::As)
1252                    {
1253                        let new_args = if let Some(old_first_arg) = args.args.first()
1254                            && let LitExpr::Path(path_selection) = old_first_arg.as_ref()
1255                            && let PathList::Var(var_name, var_tail) = path_selection.path.as_ref()
1256                            && let KnownVariable::External(var_str) | KnownVariable::Local(var_str) =
1257                                var_name.as_ref()
1258                        {
1259                            let as_var = WithRange::new(
1260                                // This is the key change: remap to KnownVariable::Local.
1261                                KnownVariable::Local(var_str.clone()),
1262                                var_name.range(),
1263                            );
1264
1265                            local_var_name = Some(var_str.clone());
1266
1267                            let new_first_arg = WithRange::new(
1268                                LitExpr::Path(PathSelection {
1269                                    path: WithRange::new(
1270                                        PathList::Var(as_var, var_tail.clone()),
1271                                        path_selection.range(),
1272                                    ),
1273                                }),
1274                                old_first_arg.range(),
1275                            );
1276
1277                            let mut new_args = vec![new_first_arg];
1278                            new_args.extend(args.args.iter().skip(1).cloned());
1279                            new_args
1280                        } else {
1281                            args.args.clone()
1282                        };
1283
1284                        Some(MethodArgs {
1285                            args: new_args,
1286                            range: args.range(),
1287                        })
1288                    } else {
1289                        args_opt
1290                    };
1291
1292                    let suffix_with_local_var = if let Some(var_name) = local_var_name {
1293                        suffix.map_extra(|extra| extra.with_local_var(var_name))
1294                    } else {
1295                        suffix
1296                    };
1297
1298                    let (remainder, rest) =
1299                        Self::parse_with_depth(suffix_with_local_var, depth + 1)?;
1300                    let full_range = merge_ranges(arrow.range(), rest.range());
1301
1302                    Ok((
1303                        remainder,
1304                        WithRange::new(Self::Method(method, args, rest), full_range),
1305                    ))
1306                }
1307                Err(_) => Err(nom_fail_message(
1308                    input.clone(),
1309                    "Method name must follow ->",
1310                )),
1311            };
1312        }
1313
1314        // Likewise, if the PathSelection has a SubSelection, it must appear at
1315        // the end of a non-empty path. PathList::parse_with_depth is not
1316        // responsible for enforcing a trailing SubSelection in the
1317        // PathWithSubSelection case, since that requirement is checked by
1318        // NamedSelection::parse_path.
1319        if let Ok((suffix, selection)) = SubSelection::parse(input.clone()) {
1320            let selection_range = selection.range();
1321            return Ok((
1322                suffix,
1323                WithRange::new(Self::Selection(selection), selection_range),
1324            ));
1325        }
1326
1327        // The Self::Empty enum case is used to indicate the end of a
1328        // PathSelection that has no SubSelection.
1329        Ok((input.clone(), WithRange::new(Self::Empty, range_if_empty)))
1330    }
1331
1332    pub(super) fn is_anonymous(&self) -> bool {
1333        self.get_single_key().is_none()
1334    }
1335
1336    pub(super) fn is_single_key(&self) -> bool {
1337        self.get_single_key().is_some()
1338    }
1339
1340    pub(super) fn get_single_key(&self) -> Option<&WithRange<Key>> {
1341        fn rest_is_empty_or_selection(rest: &WithRange<PathList>) -> bool {
1342            match rest.as_ref() {
1343                PathList::Selection(_) | PathList::Empty => true,
1344                PathList::Question(tail) => rest_is_empty_or_selection(tail),
1345                // We could have a `_ => false` catch-all case here, but relying
1346                // on the exhaustiveness of this match ensures additions of new
1347                // PathList variants in the future (e.g. PathList::Question)
1348                // will be nudged to consider whether they should be compatible
1349                // with single-key field selections.
1350                PathList::Var(_, _)
1351                | PathList::Key(_, _)
1352                | PathList::Expr(_, _)
1353                | PathList::Method(_, _, _) => false,
1354            }
1355        }
1356
1357        match self {
1358            Self::Key(key, key_rest) => {
1359                if rest_is_empty_or_selection(key_rest) {
1360                    Some(key)
1361                } else {
1362                    None
1363                }
1364            }
1365            _ => None,
1366        }
1367    }
1368
1369    pub(super) fn is_question(&self) -> bool {
1370        matches!(self, Self::Question(_))
1371    }
1372
1373    #[allow(unused)]
1374    pub(super) fn from_slice(properties: &[Key], selection: Option<SubSelection>) -> Self {
1375        match properties {
1376            [] => selection.map_or(Self::Empty, Self::Selection),
1377            [head, tail @ ..] => Self::Key(
1378                WithRange::new(head.clone(), None),
1379                WithRange::new(Self::from_slice(tail, selection), None),
1380            ),
1381        }
1382    }
1383
1384    pub(super) fn has_subselection(&self) -> bool {
1385        self.next_subselection().is_some()
1386    }
1387
1388    /// Find the next subselection, traversing nested chains if needed
1389    pub(super) fn next_subselection(&self) -> Option<&SubSelection> {
1390        match self {
1391            Self::Var(_, tail) => tail.next_subselection(),
1392            Self::Key(_, tail) => tail.next_subselection(),
1393            Self::Expr(_, tail) => tail.next_subselection(),
1394            Self::Method(_, _, tail) => tail.next_subselection(),
1395            Self::Question(tail) => tail.next_subselection(),
1396            Self::Selection(sub) => Some(sub),
1397            Self::Empty => None,
1398        }
1399    }
1400
1401    #[allow(unused)]
1402    /// Find the next subselection, traversing nested chains if needed. Returns a mutable reference
1403    pub(super) fn next_mut_subselection(&mut self) -> Option<&mut SubSelection> {
1404        match self {
1405            Self::Var(_, tail) => tail.next_mut_subselection(),
1406            Self::Key(_, tail) => tail.next_mut_subselection(),
1407            Self::Expr(_, tail) => tail.next_mut_subselection(),
1408            Self::Method(_, _, tail) => tail.next_mut_subselection(),
1409            Self::Question(tail) => tail.next_mut_subselection(),
1410            Self::Selection(sub) => Some(sub),
1411            Self::Empty => None,
1412        }
1413    }
1414}
1415
1416impl VarPaths for PathList {
1417    fn var_paths(&self) -> Vec<&PathSelection> {
1418        let mut paths = Vec::new();
1419        match self {
1420            // PathSelection::var_paths is responsible for adding all
1421            // variable &PathSelection items to the set, since this
1422            // PathList::Var case cannot be sure it's looking at the beginning
1423            // of the path. However, we call rest.var_paths()
1424            // recursively because the tail of the list could contain other full
1425            // PathSelection variable references.
1426            PathList::Var(_, rest) | PathList::Key(_, rest) => {
1427                paths.extend(rest.var_paths());
1428            }
1429            PathList::Expr(expr, rest) => {
1430                paths.extend(expr.var_paths());
1431                paths.extend(rest.var_paths());
1432            }
1433            PathList::Method(_, opt_args, rest) => {
1434                if let Some(args) = opt_args {
1435                    for lit_arg in &args.args {
1436                        paths.extend(lit_arg.var_paths());
1437                    }
1438                }
1439                paths.extend(rest.var_paths());
1440            }
1441            PathList::Question(rest) => {
1442                paths.extend(rest.var_paths());
1443            }
1444            PathList::Selection(sub) => paths.extend(sub.var_paths()),
1445            PathList::Empty => {}
1446        }
1447        paths
1448    }
1449}
1450
1451// SubSelection ::= "{" NakedSubSelection "}"
1452
1453#[derive(Debug, PartialEq, Eq, Clone, Default)]
1454pub struct SubSelection {
1455    pub(super) selections: Vec<NamedSelection>,
1456    pub(super) range: OffsetRange,
1457}
1458
1459impl Ranged for SubSelection {
1460    // Since SubSelection is a struct, we can store its range directly as a
1461    // field of the struct, allowing SubSelection to implement the Ranged trait
1462    // without a WithRange<SubSelection> wrapper.
1463    fn range(&self) -> OffsetRange {
1464        self.range.clone()
1465    }
1466}
1467
1468impl SubSelection {
1469    pub(crate) fn parse(input: Span) -> ParseResult<Self> {
1470        match tuple((
1471            spaces_or_comments,
1472            ranged_span("{"),
1473            Self::parse_naked,
1474            spaces_or_comments,
1475            ranged_span("}"),
1476        ))(input)
1477        {
1478            Ok((remainder, (_, open_brace, sub, _, close_brace))) => {
1479                let range = merge_ranges(open_brace.range(), close_brace.range());
1480                Ok((
1481                    remainder,
1482                    Self {
1483                        selections: sub.selections,
1484                        range,
1485                    },
1486                ))
1487            }
1488            Err(e) => Err(e),
1489        }
1490    }
1491
1492    fn parse_naked(input: Span) -> ParseResult<Self> {
1493        match many0(NamedSelection::parse)(input.clone()) {
1494            Ok((remainder, selections)) => {
1495                // Enforce that if selections has any anonymous NamedSelection
1496                // elements, there is only one and it's the only NamedSelection in
1497                // the SubSelection.
1498                for sel in selections.iter() {
1499                    if sel.is_anonymous() && selections.len() > 1 {
1500                        return Err(nom_error_message(
1501                            input.clone(),
1502                            "SubSelection cannot contain multiple elements if it contains an anonymous NamedSelection",
1503                        ));
1504                    }
1505                }
1506
1507                let range = merge_ranges(
1508                    selections.first().and_then(|first| first.range()),
1509                    selections.last().and_then(|last| last.range()),
1510                );
1511
1512                Ok((remainder, Self { selections, range }))
1513            }
1514            Err(e) => Err(e),
1515        }
1516    }
1517
1518    // Returns an Iterator over each &NamedSelection that contributes a single
1519    // name to the output object. This is more complicated than returning
1520    // self.selections.iter() because some NamedSelection::Path elements can
1521    // contribute multiple names if they do no have an Alias.
1522    pub fn selections_iter(&self) -> impl Iterator<Item = &NamedSelection> {
1523        // TODO Implement a NamedSelectionIterator to traverse nested selections
1524        // lazily, rather than using an intermediary vector.
1525        let mut selections = Vec::new();
1526        for selection in &self.selections {
1527            if selection.has_single_output_key() {
1528                // If the PathSelection has an Alias, then it has a singular
1529                // name and should be visited directly.
1530                selections.push(selection);
1531            } else if let Some(sub) = selection.path.next_subselection() {
1532                // If the PathSelection does not have an Alias but does have a
1533                // SubSelection, then it represents the PathWithSubSelection
1534                // non-terminal from the grammar (see README.md + PR #6076),
1535                // which produces multiple names derived from the SubSelection,
1536                // which need to be recursively collected.
1537                selections.extend(sub.selections_iter());
1538            } else {
1539                // This no-Alias, no-SubSelection case should be forbidden by
1540                // NamedSelection::parse_path.
1541                debug_assert!(false, "PathSelection without Alias or SubSelection");
1542            }
1543        }
1544        selections.into_iter()
1545    }
1546
1547    pub fn append_selection(&mut self, selection: NamedSelection) {
1548        self.selections.push(selection);
1549    }
1550
1551    pub fn last_selection_mut(&mut self) -> Option<&mut NamedSelection> {
1552        self.selections.last_mut()
1553    }
1554}
1555
1556impl VarPaths for SubSelection {
1557    fn var_paths(&self) -> Vec<&PathSelection> {
1558        let mut paths = Vec::new();
1559        for selection in &self.selections {
1560            paths.extend(selection.var_paths());
1561        }
1562        paths
1563    }
1564}
1565
1566// Alias ::= Key ":"
1567
1568#[derive(Debug, PartialEq, Eq, Clone)]
1569pub(crate) struct Alias {
1570    pub(super) name: WithRange<Key>,
1571    pub(super) range: OffsetRange,
1572}
1573
1574impl Ranged for Alias {
1575    fn range(&self) -> OffsetRange {
1576        self.range.clone()
1577    }
1578}
1579
1580impl Alias {
1581    pub(crate) fn new(name: &str) -> Self {
1582        if is_identifier(name) {
1583            Self::field(name)
1584        } else {
1585            Self::quoted(name)
1586        }
1587    }
1588
1589    pub(crate) fn field(name: &str) -> Self {
1590        Self {
1591            name: WithRange::new(Key::field(name), None),
1592            range: None,
1593        }
1594    }
1595
1596    pub(crate) fn quoted(name: &str) -> Self {
1597        Self {
1598            name: WithRange::new(Key::quoted(name), None),
1599            range: None,
1600        }
1601    }
1602
1603    pub(crate) fn parse(input: Span) -> ParseResult<Self> {
1604        tuple((Key::parse, spaces_or_comments, ranged_span(":")))(input).map(
1605            |(input, (name, _, colon))| {
1606                let range = merge_ranges(name.range(), colon.range());
1607                (input, Self { name, range })
1608            },
1609        )
1610    }
1611}
1612
1613// Key ::= Identifier | LitString
1614
1615#[derive(Debug, PartialEq, Eq, Clone, Hash)]
1616pub enum Key {
1617    Field(String),
1618    Quoted(String),
1619}
1620
1621impl Key {
1622    pub(crate) fn parse(input: Span) -> ParseResult<WithRange<Self>> {
1623        alt((
1624            map(parse_identifier, |id| id.take_as(Key::Field)),
1625            map(parse_string_literal, |s| s.take_as(Key::Quoted)),
1626        ))(input)
1627    }
1628
1629    pub fn field(name: &str) -> Self {
1630        Self::Field(name.to_string())
1631    }
1632
1633    pub fn quoted(name: &str) -> Self {
1634        Self::Quoted(name.to_string())
1635    }
1636
1637    pub fn into_with_range(self) -> WithRange<Self> {
1638        WithRange::new(self, None)
1639    }
1640
1641    pub fn is_quoted(&self) -> bool {
1642        matches!(self, Self::Quoted(_))
1643    }
1644
1645    pub fn to_json(&self) -> JSON {
1646        match self {
1647            Key::Field(name) => JSON::String(name.clone().into()),
1648            Key::Quoted(name) => JSON::String(name.clone().into()),
1649        }
1650    }
1651
1652    // This method returns the field/property name as a String, and is
1653    // appropriate for accessing JSON properties, in contrast to the dotted
1654    // method below.
1655    pub fn as_string(&self) -> String {
1656        match self {
1657            Key::Field(name) => name.clone(),
1658            Key::Quoted(name) => name.clone(),
1659        }
1660    }
1661    // Like as_string, but without cloning a new String, for times when the Key
1662    // itself lives longer than the &str.
1663    pub fn as_str(&self) -> &str {
1664        match self {
1665            Key::Field(name) => name.as_str(),
1666            Key::Quoted(name) => name.as_str(),
1667        }
1668    }
1669
1670    // This method is used to implement the Display trait for Key, and includes
1671    // a leading '.' character for string keys, as well as proper quoting for
1672    // Key::Quoted values. However, these additions make key.dotted() unsafe to
1673    // use for accessing JSON properties.
1674    pub fn dotted(&self) -> String {
1675        match self {
1676            Key::Field(field) => format!(".{field}"),
1677            Key::Quoted(field) => {
1678                // JSON encoding is a reliable way to ensure a string that may
1679                // contain special characters (such as '"' characters) is
1680                // properly escaped and double-quoted.
1681                let quoted = serde_json_bytes::Value::String(field.clone().into()).to_string();
1682                format!(".{quoted}")
1683            }
1684        }
1685    }
1686}
1687
1688impl Display for Key {
1689    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1690        let dotted = self.dotted();
1691        write!(f, "{dotted}")
1692    }
1693}
1694
1695// Identifier ::= [a-zA-Z_] NO_SPACE [0-9a-zA-Z_]*
1696
1697pub(super) fn is_identifier(input: &str) -> bool {
1698    // TODO Don't use the whole parser for this?
1699    all_consuming(parse_identifier_no_space)(new_span_with_spec(
1700        input,
1701        JSONSelection::default_connect_spec(),
1702    ))
1703    .is_ok()
1704}
1705
1706fn parse_identifier(input: Span) -> ParseResult<WithRange<String>> {
1707    preceded(spaces_or_comments, parse_identifier_no_space)(input)
1708}
1709
1710fn parse_identifier_no_space(input: Span) -> ParseResult<WithRange<String>> {
1711    recognize(pair(
1712        one_of("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_"),
1713        many0(one_of(
1714            "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_0123456789",
1715        )),
1716    ))(input)
1717    .map(|(remainder, name)| {
1718        let range = Some(name.location_offset()..remainder.location_offset());
1719        (remainder, WithRange::new(name.to_string(), range))
1720    })
1721}
1722
1723// LitString ::=
1724//   | "'" ("\\'" | [^'])* "'"
1725//   | '"' ('\\"' | [^"])* '"'
1726
1727pub(crate) fn parse_string_literal(input: Span) -> ParseResult<WithRange<String>> {
1728    let input = spaces_or_comments(input)?.0;
1729    let start = input.location_offset();
1730    let mut input_char_indices = input.char_indices();
1731
1732    match input_char_indices.next() {
1733        Some((0, quote @ '\'')) | Some((0, quote @ '"')) => {
1734            let mut escape_next = false;
1735            let mut chars: Vec<char> = Vec::new();
1736            let mut remainder_opt: Option<Span> = None;
1737
1738            for (i, c) in input_char_indices {
1739                if escape_next {
1740                    match c {
1741                        'n' => chars.push('\n'),
1742                        _ => chars.push(c),
1743                    }
1744                    escape_next = false;
1745                    continue;
1746                }
1747                if c == '\\' {
1748                    escape_next = true;
1749                    continue;
1750                }
1751                if c == quote {
1752                    remainder_opt = Some(input.slice(i + 1..));
1753                    break;
1754                }
1755                chars.push(c);
1756            }
1757
1758            remainder_opt
1759                .ok_or_else(|| nom_fail_message(input, "Unterminated string literal"))
1760                .map(|remainder| {
1761                    let range = Some(start..remainder.location_offset());
1762                    (
1763                        remainder,
1764                        WithRange::new(chars.iter().collect::<String>(), range),
1765                    )
1766                })
1767        }
1768
1769        _ => Err(nom_error_message(input, "Not a string literal")),
1770    }
1771}
1772
1773#[derive(Debug, PartialEq, Eq, Clone, Default)]
1774pub(crate) struct MethodArgs {
1775    pub(super) args: Vec<WithRange<LitExpr>>,
1776    pub(super) range: OffsetRange,
1777}
1778
1779impl Ranged for MethodArgs {
1780    fn range(&self) -> OffsetRange {
1781        self.range.clone()
1782    }
1783}
1784
1785// Comma-separated positional arguments for a method, surrounded by parentheses.
1786// When an arrow method is used without arguments, the Option<MethodArgs> for
1787// the PathSelection::Method will be None, so we can safely define MethodArgs
1788// using a Vec<LitExpr> in all cases (possibly empty but never missing).
1789impl MethodArgs {
1790    fn parse(input: Span) -> ParseResult<Self> {
1791        let input = spaces_or_comments(input)?.0;
1792        let (mut input, open_paren) = ranged_span("(")(input)?;
1793        input = spaces_or_comments(input)?.0;
1794
1795        let mut args = Vec::new();
1796        if let Ok((remainder, first)) = LitExpr::parse(input.clone()) {
1797            args.push(first);
1798            input = remainder;
1799
1800            while let Ok((remainder, _)) = tuple((spaces_or_comments, char(',')))(input.clone()) {
1801                input = spaces_or_comments(remainder)?.0;
1802                if let Ok((remainder, arg)) = LitExpr::parse(input.clone()) {
1803                    args.push(arg);
1804                    input = remainder;
1805                } else {
1806                    break;
1807                }
1808            }
1809        }
1810
1811        input = spaces_or_comments(input.clone())?.0;
1812        let (input, close_paren) = ranged_span(")")(input.clone())?;
1813
1814        let range = merge_ranges(open_paren.range(), close_paren.range());
1815        Ok((input, Self { args, range }))
1816    }
1817}
1818
1819#[cfg(test)]
1820mod tests {
1821    use apollo_compiler::collections::IndexMap;
1822    use rstest::rstest;
1823
1824    use super::super::location::strip_ranges::StripRanges;
1825    use super::*;
1826    use crate::assert_debug_snapshot;
1827    use crate::connectors::json_selection::PrettyPrintable;
1828    use crate::connectors::json_selection::SelectionTrie;
1829    use crate::connectors::json_selection::fixtures::Namespace;
1830    use crate::connectors::json_selection::helpers::span_is_all_spaces_or_comments;
1831    use crate::connectors::json_selection::location::new_span;
1832    use crate::selection;
1833
1834    #[test]
1835    fn test_default_connect_spec() {
1836        // We don't necessarily want to update what
1837        // JSONSelection::default_connect_spec() returns just because
1838        // ConnectSpec::latest() changes, but we want to know when it happens,
1839        // so we can consider updating.
1840        assert_eq!(JSONSelection::default_connect_spec(), ConnectSpec::latest());
1841    }
1842
1843    #[test]
1844    fn test_identifier() {
1845        fn check(input: &str, expected_name: &str) {
1846            let (remainder, name) = parse_identifier(new_span(input)).unwrap();
1847            assert!(
1848                span_is_all_spaces_or_comments(remainder.clone()),
1849                "remainder is `{:?}`",
1850                remainder.clone(),
1851            );
1852            assert_eq!(name.as_ref(), expected_name);
1853        }
1854
1855        check("hello", "hello");
1856        check("hello_world", "hello_world");
1857        check("  hello_world ", "hello_world");
1858        check("hello_world_123", "hello_world_123");
1859        check(" hello ", "hello");
1860
1861        fn check_no_space(input: &str, expected_name: &str) {
1862            let name = parse_identifier_no_space(new_span(input)).unwrap().1;
1863            assert_eq!(name.as_ref(), expected_name);
1864        }
1865
1866        check_no_space("oyez", "oyez");
1867        check_no_space("oyez   ", "oyez");
1868
1869        {
1870            let identifier_with_leading_space = new_span("  oyez   ");
1871            assert_eq!(
1872                parse_identifier_no_space(identifier_with_leading_space.clone()),
1873                Err(nom::Err::Error(nom::error::Error::from_error_kind(
1874                    // The parse_identifier_no_space function does not provide a
1875                    // custom error message, since it's only used internally.
1876                    // Testing it directly here is somewhat contrived.
1877                    identifier_with_leading_space.clone(),
1878                    nom::error::ErrorKind::OneOf,
1879                ))),
1880            );
1881        }
1882    }
1883
1884    #[test]
1885    fn test_string_literal() {
1886        fn check(input: &str, expected: &str) {
1887            let (remainder, lit) = parse_string_literal(new_span(input)).unwrap();
1888            assert!(
1889                span_is_all_spaces_or_comments(remainder.clone()),
1890                "remainder is `{:?}`",
1891                remainder.clone(),
1892            );
1893            assert_eq!(lit.as_ref(), expected);
1894        }
1895        check("'hello world'", "hello world");
1896        check("\"hello world\"", "hello world");
1897        check("'hello \"world\"'", "hello \"world\"");
1898        check("\"hello \\\"world\\\"\"", "hello \"world\"");
1899        check("'hello \\'world\\''", "hello 'world'");
1900    }
1901
1902    #[test]
1903    fn test_key() {
1904        fn check(input: &str, expected: &Key) {
1905            let (remainder, key) = Key::parse(new_span(input)).unwrap();
1906            assert!(
1907                span_is_all_spaces_or_comments(remainder.clone()),
1908                "remainder is `{:?}`",
1909                remainder.clone(),
1910            );
1911            assert_eq!(key.as_ref(), expected);
1912        }
1913
1914        check("hello", &Key::field("hello"));
1915        check("'hello'", &Key::quoted("hello"));
1916        check("  hello ", &Key::field("hello"));
1917        check("\"hello\"", &Key::quoted("hello"));
1918        check("  \"hello\" ", &Key::quoted("hello"));
1919    }
1920
1921    #[test]
1922    fn test_alias() {
1923        fn check(input: &str, alias: &str) {
1924            let (remainder, parsed) = Alias::parse(new_span(input)).unwrap();
1925            assert!(
1926                span_is_all_spaces_or_comments(remainder.clone()),
1927                "remainder is `{:?}`",
1928                remainder.clone(),
1929            );
1930            assert_eq!(parsed.name.as_str(), alias);
1931        }
1932
1933        check("hello:", "hello");
1934        check("hello :", "hello");
1935        check("hello : ", "hello");
1936        check("  hello :", "hello");
1937        check("hello: ", "hello");
1938    }
1939
1940    #[test]
1941    fn test_named_selection() {
1942        #[track_caller]
1943        fn assert_result_and_names(input: &str, expected: NamedSelection, names: &[&str]) {
1944            let (remainder, selection) = NamedSelection::parse(new_span(input)).unwrap();
1945            assert!(
1946                span_is_all_spaces_or_comments(remainder.clone()),
1947                "remainder is `{:?}`",
1948                remainder.clone(),
1949            );
1950            let selection = selection.strip_ranges();
1951            assert_eq!(selection, expected);
1952            assert_eq!(selection.names(), names);
1953            assert_eq!(
1954                selection!(input).strip_ranges(),
1955                JSONSelection::named(SubSelection {
1956                    selections: vec![expected],
1957                    ..Default::default()
1958                },),
1959            );
1960        }
1961
1962        assert_result_and_names(
1963            "hello",
1964            NamedSelection::field(None, Key::field("hello").into_with_range(), None),
1965            &["hello"],
1966        );
1967
1968        assert_result_and_names(
1969            "hello { world }",
1970            NamedSelection::field(
1971                None,
1972                Key::field("hello").into_with_range(),
1973                Some(SubSelection {
1974                    selections: vec![NamedSelection::field(
1975                        None,
1976                        Key::field("world").into_with_range(),
1977                        None,
1978                    )],
1979                    ..Default::default()
1980                }),
1981            ),
1982            &["hello"],
1983        );
1984
1985        assert_result_and_names(
1986            "hi: hello",
1987            NamedSelection::field(
1988                Some(Alias::new("hi")),
1989                Key::field("hello").into_with_range(),
1990                None,
1991            ),
1992            &["hi"],
1993        );
1994
1995        assert_result_and_names(
1996            "hi: 'hello world'",
1997            NamedSelection::field(
1998                Some(Alias::new("hi")),
1999                Key::quoted("hello world").into_with_range(),
2000                None,
2001            ),
2002            &["hi"],
2003        );
2004
2005        assert_result_and_names(
2006            "hi: hello { world }",
2007            NamedSelection::field(
2008                Some(Alias::new("hi")),
2009                Key::field("hello").into_with_range(),
2010                Some(SubSelection {
2011                    selections: vec![NamedSelection::field(
2012                        None,
2013                        Key::field("world").into_with_range(),
2014                        None,
2015                    )],
2016                    ..Default::default()
2017                }),
2018            ),
2019            &["hi"],
2020        );
2021
2022        assert_result_and_names(
2023            "hey: hello { world again }",
2024            NamedSelection::field(
2025                Some(Alias::new("hey")),
2026                Key::field("hello").into_with_range(),
2027                Some(SubSelection {
2028                    selections: vec![
2029                        NamedSelection::field(None, Key::field("world").into_with_range(), None),
2030                        NamedSelection::field(None, Key::field("again").into_with_range(), None),
2031                    ],
2032                    ..Default::default()
2033                }),
2034            ),
2035            &["hey"],
2036        );
2037
2038        assert_result_and_names(
2039            "hey: 'hello world' { again }",
2040            NamedSelection::field(
2041                Some(Alias::new("hey")),
2042                Key::quoted("hello world").into_with_range(),
2043                Some(SubSelection {
2044                    selections: vec![NamedSelection::field(
2045                        None,
2046                        Key::field("again").into_with_range(),
2047                        None,
2048                    )],
2049                    ..Default::default()
2050                }),
2051            ),
2052            &["hey"],
2053        );
2054
2055        assert_result_and_names(
2056            "leggo: 'my ego'",
2057            NamedSelection::field(
2058                Some(Alias::new("leggo")),
2059                Key::quoted("my ego").into_with_range(),
2060                None,
2061            ),
2062            &["leggo"],
2063        );
2064
2065        assert_result_and_names(
2066            "'let go': 'my ego'",
2067            NamedSelection::field(
2068                Some(Alias::quoted("let go")),
2069                Key::quoted("my ego").into_with_range(),
2070                None,
2071            ),
2072            &["let go"],
2073        );
2074    }
2075
2076    #[test]
2077    fn test_selection() {
2078        assert_eq!(
2079            selection!("").strip_ranges(),
2080            JSONSelection::named(SubSelection {
2081                selections: vec![],
2082                ..Default::default()
2083            }),
2084        );
2085
2086        assert_eq!(
2087            selection!("   ").strip_ranges(),
2088            JSONSelection::named(SubSelection {
2089                selections: vec![],
2090                ..Default::default()
2091            }),
2092        );
2093
2094        assert_eq!(
2095            selection!("hello").strip_ranges(),
2096            JSONSelection::named(SubSelection {
2097                selections: vec![NamedSelection::field(
2098                    None,
2099                    Key::field("hello").into_with_range(),
2100                    None
2101                )],
2102                ..Default::default()
2103            }),
2104        );
2105
2106        assert_eq!(
2107            selection!("$.hello").strip_ranges(),
2108            JSONSelection::path(PathSelection {
2109                path: PathList::Var(
2110                    KnownVariable::Dollar.into_with_range(),
2111                    PathList::Key(
2112                        Key::field("hello").into_with_range(),
2113                        PathList::Empty.into_with_range()
2114                    )
2115                    .into_with_range(),
2116                )
2117                .into_with_range(),
2118            }),
2119        );
2120
2121        {
2122            let expected = JSONSelection::named(SubSelection {
2123                selections: vec![NamedSelection {
2124                    prefix: NamingPrefix::Alias(Alias::new("hi")),
2125                    path: PathSelection::from_slice(
2126                        &[
2127                            Key::Field("hello".to_string()),
2128                            Key::Field("world".to_string()),
2129                        ],
2130                        None,
2131                    ),
2132                }],
2133                ..Default::default()
2134            });
2135
2136            assert_eq!(selection!("hi: hello.world").strip_ranges(), expected);
2137            assert_eq!(selection!("hi: hello .world").strip_ranges(), expected);
2138            assert_eq!(selection!("hi:  hello. world").strip_ranges(), expected);
2139            assert_eq!(selection!("hi: hello . world").strip_ranges(), expected);
2140            assert_eq!(selection!("hi: hello.world").strip_ranges(), expected);
2141            assert_eq!(selection!("hi: hello. world").strip_ranges(), expected);
2142            assert_eq!(selection!("hi: hello .world").strip_ranges(), expected);
2143            assert_eq!(selection!("hi: hello . world ").strip_ranges(), expected);
2144        }
2145
2146        {
2147            let expected = JSONSelection::named(SubSelection {
2148                selections: vec![
2149                    NamedSelection::field(None, Key::field("before").into_with_range(), None),
2150                    NamedSelection {
2151                        prefix: NamingPrefix::Alias(Alias::new("hi")),
2152                        path: PathSelection::from_slice(
2153                            &[
2154                                Key::Field("hello".to_string()),
2155                                Key::Field("world".to_string()),
2156                            ],
2157                            None,
2158                        ),
2159                    },
2160                    NamedSelection::field(None, Key::field("after").into_with_range(), None),
2161                ],
2162                ..Default::default()
2163            });
2164
2165            assert_eq!(
2166                selection!("before hi: hello.world after").strip_ranges(),
2167                expected
2168            );
2169            assert_eq!(
2170                selection!("before hi: hello .world after").strip_ranges(),
2171                expected
2172            );
2173            assert_eq!(
2174                selection!("before hi: hello. world after").strip_ranges(),
2175                expected
2176            );
2177            assert_eq!(
2178                selection!("before hi: hello . world after").strip_ranges(),
2179                expected
2180            );
2181            assert_eq!(
2182                selection!("before hi:  hello.world after").strip_ranges(),
2183                expected
2184            );
2185            assert_eq!(
2186                selection!("before hi: hello .world after").strip_ranges(),
2187                expected
2188            );
2189            assert_eq!(
2190                selection!("before hi: hello. world after").strip_ranges(),
2191                expected
2192            );
2193            assert_eq!(
2194                selection!("before hi: hello . world after").strip_ranges(),
2195                expected
2196            );
2197        }
2198
2199        {
2200            let expected = JSONSelection::named(SubSelection {
2201                selections: vec![
2202                    NamedSelection::field(None, Key::field("before").into_with_range(), None),
2203                    NamedSelection {
2204                        prefix: NamingPrefix::Alias(Alias::new("hi")),
2205                        path: PathSelection::from_slice(
2206                            &[
2207                                Key::Field("hello".to_string()),
2208                                Key::Field("world".to_string()),
2209                            ],
2210                            Some(SubSelection {
2211                                selections: vec![
2212                                    NamedSelection::field(
2213                                        None,
2214                                        Key::field("nested").into_with_range(),
2215                                        None,
2216                                    ),
2217                                    NamedSelection::field(
2218                                        None,
2219                                        Key::field("names").into_with_range(),
2220                                        None,
2221                                    ),
2222                                ],
2223                                ..Default::default()
2224                            }),
2225                        ),
2226                    },
2227                    NamedSelection::field(None, Key::field("after").into_with_range(), None),
2228                ],
2229                ..Default::default()
2230            });
2231
2232            assert_eq!(
2233                selection!("before hi: hello.world { nested names } after").strip_ranges(),
2234                expected
2235            );
2236            assert_eq!(
2237                selection!("before hi:hello.world{nested names}after").strip_ranges(),
2238                expected
2239            );
2240            assert_eq!(
2241                selection!(" before hi : hello . world { nested names } after ").strip_ranges(),
2242                expected
2243            );
2244        }
2245
2246        assert_debug_snapshot!(selection!(
2247            "
2248            # Comments are supported because we parse them as whitespace
2249            topLevelAlias: topLevelField {
2250                identifier: 'property name with spaces'
2251                'unaliased non-identifier property'
2252                'non-identifier alias': identifier
2253
2254                # This extracts the value located at the given path and applies a
2255                # selection set to it before renaming the result to pathSelection
2256                pathSelection: some.nested.path {
2257                    still: yet
2258                    more
2259                    properties
2260                }
2261
2262                # An aliased SubSelection of fields nests the fields together
2263                # under the given alias
2264                siblingGroup: { brother sister }
2265            }"
2266        ));
2267    }
2268
2269    #[track_caller]
2270    fn check_path_selection(spec: ConnectSpec, input: &str, expected: PathSelection) {
2271        let (remainder, path_selection) =
2272            PathSelection::parse(new_span_with_spec(input, spec)).unwrap();
2273        assert!(
2274            span_is_all_spaces_or_comments(remainder.clone()),
2275            "remainder is `{:?}`",
2276            remainder.clone(),
2277        );
2278        let path_without_ranges = path_selection.strip_ranges();
2279        assert_eq!(&path_without_ranges, &expected);
2280        assert_eq!(
2281            selection!(input, spec).strip_ranges(),
2282            JSONSelection {
2283                inner: TopLevelSelection::Path(path_without_ranges),
2284                spec,
2285            },
2286        );
2287    }
2288
2289    #[rstest]
2290    #[case::v0_2(ConnectSpec::V0_2)]
2291    #[case::v0_3(ConnectSpec::V0_3)]
2292    #[case::v0_4(ConnectSpec::V0_4)]
2293    fn test_path_selection(#[case] spec: ConnectSpec) {
2294        check_path_selection(
2295            spec,
2296            "$.hello",
2297            PathSelection {
2298                path: PathList::Var(
2299                    KnownVariable::Dollar.into_with_range(),
2300                    PathList::Key(
2301                        Key::field("hello").into_with_range(),
2302                        PathList::Empty.into_with_range(),
2303                    )
2304                    .into_with_range(),
2305                )
2306                .into_with_range(),
2307            },
2308        );
2309
2310        {
2311            let expected = PathSelection {
2312                path: PathList::Var(
2313                    KnownVariable::Dollar.into_with_range(),
2314                    PathList::Key(
2315                        Key::field("hello").into_with_range(),
2316                        PathList::Key(
2317                            Key::field("world").into_with_range(),
2318                            PathList::Empty.into_with_range(),
2319                        )
2320                        .into_with_range(),
2321                    )
2322                    .into_with_range(),
2323                )
2324                .into_with_range(),
2325            };
2326            check_path_selection(spec, "$.hello.world", expected.clone());
2327            check_path_selection(spec, "$.hello .world", expected.clone());
2328            check_path_selection(spec, "$.hello. world", expected.clone());
2329            check_path_selection(spec, "$.hello . world", expected.clone());
2330            check_path_selection(spec, "$ . hello . world", expected.clone());
2331            check_path_selection(spec, " $ . hello . world ", expected);
2332        }
2333
2334        {
2335            let expected = PathSelection::from_slice(
2336                &[
2337                    Key::Field("hello".to_string()),
2338                    Key::Field("world".to_string()),
2339                ],
2340                None,
2341            );
2342            check_path_selection(spec, "hello.world", expected.clone());
2343            check_path_selection(spec, "hello .world", expected.clone());
2344            check_path_selection(spec, "hello. world", expected.clone());
2345            check_path_selection(spec, "hello . world", expected.clone());
2346            check_path_selection(spec, " hello . world ", expected);
2347        }
2348
2349        {
2350            let expected = PathSelection::from_slice(
2351                &[
2352                    Key::Field("hello".to_string()),
2353                    Key::Field("world".to_string()),
2354                ],
2355                Some(SubSelection {
2356                    selections: vec![NamedSelection::field(
2357                        None,
2358                        Key::field("hello").into_with_range(),
2359                        None,
2360                    )],
2361                    ..Default::default()
2362                }),
2363            );
2364            check_path_selection(spec, "hello.world{hello}", expected.clone());
2365            check_path_selection(spec, "hello.world { hello }", expected.clone());
2366            check_path_selection(spec, "hello .world { hello }", expected.clone());
2367            check_path_selection(spec, "hello. world { hello }", expected.clone());
2368            check_path_selection(spec, "hello . world { hello }", expected.clone());
2369            check_path_selection(spec, " hello . world { hello } ", expected);
2370        }
2371
2372        {
2373            let expected = PathSelection::from_slice(
2374                &[
2375                    Key::Field("nested".to_string()),
2376                    Key::Quoted("string literal".to_string()),
2377                    Key::Quoted("property".to_string()),
2378                    Key::Field("name".to_string()),
2379                ],
2380                None,
2381            );
2382            check_path_selection(
2383                spec,
2384                "nested.'string literal'.\"property\".name",
2385                expected.clone(),
2386            );
2387            check_path_selection(
2388                spec,
2389                "nested. 'string literal'.\"property\".name",
2390                expected.clone(),
2391            );
2392            check_path_selection(
2393                spec,
2394                "nested.'string literal'. \"property\".name",
2395                expected.clone(),
2396            );
2397            check_path_selection(
2398                spec,
2399                "nested.'string literal'.\"property\" .name",
2400                expected.clone(),
2401            );
2402            check_path_selection(
2403                spec,
2404                "nested.'string literal'.\"property\". name",
2405                expected.clone(),
2406            );
2407            check_path_selection(
2408                spec,
2409                " nested . 'string literal' . \"property\" . name ",
2410                expected,
2411            );
2412        }
2413
2414        {
2415            let expected = PathSelection::from_slice(
2416                &[
2417                    Key::Field("nested".to_string()),
2418                    Key::Quoted("string literal".to_string()),
2419                ],
2420                Some(SubSelection {
2421                    selections: vec![NamedSelection::field(
2422                        Some(Alias::new("leggo")),
2423                        Key::quoted("my ego").into_with_range(),
2424                        None,
2425                    )],
2426                    ..Default::default()
2427                }),
2428            );
2429
2430            check_path_selection(
2431                spec,
2432                "nested.'string literal' { leggo: 'my ego' }",
2433                expected.clone(),
2434            );
2435
2436            check_path_selection(
2437                spec,
2438                " nested . 'string literal' { leggo : 'my ego' } ",
2439                expected.clone(),
2440            );
2441
2442            check_path_selection(
2443                spec,
2444                "nested. 'string literal' { leggo: 'my ego' }",
2445                expected.clone(),
2446            );
2447
2448            check_path_selection(
2449                spec,
2450                "nested . 'string literal' { leggo: 'my ego' }",
2451                expected.clone(),
2452            );
2453            check_path_selection(
2454                spec,
2455                " nested . \"string literal\" { leggo: 'my ego' } ",
2456                expected,
2457            );
2458        }
2459
2460        {
2461            let expected = PathSelection {
2462                path: PathList::Var(
2463                    KnownVariable::Dollar.into_with_range(),
2464                    PathList::Key(
2465                        Key::field("results").into_with_range(),
2466                        PathList::Selection(SubSelection {
2467                            selections: vec![NamedSelection::field(
2468                                None,
2469                                Key::quoted("quoted without alias").into_with_range(),
2470                                Some(SubSelection {
2471                                    selections: vec![
2472                                        NamedSelection::field(
2473                                            None,
2474                                            Key::field("id").into_with_range(),
2475                                            None,
2476                                        ),
2477                                        NamedSelection::field(
2478                                            None,
2479                                            Key::quoted("n a m e").into_with_range(),
2480                                            None,
2481                                        ),
2482                                    ],
2483                                    ..Default::default()
2484                                }),
2485                            )],
2486                            ..Default::default()
2487                        })
2488                        .into_with_range(),
2489                    )
2490                    .into_with_range(),
2491                )
2492                .into_with_range(),
2493            };
2494            check_path_selection(
2495                spec,
2496                "$.results{'quoted without alias'{id'n a m e'}}",
2497                expected.clone(),
2498            );
2499            check_path_selection(
2500                spec,
2501                " $ . results { 'quoted without alias' { id 'n a m e' } } ",
2502                expected,
2503            );
2504        }
2505
2506        {
2507            let expected = PathSelection {
2508                path: PathList::Var(
2509                    KnownVariable::Dollar.into_with_range(),
2510                    PathList::Key(
2511                        Key::field("results").into_with_range(),
2512                        PathList::Selection(SubSelection {
2513                            selections: vec![NamedSelection::field(
2514                                Some(Alias::quoted("non-identifier alias")),
2515                                Key::quoted("quoted with alias").into_with_range(),
2516                                Some(SubSelection {
2517                                    selections: vec![
2518                                        NamedSelection::field(
2519                                            None,
2520                                            Key::field("id").into_with_range(),
2521                                            None,
2522                                        ),
2523                                        NamedSelection::field(
2524                                            Some(Alias::quoted("n a m e")),
2525                                            Key::field("name").into_with_range(),
2526                                            None,
2527                                        ),
2528                                    ],
2529                                    ..Default::default()
2530                                }),
2531                            )],
2532                            ..Default::default()
2533                        })
2534                        .into_with_range(),
2535                    )
2536                    .into_with_range(),
2537                )
2538                .into_with_range(),
2539            };
2540            check_path_selection(
2541                spec,
2542                "$.results{'non-identifier alias':'quoted with alias'{id'n a m e':name}}",
2543                expected.clone(),
2544            );
2545            check_path_selection(
2546                spec,
2547                " $ . results { 'non-identifier alias' : 'quoted with alias' { id 'n a m e': name } } ",
2548                expected,
2549            );
2550        }
2551    }
2552
2553    #[rstest]
2554    #[case::v0_2(ConnectSpec::V0_2)]
2555    #[case::v0_3(ConnectSpec::V0_3)]
2556    #[case::v0_4(ConnectSpec::V0_4)]
2557    fn test_path_selection_vars(#[case] spec: ConnectSpec) {
2558        check_path_selection(
2559            spec,
2560            "$this",
2561            PathSelection {
2562                path: PathList::Var(
2563                    KnownVariable::External(Namespace::This.to_string()).into_with_range(),
2564                    PathList::Empty.into_with_range(),
2565                )
2566                .into_with_range(),
2567            },
2568        );
2569
2570        check_path_selection(
2571            spec,
2572            "$",
2573            PathSelection {
2574                path: PathList::Var(
2575                    KnownVariable::Dollar.into_with_range(),
2576                    PathList::Empty.into_with_range(),
2577                )
2578                .into_with_range(),
2579            },
2580        );
2581
2582        check_path_selection(
2583            spec,
2584            "$this { hello }",
2585            PathSelection {
2586                path: PathList::Var(
2587                    KnownVariable::External(Namespace::This.to_string()).into_with_range(),
2588                    PathList::Selection(SubSelection {
2589                        selections: vec![NamedSelection::field(
2590                            None,
2591                            Key::field("hello").into_with_range(),
2592                            None,
2593                        )],
2594                        ..Default::default()
2595                    })
2596                    .into_with_range(),
2597                )
2598                .into_with_range(),
2599            },
2600        );
2601
2602        check_path_selection(
2603            spec,
2604            "$ { hello }",
2605            PathSelection {
2606                path: PathList::Var(
2607                    KnownVariable::Dollar.into_with_range(),
2608                    PathList::Selection(SubSelection {
2609                        selections: vec![NamedSelection::field(
2610                            None,
2611                            Key::field("hello").into_with_range(),
2612                            None,
2613                        )],
2614                        ..Default::default()
2615                    })
2616                    .into_with_range(),
2617                )
2618                .into_with_range(),
2619            },
2620        );
2621
2622        check_path_selection(
2623            spec,
2624            "$this { before alias: $args.arg after }",
2625            PathList::Var(
2626                KnownVariable::External(Namespace::This.to_string()).into_with_range(),
2627                PathList::Selection(SubSelection {
2628                    selections: vec![
2629                        NamedSelection::field(None, Key::field("before").into_with_range(), None),
2630                        NamedSelection {
2631                            prefix: NamingPrefix::Alias(Alias::new("alias")),
2632                            path: PathSelection {
2633                                path: PathList::Var(
2634                                    KnownVariable::External(Namespace::Args.to_string())
2635                                        .into_with_range(),
2636                                    PathList::Key(
2637                                        Key::field("arg").into_with_range(),
2638                                        PathList::Empty.into_with_range(),
2639                                    )
2640                                    .into_with_range(),
2641                                )
2642                                .into_with_range(),
2643                            },
2644                        },
2645                        NamedSelection::field(None, Key::field("after").into_with_range(), None),
2646                    ],
2647                    ..Default::default()
2648                })
2649                .into_with_range(),
2650            )
2651            .into(),
2652        );
2653
2654        check_path_selection(
2655            spec,
2656            "$.nested { key injected: $args.arg }",
2657            PathSelection {
2658                path: PathList::Var(
2659                    KnownVariable::Dollar.into_with_range(),
2660                    PathList::Key(
2661                        Key::field("nested").into_with_range(),
2662                        PathList::Selection(SubSelection {
2663                            selections: vec![
2664                                NamedSelection::field(
2665                                    None,
2666                                    Key::field("key").into_with_range(),
2667                                    None,
2668                                ),
2669                                NamedSelection {
2670                                    prefix: NamingPrefix::Alias(Alias::new("injected")),
2671                                    path: PathSelection {
2672                                        path: PathList::Var(
2673                                            KnownVariable::External(Namespace::Args.to_string())
2674                                                .into_with_range(),
2675                                            PathList::Key(
2676                                                Key::field("arg").into_with_range(),
2677                                                PathList::Empty.into_with_range(),
2678                                            )
2679                                            .into_with_range(),
2680                                        )
2681                                        .into_with_range(),
2682                                    },
2683                                },
2684                            ],
2685                            ..Default::default()
2686                        })
2687                        .into_with_range(),
2688                    )
2689                    .into_with_range(),
2690                )
2691                .into_with_range(),
2692            },
2693        );
2694
2695        check_path_selection(
2696            spec,
2697            "$args.a.b.c",
2698            PathSelection {
2699                path: PathList::Var(
2700                    KnownVariable::External(Namespace::Args.to_string()).into_with_range(),
2701                    PathList::from_slice(
2702                        &[
2703                            Key::Field("a".to_string()),
2704                            Key::Field("b".to_string()),
2705                            Key::Field("c".to_string()),
2706                        ],
2707                        None,
2708                    )
2709                    .into_with_range(),
2710                )
2711                .into_with_range(),
2712            },
2713        );
2714
2715        check_path_selection(
2716            spec,
2717            "root.x.y.z",
2718            PathSelection::from_slice(
2719                &[
2720                    Key::Field("root".to_string()),
2721                    Key::Field("x".to_string()),
2722                    Key::Field("y".to_string()),
2723                    Key::Field("z".to_string()),
2724                ],
2725                None,
2726            ),
2727        );
2728
2729        check_path_selection(
2730            spec,
2731            "$.data",
2732            PathSelection {
2733                path: PathList::Var(
2734                    KnownVariable::Dollar.into_with_range(),
2735                    PathList::Key(
2736                        Key::field("data").into_with_range(),
2737                        PathList::Empty.into_with_range(),
2738                    )
2739                    .into_with_range(),
2740                )
2741                .into_with_range(),
2742            },
2743        );
2744
2745        check_path_selection(
2746            spec,
2747            "$.data.'quoted property'.nested",
2748            PathSelection {
2749                path: PathList::Var(
2750                    KnownVariable::Dollar.into_with_range(),
2751                    PathList::Key(
2752                        Key::field("data").into_with_range(),
2753                        PathList::Key(
2754                            Key::quoted("quoted property").into_with_range(),
2755                            PathList::Key(
2756                                Key::field("nested").into_with_range(),
2757                                PathList::Empty.into_with_range(),
2758                            )
2759                            .into_with_range(),
2760                        )
2761                        .into_with_range(),
2762                    )
2763                    .into_with_range(),
2764                )
2765                .into_with_range(),
2766            },
2767        );
2768
2769        #[track_caller]
2770        fn check_path_parse_error(
2771            input: &str,
2772            expected_offset: usize,
2773            expected_message: impl Into<String>,
2774        ) {
2775            let expected_message: String = expected_message.into();
2776            match PathSelection::parse(new_span_with_spec(input, ConnectSpec::latest())) {
2777                Ok((remainder, path)) => {
2778                    panic!(
2779                        "Expected error at offset {expected_offset} with message '{expected_message}', but got path {path:?} and remainder {remainder:?}",
2780                    );
2781                }
2782                Err(nom::Err::Error(e) | nom::Err::Failure(e)) => {
2783                    assert_eq!(&input[expected_offset..], *e.input.fragment());
2784                    // The PartialEq implementation for LocatedSpan
2785                    // unfortunately ignores span.extra, so we have to check
2786                    // e.input.extra manually.
2787                    assert_eq!(
2788                        e.input.extra,
2789                        SpanExtra {
2790                            spec: ConnectSpec::latest(),
2791                            errors: vec![(expected_message, expected_offset)],
2792                            local_vars: Vec::new(),
2793                        }
2794                    );
2795                }
2796                Err(e) => {
2797                    panic!("Unexpected error {e:?}");
2798                }
2799            }
2800        }
2801
2802        let single_key_path_error_message =
2803            "Single-key path must be prefixed with $. to avoid ambiguity with field name";
2804        check_path_parse_error(
2805            new_span("naked").fragment(),
2806            0,
2807            single_key_path_error_message,
2808        );
2809        check_path_parse_error(
2810            new_span("naked { hi }").fragment(),
2811            0,
2812            single_key_path_error_message,
2813        );
2814        check_path_parse_error(
2815            new_span("  naked { hi }").fragment(),
2816            2,
2817            single_key_path_error_message,
2818        );
2819
2820        let path_key_ambiguity_error_message =
2821            "Path selection . must be followed by key (identifier or quoted string literal)";
2822        check_path_parse_error(
2823            new_span("valid.$invalid").fragment(),
2824            5,
2825            path_key_ambiguity_error_message,
2826        );
2827        check_path_parse_error(
2828            new_span("  valid.$invalid").fragment(),
2829            7,
2830            path_key_ambiguity_error_message,
2831        );
2832        check_path_parse_error(
2833            new_span("  valid . $invalid").fragment(),
2834            8,
2835            path_key_ambiguity_error_message,
2836        );
2837
2838        assert_eq!(
2839            selection!("$").strip_ranges(),
2840            JSONSelection::path(PathSelection {
2841                path: PathList::Var(
2842                    KnownVariable::Dollar.into_with_range(),
2843                    PathList::Empty.into_with_range()
2844                )
2845                .into_with_range(),
2846            }),
2847        );
2848
2849        assert_eq!(
2850            selection!("$this").strip_ranges(),
2851            JSONSelection::path(PathSelection {
2852                path: PathList::Var(
2853                    KnownVariable::External(Namespace::This.to_string()).into_with_range(),
2854                    PathList::Empty.into_with_range()
2855                )
2856                .into_with_range(),
2857            }),
2858        );
2859
2860        assert_eq!(
2861            selection!("value: $ a { b c }").strip_ranges(),
2862            JSONSelection::named(SubSelection {
2863                selections: vec![
2864                    NamedSelection {
2865                        prefix: NamingPrefix::Alias(Alias::new("value")),
2866                        path: PathSelection {
2867                            path: PathList::Var(
2868                                KnownVariable::Dollar.into_with_range(),
2869                                PathList::Empty.into_with_range()
2870                            )
2871                            .into_with_range(),
2872                        },
2873                    },
2874                    NamedSelection::field(
2875                        None,
2876                        Key::field("a").into_with_range(),
2877                        Some(SubSelection {
2878                            selections: vec![
2879                                NamedSelection::field(
2880                                    None,
2881                                    Key::field("b").into_with_range(),
2882                                    None
2883                                ),
2884                                NamedSelection::field(
2885                                    None,
2886                                    Key::field("c").into_with_range(),
2887                                    None
2888                                ),
2889                            ],
2890                            ..Default::default()
2891                        }),
2892                    ),
2893                ],
2894                ..Default::default()
2895            }),
2896        );
2897        assert_eq!(
2898            selection!("value: $this { b c }").strip_ranges(),
2899            JSONSelection::named(SubSelection {
2900                selections: vec![NamedSelection {
2901                    prefix: NamingPrefix::Alias(Alias::new("value")),
2902                    path: PathSelection {
2903                        path: PathList::Var(
2904                            KnownVariable::External(Namespace::This.to_string()).into_with_range(),
2905                            PathList::Selection(SubSelection {
2906                                selections: vec![
2907                                    NamedSelection::field(
2908                                        None,
2909                                        Key::field("b").into_with_range(),
2910                                        None
2911                                    ),
2912                                    NamedSelection::field(
2913                                        None,
2914                                        Key::field("c").into_with_range(),
2915                                        None
2916                                    ),
2917                                ],
2918                                ..Default::default()
2919                            })
2920                            .into_with_range(),
2921                        )
2922                        .into_with_range(),
2923                    },
2924                }],
2925                ..Default::default()
2926            }),
2927        );
2928    }
2929
2930    #[test]
2931    fn test_error_snapshots_v0_2() {
2932        let spec = ConnectSpec::V0_2;
2933
2934        // The .data shorthand is no longer allowed, since it can be mistakenly
2935        // parsed as a continuation of a previous selection. Instead, use $.data
2936        // to achieve the same effect without ambiguity.
2937        assert_debug_snapshot!(JSONSelection::parse_with_spec(".data", spec));
2938
2939        // If you want to mix a path selection with other named selections, the
2940        // path selection must have a trailing subselection, to enforce that it
2941        // returns an object with statically known keys, or be inlined/spread
2942        // with a ... token.
2943        assert_debug_snapshot!(JSONSelection::parse_with_spec("id $.object", spec));
2944    }
2945
2946    #[test]
2947    fn test_error_snapshots_v0_3() {
2948        let spec = ConnectSpec::V0_3;
2949
2950        // The .data shorthand is no longer allowed, since it can be mistakenly
2951        // parsed as a continuation of a previous selection. Instead, use $.data
2952        // to achieve the same effect without ambiguity.
2953        assert_debug_snapshot!(JSONSelection::parse_with_spec(".data", spec));
2954
2955        // If you want to mix a path selection with other named selections, the
2956        // path selection must have a trailing subselection, to enforce that it
2957        // returns an object with statically known keys, or be inlined/spread
2958        // with a ... token.
2959        assert_debug_snapshot!(JSONSelection::parse_with_spec("id $.object", spec));
2960    }
2961
2962    #[test]
2963    fn test_error_snapshots_v0_4() {
2964        let spec = ConnectSpec::V0_4;
2965
2966        // When this assertion fails, don't panic, but it's time to decide how
2967        // the next-next version should behave in these error cases (possibly
2968        // exactly the same).
2969        assert_eq!(spec, ConnectSpec::next());
2970
2971        // The .data shorthand is no longer allowed, since it can be mistakenly
2972        // parsed as a continuation of a previous selection. Instead, use $.data
2973        // to achieve the same effect without ambiguity.
2974        assert_debug_snapshot!(JSONSelection::parse_with_spec(".data", spec));
2975
2976        // If you want to mix a path selection with other named selections, the
2977        // path selection must have a trailing subselection, to enforce that it
2978        // returns an object with statically known keys, or be inlined/spread
2979        // with a ... token.
2980        assert_debug_snapshot!(JSONSelection::parse_with_spec("id $.object", spec));
2981    }
2982
2983    #[rstest]
2984    #[case::v0_2(ConnectSpec::V0_2)]
2985    #[case::v0_3(ConnectSpec::V0_3)]
2986    #[case::v0_4(ConnectSpec::V0_4)]
2987    fn test_path_selection_at(#[case] spec: ConnectSpec) {
2988        check_path_selection(
2989            spec,
2990            "@",
2991            PathSelection {
2992                path: PathList::Var(
2993                    KnownVariable::AtSign.into_with_range(),
2994                    PathList::Empty.into_with_range(),
2995                )
2996                .into_with_range(),
2997            },
2998        );
2999
3000        check_path_selection(
3001            spec,
3002            "@.a.b.c",
3003            PathSelection {
3004                path: PathList::Var(
3005                    KnownVariable::AtSign.into_with_range(),
3006                    PathList::from_slice(
3007                        &[
3008                            Key::Field("a".to_string()),
3009                            Key::Field("b".to_string()),
3010                            Key::Field("c".to_string()),
3011                        ],
3012                        None,
3013                    )
3014                    .into_with_range(),
3015                )
3016                .into_with_range(),
3017            },
3018        );
3019
3020        check_path_selection(
3021            spec,
3022            "@.items->first",
3023            PathSelection {
3024                path: PathList::Var(
3025                    KnownVariable::AtSign.into_with_range(),
3026                    PathList::Key(
3027                        Key::field("items").into_with_range(),
3028                        PathList::Method(
3029                            WithRange::new("first".to_string(), None),
3030                            None,
3031                            PathList::Empty.into_with_range(),
3032                        )
3033                        .into_with_range(),
3034                    )
3035                    .into_with_range(),
3036                )
3037                .into_with_range(),
3038            },
3039        );
3040    }
3041
3042    #[rstest]
3043    #[case::v0_2(ConnectSpec::V0_2)]
3044    #[case::v0_3(ConnectSpec::V0_3)]
3045    #[case::v0_4(ConnectSpec::V0_4)]
3046    fn test_expr_path_selections(#[case] spec: ConnectSpec) {
3047        fn check_simple_lit_expr(spec: ConnectSpec, input: &str, expected: LitExpr) {
3048            check_path_selection(
3049                spec,
3050                input,
3051                PathSelection {
3052                    path: PathList::Expr(
3053                        expected.into_with_range(),
3054                        PathList::Empty.into_with_range(),
3055                    )
3056                    .into_with_range(),
3057                },
3058            );
3059        }
3060
3061        check_simple_lit_expr(spec, "$(null)", LitExpr::Null);
3062
3063        check_simple_lit_expr(spec, "$(true)", LitExpr::Bool(true));
3064        check_simple_lit_expr(spec, "$(false)", LitExpr::Bool(false));
3065
3066        check_simple_lit_expr(
3067            spec,
3068            "$(1234)",
3069            LitExpr::Number("1234".parse().expect("serde_json::Number parse error")),
3070        );
3071        check_simple_lit_expr(
3072            spec,
3073            "$(1234.5678)",
3074            LitExpr::Number("1234.5678".parse().expect("serde_json::Number parse error")),
3075        );
3076
3077        check_simple_lit_expr(
3078            spec,
3079            "$('hello world')",
3080            LitExpr::String("hello world".to_string()),
3081        );
3082        check_simple_lit_expr(
3083            spec,
3084            "$(\"hello world\")",
3085            LitExpr::String("hello world".to_string()),
3086        );
3087        check_simple_lit_expr(
3088            spec,
3089            "$(\"hello \\\"world\\\"\")",
3090            LitExpr::String("hello \"world\"".to_string()),
3091        );
3092
3093        check_simple_lit_expr(
3094            spec,
3095            "$([1, 2, 3])",
3096            LitExpr::Array(
3097                vec!["1".parse(), "2".parse(), "3".parse()]
3098                    .into_iter()
3099                    .map(|n| {
3100                        LitExpr::Number(n.expect("serde_json::Number parse error"))
3101                            .into_with_range()
3102                    })
3103                    .collect(),
3104            ),
3105        );
3106
3107        check_simple_lit_expr(spec, "$({})", LitExpr::Object(IndexMap::default()));
3108        check_simple_lit_expr(
3109            spec,
3110            "$({ a: 1, b: 2, c: 3 })",
3111            LitExpr::Object({
3112                let mut map = IndexMap::default();
3113                for (key, value) in &[("a", "1"), ("b", "2"), ("c", "3")] {
3114                    map.insert(
3115                        Key::field(key).into_with_range(),
3116                        LitExpr::Number(value.parse().expect("serde_json::Number parse error"))
3117                            .into_with_range(),
3118                    );
3119                }
3120                map
3121            }),
3122        );
3123    }
3124
3125    #[test]
3126    fn test_path_expr_with_spaces_v0_2() {
3127        assert_debug_snapshot!(selection!(
3128            " suffix : results -> slice ( $( - 1 ) -> mul ( $args . suffixLength ) ) ",
3129            // Snapshot tests can be brittle when used with (multiple) #[rstest]
3130            // cases, since the filenames of the snapshots do not always take
3131            // into account the differences between the cases, so we hard-code
3132            // the ConnectSpec in tests like this.
3133            ConnectSpec::V0_2
3134        ));
3135    }
3136
3137    #[test]
3138    fn test_path_expr_with_spaces_v0_3() {
3139        assert_debug_snapshot!(selection!(
3140            " suffix : results -> slice ( $( - 1 ) -> mul ( $args . suffixLength ) ) ",
3141            ConnectSpec::V0_3
3142        ));
3143    }
3144
3145    #[rstest]
3146    #[case::v0_2(ConnectSpec::V0_2)]
3147    #[case::v0_3(ConnectSpec::V0_3)]
3148    #[case::v0_4(ConnectSpec::V0_4)]
3149    fn test_path_methods(#[case] spec: ConnectSpec) {
3150        check_path_selection(
3151            spec,
3152            "data.x->or(data.y)",
3153            PathSelection {
3154                path: PathList::Key(
3155                    Key::field("data").into_with_range(),
3156                    PathList::Key(
3157                        Key::field("x").into_with_range(),
3158                        PathList::Method(
3159                            WithRange::new("or".to_string(), None),
3160                            Some(MethodArgs {
3161                                args: vec![
3162                                    LitExpr::Path(PathSelection::from_slice(
3163                                        &[Key::field("data"), Key::field("y")],
3164                                        None,
3165                                    ))
3166                                    .into_with_range(),
3167                                ],
3168                                ..Default::default()
3169                            }),
3170                            PathList::Empty.into_with_range(),
3171                        )
3172                        .into_with_range(),
3173                    )
3174                    .into_with_range(),
3175                )
3176                .into_with_range(),
3177            },
3178        );
3179
3180        {
3181            fn make_dollar_key_expr(key: &str) -> WithRange<LitExpr> {
3182                WithRange::new(
3183                    LitExpr::Path(PathSelection {
3184                        path: PathList::Var(
3185                            KnownVariable::Dollar.into_with_range(),
3186                            PathList::Key(
3187                                Key::field(key).into_with_range(),
3188                                PathList::Empty.into_with_range(),
3189                            )
3190                            .into_with_range(),
3191                        )
3192                        .into_with_range(),
3193                    }),
3194                    None,
3195                )
3196            }
3197
3198            let expected = PathSelection {
3199                path: PathList::Key(
3200                    Key::field("data").into_with_range(),
3201                    PathList::Method(
3202                        WithRange::new("query".to_string(), None),
3203                        Some(MethodArgs {
3204                            args: vec![
3205                                make_dollar_key_expr("a"),
3206                                make_dollar_key_expr("b"),
3207                                make_dollar_key_expr("c"),
3208                            ],
3209                            ..Default::default()
3210                        }),
3211                        PathList::Empty.into_with_range(),
3212                    )
3213                    .into_with_range(),
3214                )
3215                .into_with_range(),
3216            };
3217            check_path_selection(spec, "data->query($.a, $.b, $.c)", expected.clone());
3218            check_path_selection(spec, "data->query($.a, $.b, $.c )", expected.clone());
3219            check_path_selection(spec, "data->query($.a, $.b, $.c,)", expected.clone());
3220            check_path_selection(spec, "data->query($.a, $.b, $.c ,)", expected.clone());
3221            check_path_selection(spec, "data->query($.a, $.b, $.c , )", expected);
3222        }
3223
3224        {
3225            let expected = PathSelection {
3226                path: PathList::Key(
3227                    Key::field("data").into_with_range(),
3228                    PathList::Key(
3229                        Key::field("x").into_with_range(),
3230                        PathList::Method(
3231                            WithRange::new("concat".to_string(), None),
3232                            Some(MethodArgs {
3233                                args: vec![
3234                                    LitExpr::Array(vec![
3235                                        LitExpr::Path(PathSelection::from_slice(
3236                                            &[Key::field("data"), Key::field("y")],
3237                                            None,
3238                                        ))
3239                                        .into_with_range(),
3240                                        LitExpr::Path(PathSelection::from_slice(
3241                                            &[Key::field("data"), Key::field("z")],
3242                                            None,
3243                                        ))
3244                                        .into_with_range(),
3245                                    ])
3246                                    .into_with_range(),
3247                                ],
3248                                ..Default::default()
3249                            }),
3250                            PathList::Empty.into_with_range(),
3251                        )
3252                        .into_with_range(),
3253                    )
3254                    .into_with_range(),
3255                )
3256                .into_with_range(),
3257            };
3258            check_path_selection(spec, "data.x->concat([data.y, data.z])", expected.clone());
3259            check_path_selection(spec, "data.x->concat([ data.y, data.z ])", expected.clone());
3260            check_path_selection(spec, "data.x->concat([data.y, data.z,])", expected.clone());
3261            check_path_selection(
3262                spec,
3263                "data.x->concat([data.y, data.z , ])",
3264                expected.clone(),
3265            );
3266            check_path_selection(spec, "data.x->concat([data.y, data.z,],)", expected.clone());
3267            check_path_selection(spec, "data.x->concat([data.y, data.z , ] , )", expected);
3268        }
3269
3270        check_path_selection(
3271            spec,
3272            "data->method([$ { x2: x->times(2) }, $ { y2: y->times(2) }])",
3273            PathSelection {
3274                path: PathList::Key(
3275                    Key::field("data").into_with_range(),
3276                    PathList::Method(
3277                        WithRange::new("method".to_string(), None),
3278                        Some(MethodArgs {
3279                                args: vec![LitExpr::Array(vec![
3280                                LitExpr::Path(PathSelection {
3281                                    path: PathList::Var(
3282                                        KnownVariable::Dollar.into_with_range(),
3283                                        PathList::Selection(
3284                                            SubSelection {
3285                                                selections: vec![NamedSelection {
3286                                                    prefix: NamingPrefix::Alias(Alias::new("x2")),
3287                                                    path: PathSelection {
3288                                                        path: PathList::Key(
3289                                                            Key::field("x").into_with_range(),
3290                                                            PathList::Method(
3291                                                                WithRange::new(
3292                                                                    "times".to_string(),
3293                                                                    None,
3294                                                                ),
3295                                                                Some(MethodArgs {
3296                                                                    args: vec![LitExpr::Number(
3297                                                                        "2".parse().expect(
3298                                                                            "serde_json::Number parse error",
3299                                                                        ),
3300                                                                    ).into_with_range()],
3301                                                                    ..Default::default()
3302                                                                }),
3303                                                                PathList::Empty.into_with_range(),
3304                                                            )
3305                                                            .into_with_range(),
3306                                                        )
3307                                                        .into_with_range(),
3308                                                    },
3309                                                }],
3310                                                ..Default::default()
3311                                            },
3312                                        )
3313                                        .into_with_range(),
3314                                    )
3315                                    .into_with_range(),
3316                                })
3317                                .into_with_range(),
3318                                LitExpr::Path(PathSelection {
3319                                    path: PathList::Var(
3320                                        KnownVariable::Dollar.into_with_range(),
3321                                        PathList::Selection(
3322                                            SubSelection {
3323                                                selections: vec![NamedSelection {
3324                                                    prefix: NamingPrefix::Alias(Alias::new("y2")),
3325                                                    path: PathSelection {
3326                                                        path: PathList::Key(
3327                                                            Key::field("y").into_with_range(),
3328                                                            PathList::Method(
3329                                                                WithRange::new(
3330                                                                    "times".to_string(),
3331                                                                    None,
3332                                                                ),
3333                                                                Some(
3334                                                                    MethodArgs {
3335                                                                        args: vec![LitExpr::Number(
3336                                                                            "2".parse().expect(
3337                                                                                "serde_json::Number parse error",
3338                                                                            ),
3339                                                                        ).into_with_range()],
3340                                                                        ..Default::default()
3341                                                                    },
3342                                                                ),
3343                                                                PathList::Empty.into_with_range(),
3344                                                            )
3345                                                            .into_with_range(),
3346                                                        )
3347                                                        .into_with_range(),
3348                                                    },
3349                                                }],
3350                                                ..Default::default()
3351                                            },
3352                                        )
3353                                        .into_with_range(),
3354                                    )
3355                                    .into_with_range(),
3356                                })
3357                                .into_with_range(),
3358                            ])
3359                            .into_with_range()],
3360                            ..Default::default()
3361                        }),
3362                        PathList::Empty.into_with_range(),
3363                    )
3364                    .into_with_range(),
3365                )
3366                .into_with_range(),
3367            },
3368        );
3369    }
3370
3371    #[test]
3372    fn test_path_with_subselection() {
3373        assert_debug_snapshot!(selection!(
3374            r#"
3375            choices->first.message { content role }
3376        "#
3377        ));
3378
3379        assert_debug_snapshot!(selection!(
3380            r#"
3381            id
3382            created
3383            choices->first.message { content role }
3384            model
3385        "#
3386        ));
3387
3388        assert_debug_snapshot!(selection!(
3389            r#"
3390            id
3391            created
3392            choices->first.message { content role }
3393            model
3394            choices->last.message { lastContent: content }
3395        "#
3396        ));
3397
3398        assert_debug_snapshot!(JSONSelection::parse(
3399            r#"
3400            id
3401            created
3402            choices->first.message
3403            model
3404        "#
3405        ));
3406
3407        assert_debug_snapshot!(JSONSelection::parse(
3408            r#"
3409            id: $this.id
3410            $args.input {
3411                title
3412                body
3413            }
3414        "#
3415        ));
3416
3417        // Like the selection above, this selection produces an output shape
3418        // with id, title, and body all flattened in a top-level object.
3419        assert_debug_snapshot!(JSONSelection::parse(
3420            r#"
3421            $this { id }
3422            $args { $.input { title body } }
3423        "#
3424        ));
3425
3426        assert_debug_snapshot!(JSONSelection::parse(
3427            r#"
3428            # Equivalent to id: $this.id
3429            $this { id }
3430
3431            $args {
3432                __typename: $("Args")
3433
3434                # Using $. instead of just . prevents .input from
3435                # parsing as a key applied to the $("Args") string.
3436                $.input { title body }
3437
3438                extra
3439            }
3440
3441            from: $.from
3442        "#
3443        ));
3444    }
3445
3446    #[test]
3447    fn test_subselection() {
3448        fn check_parsed(input: &str, expected: SubSelection) {
3449            let (remainder, parsed) = SubSelection::parse(new_span(input)).unwrap();
3450            assert!(
3451                span_is_all_spaces_or_comments(remainder.clone()),
3452                "remainder is `{:?}`",
3453                remainder.clone(),
3454            );
3455            assert_eq!(parsed.strip_ranges(), expected);
3456        }
3457
3458        check_parsed(
3459            " { \n } ",
3460            SubSelection {
3461                selections: vec![],
3462                ..Default::default()
3463            },
3464        );
3465
3466        check_parsed(
3467            "{hello}",
3468            SubSelection {
3469                selections: vec![NamedSelection::field(
3470                    None,
3471                    Key::field("hello").into_with_range(),
3472                    None,
3473                )],
3474                ..Default::default()
3475            },
3476        );
3477
3478        check_parsed(
3479            "{ hello }",
3480            SubSelection {
3481                selections: vec![NamedSelection::field(
3482                    None,
3483                    Key::field("hello").into_with_range(),
3484                    None,
3485                )],
3486                ..Default::default()
3487            },
3488        );
3489
3490        check_parsed(
3491            "  { padded  } ",
3492            SubSelection {
3493                selections: vec![NamedSelection::field(
3494                    None,
3495                    Key::field("padded").into_with_range(),
3496                    None,
3497                )],
3498                ..Default::default()
3499            },
3500        );
3501
3502        check_parsed(
3503            "{ hello world }",
3504            SubSelection {
3505                selections: vec![
3506                    NamedSelection::field(None, Key::field("hello").into_with_range(), None),
3507                    NamedSelection::field(None, Key::field("world").into_with_range(), None),
3508                ],
3509                ..Default::default()
3510            },
3511        );
3512
3513        check_parsed(
3514            "{ hello { world } }",
3515            SubSelection {
3516                selections: vec![NamedSelection::field(
3517                    None,
3518                    Key::field("hello").into_with_range(),
3519                    Some(SubSelection {
3520                        selections: vec![NamedSelection::field(
3521                            None,
3522                            Key::field("world").into_with_range(),
3523                            None,
3524                        )],
3525                        ..Default::default()
3526                    }),
3527                )],
3528                ..Default::default()
3529            },
3530        );
3531    }
3532
3533    #[test]
3534    fn test_external_var_paths() {
3535        fn parse(input: &str) -> PathSelection {
3536            PathSelection::parse(new_span(input))
3537                .unwrap()
3538                .1
3539                .strip_ranges()
3540        }
3541
3542        {
3543            let sel = selection!(
3544                r#"
3545                $->echo([$args.arg1, $args.arg2, @.items->first])
3546            "#
3547            )
3548            .strip_ranges();
3549            let args_arg1_path = parse("$args.arg1");
3550            let args_arg2_path = parse("$args.arg2");
3551            assert_eq!(
3552                sel.external_var_paths(),
3553                vec![&args_arg1_path, &args_arg2_path]
3554            );
3555        }
3556        {
3557            let sel = selection!(
3558                r#"
3559                $this.kind->match(
3560                    ["A", $this.a],
3561                    ["B", $this.b],
3562                    ["C", $this.c],
3563                    [@, @->to_lower_case],
3564                )
3565            "#
3566            )
3567            .strip_ranges();
3568            let this_kind_path = match &sel.inner {
3569                TopLevelSelection::Path(path) => path,
3570                _ => panic!("Expected PathSelection"),
3571            };
3572            let this_a_path = parse("$this.a");
3573            let this_b_path = parse("$this.b");
3574            let this_c_path = parse("$this.c");
3575            assert_eq!(
3576                sel.external_var_paths(),
3577                vec![this_kind_path, &this_a_path, &this_b_path, &this_c_path,]
3578            );
3579        }
3580        {
3581            let sel = selection!(
3582                r#"
3583                data.results->slice($args.start, $args.end) {
3584                    id
3585                    __typename: $args.type
3586                }
3587            "#
3588            )
3589            .strip_ranges();
3590            let start_path = parse("$args.start");
3591            let end_path = parse("$args.end");
3592            let args_type_path = parse("$args.type");
3593            assert_eq!(
3594                sel.external_var_paths(),
3595                vec![&start_path, &end_path, &args_type_path]
3596            );
3597        }
3598    }
3599
3600    #[test]
3601    fn test_local_var_paths() {
3602        let spec = ConnectSpec::V0_3;
3603        let name_selection = selection!(
3604            "person->as($name, @.name)->as($stray, 123)->echo({ hello: $name })",
3605            spec
3606        );
3607        let local_var_names = name_selection.local_var_names();
3608        assert_eq!(local_var_names.len(), 2);
3609        assert!(local_var_names.contains("$name"));
3610        assert!(local_var_names.contains("$stray"));
3611    }
3612
3613    #[test]
3614    fn test_ranged_locations() {
3615        fn check(input: &str, expected: JSONSelection) {
3616            let parsed = JSONSelection::parse(input).unwrap();
3617            assert_eq!(parsed, expected);
3618        }
3619
3620        check(
3621            "hello",
3622            JSONSelection::named(SubSelection {
3623                selections: vec![NamedSelection::field(
3624                    None,
3625                    WithRange::new(Key::field("hello"), Some(0..5)),
3626                    None,
3627                )],
3628                range: Some(0..5),
3629            }),
3630        );
3631
3632        check(
3633            "  hello ",
3634            JSONSelection::named(SubSelection {
3635                selections: vec![NamedSelection::field(
3636                    None,
3637                    WithRange::new(Key::field("hello"), Some(2..7)),
3638                    None,
3639                )],
3640                range: Some(2..7),
3641            }),
3642        );
3643
3644        check(
3645            "  hello  { hi name }",
3646            JSONSelection::named(SubSelection {
3647                selections: vec![NamedSelection::field(
3648                    None,
3649                    WithRange::new(Key::field("hello"), Some(2..7)),
3650                    Some(SubSelection {
3651                        selections: vec![
3652                            NamedSelection::field(
3653                                None,
3654                                WithRange::new(Key::field("hi"), Some(11..13)),
3655                                None,
3656                            ),
3657                            NamedSelection::field(
3658                                None,
3659                                WithRange::new(Key::field("name"), Some(14..18)),
3660                                None,
3661                            ),
3662                        ],
3663                        range: Some(9..20),
3664                    }),
3665                )],
3666                range: Some(2..20),
3667            }),
3668        );
3669
3670        check(
3671            "$args.product.id",
3672            JSONSelection::path(PathSelection {
3673                path: WithRange::new(
3674                    PathList::Var(
3675                        WithRange::new(
3676                            KnownVariable::External(Namespace::Args.to_string()),
3677                            Some(0..5),
3678                        ),
3679                        WithRange::new(
3680                            PathList::Key(
3681                                WithRange::new(Key::field("product"), Some(6..13)),
3682                                WithRange::new(
3683                                    PathList::Key(
3684                                        WithRange::new(Key::field("id"), Some(14..16)),
3685                                        WithRange::new(PathList::Empty, Some(16..16)),
3686                                    ),
3687                                    Some(13..16),
3688                                ),
3689                            ),
3690                            Some(5..16),
3691                        ),
3692                    ),
3693                    Some(0..16),
3694                ),
3695            }),
3696        );
3697
3698        check(
3699            " $args . product . id ",
3700            JSONSelection::path(PathSelection {
3701                path: WithRange::new(
3702                    PathList::Var(
3703                        WithRange::new(
3704                            KnownVariable::External(Namespace::Args.to_string()),
3705                            Some(1..6),
3706                        ),
3707                        WithRange::new(
3708                            PathList::Key(
3709                                WithRange::new(Key::field("product"), Some(9..16)),
3710                                WithRange::new(
3711                                    PathList::Key(
3712                                        WithRange::new(Key::field("id"), Some(19..21)),
3713                                        WithRange::new(PathList::Empty, Some(21..21)),
3714                                    ),
3715                                    Some(17..21),
3716                                ),
3717                            ),
3718                            Some(7..21),
3719                        ),
3720                    ),
3721                    Some(1..21),
3722                ),
3723            }),
3724        );
3725
3726        check(
3727            "before product:$args.product{id name}after",
3728            JSONSelection::named(SubSelection {
3729                selections: vec![
3730                    NamedSelection::field(
3731                        None,
3732                        WithRange::new(Key::field("before"), Some(0..6)),
3733                        None,
3734                    ),
3735                    NamedSelection {
3736                        prefix: NamingPrefix::Alias(Alias {
3737                            name: WithRange::new(Key::field("product"), Some(7..14)),
3738                            range: Some(7..15),
3739                        }),
3740                        path: PathSelection {
3741                            path: WithRange::new(
3742                                PathList::Var(
3743                                    WithRange::new(
3744                                        KnownVariable::External(Namespace::Args.to_string()),
3745                                        Some(15..20),
3746                                    ),
3747                                    WithRange::new(
3748                                        PathList::Key(
3749                                            WithRange::new(Key::field("product"), Some(21..28)),
3750                                            WithRange::new(
3751                                                PathList::Selection(SubSelection {
3752                                                    selections: vec![
3753                                                        NamedSelection::field(
3754                                                            None,
3755                                                            WithRange::new(
3756                                                                Key::field("id"),
3757                                                                Some(29..31),
3758                                                            ),
3759                                                            None,
3760                                                        ),
3761                                                        NamedSelection::field(
3762                                                            None,
3763                                                            WithRange::new(
3764                                                                Key::field("name"),
3765                                                                Some(32..36),
3766                                                            ),
3767                                                            None,
3768                                                        ),
3769                                                    ],
3770                                                    range: Some(28..37),
3771                                                }),
3772                                                Some(28..37),
3773                                            ),
3774                                        ),
3775                                        Some(20..37),
3776                                    ),
3777                                ),
3778                                Some(15..37),
3779                            ),
3780                        },
3781                    },
3782                    NamedSelection::field(
3783                        None,
3784                        WithRange::new(Key::field("after"), Some(37..42)),
3785                        None,
3786                    ),
3787                ],
3788                range: Some(0..42),
3789            }),
3790        );
3791    }
3792
3793    #[test]
3794    fn test_variable_reference_no_path() {
3795        let selection = JSONSelection::parse("$this").unwrap();
3796        let var_paths = selection.external_var_paths();
3797        assert_eq!(var_paths.len(), 1);
3798        assert_eq!(
3799            var_paths[0].variable_reference(),
3800            Some(VariableReference {
3801                namespace: VariableNamespace {
3802                    namespace: Namespace::This,
3803                    location: Some(0..5),
3804                },
3805                selection: {
3806                    let mut selection = SelectionTrie::new();
3807                    selection.add_str_path([]);
3808                    selection
3809                },
3810                location: Some(0..5),
3811            })
3812        );
3813    }
3814
3815    #[test]
3816    fn test_variable_reference_with_path() {
3817        let selection = JSONSelection::parse("$this.a.b.c").unwrap();
3818        let var_paths = selection.external_var_paths();
3819        assert_eq!(var_paths.len(), 1);
3820
3821        let var_ref = var_paths[0].variable_reference().unwrap();
3822        assert_eq!(
3823            var_ref.namespace,
3824            VariableNamespace {
3825                namespace: Namespace::This,
3826                location: Some(0..5)
3827            }
3828        );
3829        assert_eq!(var_ref.selection.to_string(), "a { b { c } }");
3830        assert_eq!(var_ref.location, Some(0..11));
3831
3832        assert_eq!(
3833            var_ref.selection.key_ranges("a").collect::<Vec<_>>(),
3834            vec![6..7]
3835        );
3836        let a_trie = var_ref.selection.get("a").unwrap();
3837        assert_eq!(a_trie.key_ranges("b").collect::<Vec<_>>(), vec![8..9]);
3838        let b_trie = a_trie.get("b").unwrap();
3839        assert_eq!(b_trie.key_ranges("c").collect::<Vec<_>>(), vec![10..11]);
3840    }
3841
3842    #[test]
3843    fn test_variable_reference_nested() {
3844        let selection = JSONSelection::parse("a b { c: $this.x.y.z { d } }").unwrap();
3845        let var_paths = selection.external_var_paths();
3846        assert_eq!(var_paths.len(), 1);
3847
3848        let var_ref = var_paths[0].variable_reference().unwrap();
3849        assert_eq!(
3850            var_ref.namespace,
3851            VariableNamespace {
3852                namespace: Namespace::This,
3853                location: Some(9..14),
3854            }
3855        );
3856        assert_eq!(var_ref.selection.to_string(), "x { y { z { d } } }");
3857        assert_eq!(var_ref.location, Some(9..26));
3858
3859        assert_eq!(
3860            var_ref.selection.key_ranges("x").collect::<Vec<_>>(),
3861            vec![15..16]
3862        );
3863        let x_trie = var_ref.selection.get("x").unwrap();
3864        assert_eq!(x_trie.key_ranges("y").collect::<Vec<_>>(), vec![17..18]);
3865        let y_trie = x_trie.get("y").unwrap();
3866        assert_eq!(y_trie.key_ranges("z").collect::<Vec<_>>(), vec![19..20]);
3867        let z_trie = y_trie.get("z").unwrap();
3868        assert_eq!(z_trie.key_ranges("d").collect::<Vec<_>>(), vec![23..24]);
3869    }
3870
3871    #[test]
3872    fn test_external_var_paths_no_variable() {
3873        let selection = JSONSelection::parse("a.b.c").unwrap();
3874        let var_paths = selection.external_var_paths();
3875        assert_eq!(var_paths.len(), 0);
3876    }
3877
3878    #[test]
3879    fn test_naked_literal_path_for_connect_v0_2() {
3880        let spec = ConnectSpec::V0_2;
3881
3882        let selection_null_stringify_v0_2 = selection!("$(null->jsonStringify)", spec);
3883        assert_eq!(
3884            selection_null_stringify_v0_2.pretty_print(),
3885            "$(null->jsonStringify)"
3886        );
3887
3888        let selection_hello_slice_v0_2 = selection!("sliced: $('hello'->slice(1, 3))", spec);
3889        assert_eq!(
3890            selection_hello_slice_v0_2.pretty_print(),
3891            "sliced: $(\"hello\"->slice(1, 3))"
3892        );
3893
3894        let selection_true_not_v0_2 = selection!("true->not", spec);
3895        assert_eq!(selection_true_not_v0_2.pretty_print(), "true->not");
3896
3897        let selection_false_not_v0_2 = selection!("false->not", spec);
3898        assert_eq!(selection_false_not_v0_2.pretty_print(), "false->not");
3899
3900        let selection_object_path_v0_2 = selection!("$({ a: 123 }.a)", spec);
3901        assert_eq!(
3902            selection_object_path_v0_2.pretty_print_with_indentation(true, 0),
3903            "$({ a: 123 }.a)"
3904        );
3905
3906        let selection_array_path_v0_2 = selection!("$([1, 2, 3]->get(1))", spec);
3907        assert_eq!(
3908            selection_array_path_v0_2.pretty_print(),
3909            "$([1, 2, 3]->get(1))"
3910        );
3911
3912        assert_debug_snapshot!(selection_null_stringify_v0_2);
3913        assert_debug_snapshot!(selection_hello_slice_v0_2);
3914        assert_debug_snapshot!(selection_true_not_v0_2);
3915        assert_debug_snapshot!(selection_false_not_v0_2);
3916        assert_debug_snapshot!(selection_object_path_v0_2);
3917        assert_debug_snapshot!(selection_array_path_v0_2);
3918    }
3919
3920    #[test]
3921    fn test_optional_key_access() {
3922        let spec = ConnectSpec::V0_3;
3923
3924        check_path_selection(
3925            spec,
3926            "$.foo?.bar",
3927            PathSelection {
3928                path: PathList::Var(
3929                    KnownVariable::Dollar.into_with_range(),
3930                    PathList::Key(
3931                        Key::field("foo").into_with_range(),
3932                        PathList::Question(
3933                            PathList::Key(
3934                                Key::field("bar").into_with_range(),
3935                                PathList::Empty.into_with_range(),
3936                            )
3937                            .into_with_range(),
3938                        )
3939                        .into_with_range(),
3940                    )
3941                    .into_with_range(),
3942                )
3943                .into_with_range(),
3944            },
3945        );
3946    }
3947
3948    #[test]
3949    fn test_unambiguous_single_key_paths_v0_2() {
3950        let spec = ConnectSpec::V0_2;
3951
3952        let mul_with_dollars = selection!("a->mul($.b, $.c)", spec);
3953        mul_with_dollars.if_named_else_path(
3954            |named| {
3955                panic!("Expected a path selection, got named: {named:?}");
3956            },
3957            |path| {
3958                assert_eq!(path.get_single_key(), None);
3959                assert_eq!(path.pretty_print(), "a->mul($.b, $.c)");
3960            },
3961        );
3962
3963        assert_debug_snapshot!(mul_with_dollars);
3964    }
3965
3966    #[test]
3967    fn test_invalid_single_key_paths_v0_2() {
3968        let spec = ConnectSpec::V0_2;
3969
3970        let a_plus_b_plus_c = JSONSelection::parse_with_spec("a->add(b, c)", spec);
3971        assert_eq!(a_plus_b_plus_c, Err(JSONSelectionParseError {
3972            message: "Named path selection must either begin with alias or ..., or end with subselection".to_string(),
3973            fragment: "a->add(b, c)".to_string(),
3974            offset: 0,
3975            spec: ConnectSpec::V0_2,
3976        }));
3977
3978        let sum_a_plus_b_plus_c = JSONSelection::parse_with_spec("sum: a->add(b, c)", spec);
3979        assert_eq!(
3980            sum_a_plus_b_plus_c,
3981            Err(JSONSelectionParseError {
3982                message: "nom::error::ErrorKind::Eof".to_string(),
3983                fragment: "(b, c)".to_string(),
3984                offset: 11,
3985                spec: ConnectSpec::V0_2,
3986            })
3987        );
3988    }
3989
3990    #[test]
3991    fn test_optional_method_call() {
3992        let spec = ConnectSpec::V0_3;
3993
3994        check_path_selection(
3995            spec,
3996            "$.foo?->method",
3997            PathSelection {
3998                path: PathList::Var(
3999                    KnownVariable::Dollar.into_with_range(),
4000                    PathList::Key(
4001                        Key::field("foo").into_with_range(),
4002                        PathList::Question(
4003                            PathList::Method(
4004                                WithRange::new("method".to_string(), None),
4005                                None,
4006                                PathList::Empty.into_with_range(),
4007                            )
4008                            .into_with_range(),
4009                        )
4010                        .into_with_range(),
4011                    )
4012                    .into_with_range(),
4013                )
4014                .into_with_range(),
4015            },
4016        );
4017    }
4018
4019    #[test]
4020    fn test_chained_optional_accesses() {
4021        let spec = ConnectSpec::V0_3;
4022
4023        check_path_selection(
4024            spec,
4025            "$.foo?.bar?.baz",
4026            PathSelection {
4027                path: PathList::Var(
4028                    KnownVariable::Dollar.into_with_range(),
4029                    PathList::Key(
4030                        Key::field("foo").into_with_range(),
4031                        PathList::Question(
4032                            PathList::Key(
4033                                Key::field("bar").into_with_range(),
4034                                PathList::Question(
4035                                    PathList::Key(
4036                                        Key::field("baz").into_with_range(),
4037                                        PathList::Empty.into_with_range(),
4038                                    )
4039                                    .into_with_range(),
4040                                )
4041                                .into_with_range(),
4042                            )
4043                            .into_with_range(),
4044                        )
4045                        .into_with_range(),
4046                    )
4047                    .into_with_range(),
4048                )
4049                .into_with_range(),
4050            },
4051        );
4052    }
4053
4054    #[test]
4055    fn test_mixed_regular_and_optional_access() {
4056        let spec = ConnectSpec::V0_3;
4057
4058        check_path_selection(
4059            spec,
4060            "$.foo.bar?.baz",
4061            PathSelection {
4062                path: PathList::Var(
4063                    KnownVariable::Dollar.into_with_range(),
4064                    PathList::Key(
4065                        Key::field("foo").into_with_range(),
4066                        PathList::Key(
4067                            Key::field("bar").into_with_range(),
4068                            PathList::Question(
4069                                PathList::Key(
4070                                    Key::field("baz").into_with_range(),
4071                                    PathList::Empty.into_with_range(),
4072                                )
4073                                .into_with_range(),
4074                            )
4075                            .into_with_range(),
4076                        )
4077                        .into_with_range(),
4078                    )
4079                    .into_with_range(),
4080                )
4081                .into_with_range(),
4082            },
4083        );
4084    }
4085
4086    #[test]
4087    fn test_invalid_sequential_question_marks() {
4088        let spec = ConnectSpec::V0_3;
4089
4090        assert_eq!(
4091            JSONSelection::parse_with_spec("baz: $.foo??.bar", spec),
4092            Err(JSONSelectionParseError {
4093                message: "nom::error::ErrorKind::Eof".to_string(),
4094                fragment: "??.bar".to_string(),
4095                offset: 10,
4096                spec,
4097            }),
4098        );
4099
4100        assert_eq!(
4101            JSONSelection::parse_with_spec("baz: $.foo?->echo(null)??.bar", spec),
4102            Err(JSONSelectionParseError {
4103                message: "nom::error::ErrorKind::Eof".to_string(),
4104                fragment: "??.bar".to_string(),
4105                offset: 23,
4106                spec,
4107            }),
4108        );
4109    }
4110
4111    #[test]
4112    fn test_invalid_infix_operator_parsing() {
4113        let spec = ConnectSpec::V0_2;
4114
4115        assert_eq!(
4116            JSONSelection::parse_with_spec("aOrB: $($.a ?? $.b)", spec),
4117            Err(JSONSelectionParseError {
4118                message: "nom::error::ErrorKind::Eof".to_string(),
4119                fragment: "($.a ?? $.b)".to_string(),
4120                offset: 7,
4121                spec,
4122            }),
4123        );
4124
4125        assert_eq!(
4126            JSONSelection::parse_with_spec("aOrB: $($.a ?! $.b)", spec),
4127            Err(JSONSelectionParseError {
4128                message: "nom::error::ErrorKind::Eof".to_string(),
4129                fragment: "($.a ?! $.b)".to_string(),
4130                offset: 7,
4131                spec,
4132            }),
4133        );
4134    }
4135
4136    #[test]
4137    fn test_optional_chaining_with_subselection() {
4138        let spec = ConnectSpec::V0_3;
4139
4140        check_path_selection(
4141            spec,
4142            "$.foo?.bar { id name }",
4143            PathSelection {
4144                path: PathList::Var(
4145                    KnownVariable::Dollar.into_with_range(),
4146                    PathList::Key(
4147                        Key::field("foo").into_with_range(),
4148                        PathList::Question(
4149                            PathList::Key(
4150                                Key::field("bar").into_with_range(),
4151                                PathList::Selection(SubSelection {
4152                                    selections: vec![
4153                                        NamedSelection::field(
4154                                            None,
4155                                            Key::field("id").into_with_range(),
4156                                            None,
4157                                        ),
4158                                        NamedSelection::field(
4159                                            None,
4160                                            Key::field("name").into_with_range(),
4161                                            None,
4162                                        ),
4163                                    ],
4164                                    ..Default::default()
4165                                })
4166                                .into_with_range(),
4167                            )
4168                            .into_with_range(),
4169                        )
4170                        .into_with_range(),
4171                    )
4172                    .into_with_range(),
4173                )
4174                .into_with_range(),
4175            },
4176        );
4177    }
4178
4179    #[test]
4180    fn test_optional_method_with_arguments() {
4181        let spec = ConnectSpec::V0_3;
4182
4183        check_path_selection(
4184            spec,
4185            "$.foo?->filter('active')",
4186            PathSelection {
4187                path: PathList::Var(
4188                    KnownVariable::Dollar.into_with_range(),
4189                    PathList::Key(
4190                        Key::field("foo").into_with_range(),
4191                        PathList::Question(
4192                            PathList::Method(
4193                                WithRange::new("filter".to_string(), None),
4194                                Some(MethodArgs {
4195                                    args: vec![
4196                                        LitExpr::String("active".to_string()).into_with_range(),
4197                                    ],
4198                                    ..Default::default()
4199                                }),
4200                                PathList::Empty.into_with_range(),
4201                            )
4202                            .into_with_range(),
4203                        )
4204                        .into_with_range(),
4205                    )
4206                    .into_with_range(),
4207                )
4208                .into_with_range(),
4209            },
4210        );
4211    }
4212
4213    #[test]
4214    fn test_unambiguous_single_key_paths_v0_3() {
4215        let spec = ConnectSpec::V0_3;
4216
4217        let mul_with_dollars = selection!("a->mul($.b, $.c)", spec);
4218        mul_with_dollars.if_named_else_path(
4219            |named| {
4220                panic!("Expected a path selection, got named: {named:?}");
4221            },
4222            |path| {
4223                assert_eq!(path.get_single_key(), None);
4224                assert_eq!(path.pretty_print(), "a->mul($.b, $.c)");
4225            },
4226        );
4227
4228        assert_debug_snapshot!(mul_with_dollars);
4229    }
4230
4231    #[test]
4232    fn test_valid_single_key_path_v0_3() {
4233        let spec = ConnectSpec::V0_3;
4234
4235        let a_plus_b_plus_c = JSONSelection::parse_with_spec("a->add(b, c)", spec);
4236        if let Ok(selection) = a_plus_b_plus_c {
4237            selection.if_named_else_path(
4238                |named| {
4239                    panic!("Expected a path selection, got named: {named:?}");
4240                },
4241                |path| {
4242                    assert_eq!(path.pretty_print(), "a->add(b, c)");
4243                    assert_eq!(path.get_single_key(), None);
4244                },
4245            );
4246            assert_debug_snapshot!(selection);
4247        } else {
4248            panic!("Expected a valid selection, got error: {a_plus_b_plus_c:?}");
4249        }
4250    }
4251
4252    #[test]
4253    fn test_valid_single_key_path_with_alias_v0_3() {
4254        let spec = ConnectSpec::V0_3;
4255
4256        let sum_a_plus_b_plus_c = JSONSelection::parse_with_spec("sum: a->add(b, c)", spec);
4257        if let Ok(selection) = sum_a_plus_b_plus_c {
4258            selection.if_named_else_path(
4259                |named| {
4260                    for selection in named.selections_iter() {
4261                        assert_eq!(selection.pretty_print(), "sum: a->add(b, c)");
4262                        assert_eq!(
4263                            selection.get_single_key().map(|key| key.as_str()),
4264                            Some("sum")
4265                        );
4266                    }
4267                },
4268                |path| {
4269                    panic!("Expected any number of named selections, got path: {path:?}");
4270                },
4271            );
4272            assert_debug_snapshot!(selection);
4273        } else {
4274            panic!("Expected a valid selection, got error: {sum_a_plus_b_plus_c:?}");
4275        }
4276    }
4277
4278    #[test]
4279    fn test_disallowed_spread_syntax_error() {
4280        assert_eq!(
4281            JSONSelection::parse_with_spec("id ...names", ConnectSpec::V0_2),
4282            Err(JSONSelectionParseError {
4283                message: "nom::error::ErrorKind::Eof".to_string(),
4284                fragment: "...names".to_string(),
4285                offset: 3,
4286                spec: ConnectSpec::V0_2,
4287            }),
4288        );
4289
4290        assert_eq!(
4291            JSONSelection::parse_with_spec("id ...names", ConnectSpec::V0_3),
4292            Err(JSONSelectionParseError {
4293                message: "Spread syntax (...) is not supported in connect/v0.3 (use connect/v0.4)"
4294                    .to_string(),
4295                // This is the fragment and offset we should get, but we need to
4296                // store error offsets in SpanExtra::errors to provide that
4297                // information.
4298                fragment: "...names".to_string(),
4299                offset: 3,
4300                spec: ConnectSpec::V0_3,
4301            }),
4302        );
4303
4304        // Guard test disabled - spread syntax tests have been enabled for V0_4.
4305        // assert_eq!(ConnectSpec::V0_3, ConnectSpec::next());
4306    }
4307
4308    // Spread syntax tests for ConnectSpec::V0_4 (now enabled!)
4309    #[cfg(test)]
4310    mod spread_parsing {
4311        use crate::connectors::ConnectSpec;
4312        use crate::connectors::json_selection::PrettyPrintable;
4313        use crate::selection;
4314
4315        #[track_caller]
4316        pub(super) fn check(spec: ConnectSpec, input: &str, expected_pretty: &str) {
4317            let selection = selection!(input, spec);
4318            assert_eq!(selection.pretty_print(), expected_pretty);
4319        }
4320    }
4321
4322    #[test]
4323    fn test_basic_spread_parsing_one_field() {
4324        let spec = ConnectSpec::V0_4;
4325        let expected = "... a";
4326        spread_parsing::check(spec, "...a", expected);
4327        spread_parsing::check(spec, "... a", expected);
4328        spread_parsing::check(spec, "...a ", expected);
4329        spread_parsing::check(spec, "... a ", expected);
4330        spread_parsing::check(spec, " ... a ", expected);
4331        spread_parsing::check(spec, "...\na", expected);
4332        assert_debug_snapshot!(selection!("...a", spec));
4333    }
4334
4335    #[test]
4336    fn test_spread_parsing_spread_a_spread_b() {
4337        let spec = ConnectSpec::V0_4;
4338        let expected = "... a\n... b";
4339        spread_parsing::check(spec, "...a...b", expected);
4340        spread_parsing::check(spec, "... a ... b", expected);
4341        spread_parsing::check(spec, "... a ...b", expected);
4342        spread_parsing::check(spec, "... a ... b ", expected);
4343        spread_parsing::check(spec, " ... a ... b ", expected);
4344        assert_debug_snapshot!(selection!("...a...b", spec));
4345    }
4346
4347    #[test]
4348    fn test_spread_parsing_a_spread_b() {
4349        let spec = ConnectSpec::V0_4;
4350        let expected = "a\n... b";
4351        spread_parsing::check(spec, "a...b", expected);
4352        spread_parsing::check(spec, "a ... b", expected);
4353        spread_parsing::check(spec, "a\n...b", expected);
4354        spread_parsing::check(spec, "a\n...\nb", expected);
4355        spread_parsing::check(spec, "a...\nb", expected);
4356        spread_parsing::check(spec, " a ... b", expected);
4357        spread_parsing::check(spec, " a ...b", expected);
4358        spread_parsing::check(spec, " a ... b ", expected);
4359        assert_debug_snapshot!(selection!("a...b", spec));
4360    }
4361
4362    #[test]
4363    fn test_spread_parsing_spread_a_b() {
4364        let spec = ConnectSpec::V0_4;
4365        let expected = "... a\nb";
4366        spread_parsing::check(spec, "...a b", expected);
4367        spread_parsing::check(spec, "... a b", expected);
4368        spread_parsing::check(spec, "... a b ", expected);
4369        spread_parsing::check(spec, "... a\nb", expected);
4370        spread_parsing::check(spec, "... a\n b", expected);
4371        spread_parsing::check(spec, " ... a b ", expected);
4372        assert_debug_snapshot!(selection!("...a b", spec));
4373    }
4374
4375    #[test]
4376    fn test_spread_parsing_spread_a_b_c() {
4377        let spec = ConnectSpec::V0_4;
4378        let expected = "... a\nb\nc";
4379        spread_parsing::check(spec, "...a b c", expected);
4380        spread_parsing::check(spec, "... a b c", expected);
4381        spread_parsing::check(spec, "... a b c ", expected);
4382        spread_parsing::check(spec, "... a\nb\nc", expected);
4383        spread_parsing::check(spec, "... a\nb\n c", expected);
4384        spread_parsing::check(spec, " ... a b c ", expected);
4385        spread_parsing::check(spec, "...\na b c", expected);
4386        assert_debug_snapshot!(selection!("...a b c", spec));
4387    }
4388
4389    #[test]
4390    fn test_spread_parsing_spread_spread_a_sub_b() {
4391        let spec = ConnectSpec::V0_4;
4392        let expected = "... a {\n  b\n}";
4393        spread_parsing::check(spec, "...a{b}", expected);
4394        spread_parsing::check(spec, "... a { b }", expected);
4395        spread_parsing::check(spec, "...a { b }", expected);
4396        spread_parsing::check(spec, "... a { b } ", expected);
4397        spread_parsing::check(spec, "... a\n{ b }", expected);
4398        spread_parsing::check(spec, "... a\n{b}", expected);
4399        spread_parsing::check(spec, " ... a { b } ", expected);
4400        spread_parsing::check(spec, "...\na { b }", expected);
4401        assert_debug_snapshot!(selection!("...a{b}", spec));
4402    }
4403
4404    #[test]
4405    fn test_spread_parsing_spread_a_sub_b_c() {
4406        let spec = ConnectSpec::V0_4;
4407        let expected = "... a {\n  b\n  c\n}";
4408        spread_parsing::check(spec, "...a{b c}", expected);
4409        spread_parsing::check(spec, "... a { b c }", expected);
4410        spread_parsing::check(spec, "...a { b c }", expected);
4411        spread_parsing::check(spec, "... a { b c } ", expected);
4412        spread_parsing::check(spec, "... a\n{ b c }", expected);
4413        spread_parsing::check(spec, "... a\n{b c}", expected);
4414        spread_parsing::check(spec, " ... a { b c } ", expected);
4415        spread_parsing::check(spec, "...\na { b c }", expected);
4416        spread_parsing::check(spec, "...\na { b\nc }", expected);
4417        assert_debug_snapshot!(selection!("...a{b c}", spec));
4418    }
4419
4420    #[test]
4421    fn test_spread_parsing_spread_a_sub_b_spread_c() {
4422        let spec = ConnectSpec::V0_4;
4423        let expected = "... a {\n  b\n  ... c\n}";
4424        spread_parsing::check(spec, "...a{b...c}", expected);
4425        spread_parsing::check(spec, "... a { b ... c }", expected);
4426        spread_parsing::check(spec, "...a { b ... c }", expected);
4427        spread_parsing::check(spec, "... a { b ... c } ", expected);
4428        spread_parsing::check(spec, "... a\n{ b ... c }", expected);
4429        spread_parsing::check(spec, "... a\n{b ... c}", expected);
4430        spread_parsing::check(spec, " ... a { b ... c } ", expected);
4431        spread_parsing::check(spec, "...\na { b ... c }", expected);
4432        spread_parsing::check(spec, "...\na {b ...\nc }", expected);
4433        assert_debug_snapshot!(selection!("...a{b...c}", spec));
4434    }
4435
4436    #[test]
4437    fn test_spread_parsing_spread_a_sub_b_spread_c_d() {
4438        let spec = ConnectSpec::V0_4;
4439        let expected = "... a {\n  b\n  ... c\n  d\n}";
4440        spread_parsing::check(spec, "...a{b...c d}", expected);
4441        spread_parsing::check(spec, "... a { b ... c d }", expected);
4442        spread_parsing::check(spec, "...a { b ... c d }", expected);
4443        spread_parsing::check(spec, "... a { b ... c d } ", expected);
4444        spread_parsing::check(spec, "... a\n{ b ... c d }", expected);
4445        spread_parsing::check(spec, "... a\n{b ... c d}", expected);
4446        spread_parsing::check(spec, " ... a { b ... c d } ", expected);
4447        spread_parsing::check(spec, "...\na { b ... c d }", expected);
4448        spread_parsing::check(spec, "...\na {b ...\nc d }", expected);
4449        assert_debug_snapshot!(selection!("...a{b...c d}", spec));
4450    }
4451
4452    #[test]
4453    fn test_spread_parsing_spread_a_sub_spread_b_c_d_spread_e() {
4454        let spec = ConnectSpec::V0_4;
4455        let expected = "... a {\n  ... b\n  c\n  d\n  ... e\n}";
4456        spread_parsing::check(spec, "...a{...b c d...e}", expected);
4457        spread_parsing::check(spec, "... a { ... b c d ... e }", expected);
4458        spread_parsing::check(spec, "...a { ... b c d ... e }", expected);
4459        spread_parsing::check(spec, "... a { ... b c d ... e } ", expected);
4460        spread_parsing::check(spec, "... a\n{ ... b c d ... e }", expected);
4461        spread_parsing::check(spec, "... a\n{... b c d ... e}", expected);
4462        spread_parsing::check(spec, " ... a { ... b c d ... e } ", expected);
4463        spread_parsing::check(spec, "...\na { ... b c d ... e }", expected);
4464        spread_parsing::check(spec, "...\na {...\nb\nc d ...\ne }", expected);
4465        assert_debug_snapshot!(selection!("...a{...b c d...e}", spec));
4466    }
4467
4468    #[test]
4469    fn should_parse_null_coalescing_in_connect_0_3() {
4470        assert!(JSONSelection::parse_with_spec("sum: $(a ?? b)", ConnectSpec::V0_3).is_ok());
4471        assert!(JSONSelection::parse_with_spec("sum: $(a ?! b)", ConnectSpec::V0_3).is_ok());
4472    }
4473
4474    #[test]
4475    fn should_not_parse_null_coalescing_in_connect_0_2() {
4476        assert!(JSONSelection::parse_with_spec("sum: $(a ?? b)", ConnectSpec::V0_2).is_err());
4477        assert!(JSONSelection::parse_with_spec("sum: $(a ?! b)", ConnectSpec::V0_2).is_err());
4478    }
4479
4480    #[test]
4481    fn should_not_parse_mixed_operators_in_same_expression() {
4482        let result = JSONSelection::parse_with_spec("sum: $(a ?? b ?! c)", ConnectSpec::V0_3);
4483
4484        let err = result.expect_err("Expected parse error for mixed operators ?? and ?!");
4485        assert_eq!(
4486            err.message,
4487            "Found mixed operators ?? and ?!. You can only chain operators of the same kind."
4488        );
4489
4490        // Also test the reverse order
4491        let result2 = JSONSelection::parse_with_spec("sum: $(a ?! b ?? c)", ConnectSpec::V0_3);
4492        let err2 = result2.expect_err("Expected parse error for mixed operators ?! and ??");
4493        assert_eq!(
4494            err2.message,
4495            "Found mixed operators ?! and ??. You can only chain operators of the same kind."
4496        );
4497    }
4498
4499    #[test]
4500    fn should_parse_mixed_operators_in_nested_expression() {
4501        let result = JSONSelection::parse_with_spec("sum: $(a ?? $(b ?! c))", ConnectSpec::V0_3);
4502
4503        assert!(result.is_ok());
4504    }
4505
4506    #[test]
4507    fn should_parse_local_vars_as_such() {
4508        let spec = ConnectSpec::V0_3;
4509        // No external variable references because $ and @ are internal, and
4510        // $root is locally bound by the ->as method everywhere it's used.
4511        let all_local = selection!("$->as($root, @.data)->echo([$root, $root])", spec);
4512        assert!(all_local.external_var_paths().is_empty());
4513        assert_debug_snapshot!(all_local);
4514
4515        // Introducing one external variable reference: $ext.
4516        let ext = selection!("$->as($root, @.data)->echo([$root, $ext])", spec);
4517        let external_vars = ext.external_var_paths();
4518        assert_eq!(external_vars.len(), 1);
4519
4520        for ext_var in &external_vars {
4521            match ext_var.path.as_ref() {
4522                PathList::Var(var, _) => match var.as_ref() {
4523                    KnownVariable::External(var_name) => {
4524                        assert_eq!(var_name, "$ext");
4525                    }
4526                    _ => panic!("Expected external variable, got: {var:?}"),
4527                },
4528                _ => panic!(
4529                    "Expected variable at start of path, got: {:?}",
4530                    &ext_var.path
4531                ),
4532            };
4533        }
4534
4535        assert_debug_snapshot!(ext);
4536    }
4537}