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