Skip to main content

libgraphql_parser/
graphql_parser.rs

1//! Recursive descent parser for GraphQL documents.
2//!
3//! This module provides [`GraphQLParser`], a generic parser that works with any
4//! token source implementing [`GraphQLTokenSource`]. It supports parsing schema
5//! documents, executable documents, and mixed documents.
6//!
7//! # Architecture
8//!
9//! The parser uses recursive descent with a delimiter stack for error recovery.
10//! Most grammar rules have a corresponding `parse_*` method that returns
11//! `Result<AstNode, ()>`, where `Err(())` indicates a parse error was recorded
12//! and the caller should attempt recovery.
13//!
14//! # Error Recovery
15//!
16//! When an error is encountered:
17//! 1. An error is recorded via `record_error()`
18//! 2. The method returns `Err(())`
19//! 3. The caller can attempt recovery (e.g., skip to next definition)
20//!
21//! This allows collecting multiple errors in a single parse pass.
22
23use crate::ByteSpan;
24use crate::ast;
25use crate::GraphQLParseError;
26use crate::GraphQLParseErrorKind;
27use crate::GraphQLParserConfig;
28use crate::GraphQLTokenStream;
29use crate::ParseResult;
30use crate::ReservedNameContext;
31use crate::SourceSpan;
32use crate::ValueParsingError;
33use crate::token::GraphQLToken;
34use crate::token::GraphQLTokenKind;
35use crate::token::GraphQLTokenSource;
36use crate::token::StrGraphQLTokenSource;
37use smallvec::SmallVec;
38use std::borrow::Cow;
39
40// =============================================================================
41// Delimiter tracking for error recovery
42// =============================================================================
43
44/// Context in which a delimiter was opened, for error messages.
45#[derive(Debug, Clone, Copy)]
46enum DelimiterContext {
47    /// `schema { ... }`
48    SchemaDefinition,
49    /// `type Foo { ... }` (object type definitions)
50    ObjectTypeDefinition,
51    /// `interface Foo { ... }`
52    InterfaceDefinition,
53    /// `enum Foo { ... }`
54    EnumDefinition,
55    /// `input Foo { ... }`
56    InputObjectDefinition,
57    /// `{ field ... }` in operations/fragments
58    SelectionSet,
59    /// `(arg: value)` in field arguments
60    FieldArguments,
61    /// `@directive(arg: value)` in directive arguments
62    DirectiveArguments,
63    /// `($var: Type)` in operation variable definitions
64    VariableDefinitions,
65    /// `[Type]` in type annotations
66    ListType,
67    /// `[value, ...]` in list literals
68    ListValue,
69    /// `{ field: value }` in object literals
70    ObjectValue,
71    /// `(name: Type)` in field/directive argument definitions
72    ArgumentDefinitions,
73}
74
75impl DelimiterContext {
76    /// Returns a human-readable description of this context.
77    fn description(&self) -> &'static str {
78        match self {
79            DelimiterContext::SchemaDefinition => "schema definition",
80            DelimiterContext::ObjectTypeDefinition => "object type definition",
81            DelimiterContext::InterfaceDefinition => "interface definition",
82            DelimiterContext::EnumDefinition => "enum definition",
83            DelimiterContext::InputObjectDefinition => "input object definition",
84            DelimiterContext::SelectionSet => "selection set",
85            DelimiterContext::FieldArguments => "field arguments",
86            DelimiterContext::DirectiveArguments => "directive arguments",
87            DelimiterContext::VariableDefinitions => "variable definitions",
88            DelimiterContext::ListType => "list type annotation",
89            DelimiterContext::ListValue => "list value",
90            DelimiterContext::ObjectValue => "object value",
91            DelimiterContext::ArgumentDefinitions => "argument definitions",
92        }
93    }
94}
95
96/// Tracks an open delimiter for error recovery.
97#[derive(Debug, Clone)]
98struct OpenDelimiter {
99    /// Where the delimiter was opened
100    span: ByteSpan,
101    /// The parsing context (also implicitly identifies the delimiter type)
102    context: DelimiterContext,
103}
104
105/// Internal enum for recovery actions, used to avoid borrow conflicts.
106enum RecoveryAction {
107    /// Stop recovery, we found a valid definition start.
108    Stop,
109    /// Skip this token and continue looking.
110    Skip,
111    /// Check if this keyword starts a definition.
112    CheckKeyword(String),
113    /// Check if this string is a description before a definition.
114    CheckDescription,
115}
116
117/// Context for parsing values, determining whether variables are allowed.
118///
119/// This enum replaces a simple `bool` to provide context-specific error
120/// messages when variables appear in const-only contexts.
121#[derive(Clone, Copy, Debug)]
122enum ConstContext {
123    /// Variables are allowed (e.g., field arguments in operations).
124    AllowVariables,
125    /// Parsing a default value for a variable definition.
126    VariableDefaultValue,
127    /// Parsing a directive argument in a const context.
128    DirectiveArgument,
129    /// Parsing a default value for an input field or argument definition.
130    InputDefaultValue,
131}
132
133impl ConstContext {
134    /// Returns a human-readable description for error messages.
135    ///
136    /// Only called when variables are disallowed, so `AllowVariables` is
137    /// unreachable.
138    fn description(&self) -> &'static str {
139        match self {
140            ConstContext::AllowVariables => {
141                unreachable!("description() called on AllowVariables")
142            }
143            ConstContext::VariableDefaultValue => "variable default values",
144            ConstContext::DirectiveArgument => "directive arguments",
145            ConstContext::InputDefaultValue => "input field default values",
146        }
147    }
148}
149
150// =============================================================================
151// Implements clause (syntax-carrying return type)
152// =============================================================================
153
154/// Intermediate result from `parse_ast_implements_interfaces`,
155/// carrying the parsed interfaces and the tokens that make up
156/// the clause so callers can populate their syntax structs.
157struct ImplementsClause<'src> {
158    ampersands: Vec<GraphQLToken<'src>>,
159    implements_keyword: GraphQLToken<'src>,
160    interfaces: Vec<ast::Name<'src>>,
161    leading_ampersand: Option<GraphQLToken<'src>>,
162}
163
164/// Token-only portion of an `ImplementsClause`, separated from
165/// the `interfaces` vec so the caller can move `interfaces`
166/// into the AST node and keep the tokens for syntax population.
167struct ImplementsClauseTokens<'src> {
168    ampersands: Vec<GraphQLToken<'src>>,
169    implements_keyword: GraphQLToken<'src>,
170    leading_ampersand: Option<GraphQLToken<'src>>,
171}
172
173// =============================================================================
174// Main parser struct
175// =============================================================================
176
177/// A recursive descent parser for GraphQL documents.
178///
179/// `GraphQLParser` is [generic over the token source](crate::token::GraphQLTokenSource)
180/// where [`StrGraphQLTokenSource`] provides for `&str` common-case.
181/// Parameterization of the token source allows `GraphQLParser` to parse types
182/// of inputs other than just `&str`; For example,
183/// [`libgraphql-macros`](https://docs.rs/libgraphql-macros/latest/libgraphql_macros/)
184/// implements a
185/// [`RustMacroGraphQLTokenSource`](https://github.com/jeffmo/libgraphql/blob/main/crates/libgraphql-macros/src/rust_macro_graphql_token_source.rs)
186/// in order to provide the
187/// [`graphql_schema! {}`](https://docs.rs/libgraphql-macros/latest/libgraphql_macros/macro.graphql_schema.html)
188/// macro.
189///
190/// # Usage
191///
192/// ```rust
193/// use libgraphql_parser::ast;
194/// use libgraphql_parser::GraphQLParser;
195///
196/// let parser = GraphQLParser::new("type Query { hello: String }");
197/// let parse_result = parser.parse_schema_document();
198///
199/// assert!(!parse_result.has_errors());
200/// if let Some((doc, source_map)) = parse_result.into_valid() {
201///     // The first definition in the document is an `ast::TypeDefinition`
202///     let first_def = &doc.definitions[0];
203///     assert!(matches!(first_def, ast::Definition::TypeDefinition(_)));
204///
205///     // The first definition's `name` is "Query"
206///     assert!(matches!(first_def.name_value(), Some("Query")));
207///
208///     // Extract the starting line/col of the `Query` type definition
209///     let source_position = first_def.source_span(&source_map).unwrap();
210///     let (start_line, start_col) = source_position.start_inclusive.line_col();
211/// }
212/// ```
213pub struct GraphQLParser<'src, TTokenSource: GraphQLTokenSource<'src> = StrGraphQLTokenSource<'src>> {
214    /// Parser configuration controlling behavior such as syntax
215    /// struct population.
216    config: GraphQLParserConfig,
217
218    /// The underlying token stream with lookahead support.
219    token_stream: GraphQLTokenStream<'src, TTokenSource>,
220
221    /// Accumulated parse errors.
222    errors: Vec<GraphQLParseError>,
223
224    /// Stack of open delimiters for error recovery.
225    ///
226    /// Uses SmallVec to avoid heap allocation for typical nesting depths
227    /// (most GraphQL documents nest fewer than 8 delimiters deep).
228    delimiter_stack: SmallVec<[OpenDelimiter; 8]>,
229
230    /// Current nesting depth for recursive value parsing.
231    ///
232    /// Shared recursion depth counter, incremented on entry to
233    /// `parse_value`, `parse_selection_set`,
234    /// `parse_type_annotation`;
235    /// decremented on exit. Prevents stack overflow from deeply
236    /// nested constructs (e.g., `[[[...` values,
237    /// `{ f { f { ...` selection sets, `[[[String]]]` types).
238    recursion_depth: usize,
239
240    /// End byte offset of the most recently consumed token, used by
241    /// `eof_span()` to anchor EOF errors to the last known source
242    /// location.
243    last_end_position: Option<u32>,
244}
245
246impl<'src> GraphQLParser<'src, StrGraphQLTokenSource<'src>> {
247    /// Creates a new parser from a string-like source.
248    ///
249    /// Accepts any type that can be referenced as a `str`,
250    /// including `&str`, `&String`, and `&Cow<str>`.
251    ///
252    /// # Example
253    ///
254    /// ```
255    /// use libgraphql_parser::GraphQLParser;
256    ///
257    /// let source = "type Query { hello: String }";
258    /// let parser = GraphQLParser::new(source);
259    /// let result = parser.parse_schema_document();
260    /// assert!(!result.has_errors());
261    /// ```
262    pub fn new<S: AsRef<str> + ?Sized>(
263        source: &'src S,
264    ) -> Self {
265        let token_source =
266            StrGraphQLTokenSource::new(source.as_ref());
267        Self::from_token_source(token_source)
268    }
269
270    /// Creates a new parser from a string-like source with the
271    /// given configuration.
272    ///
273    /// # Example
274    ///
275    /// ```
276    /// use libgraphql_parser::GraphQLParser;
277    /// use libgraphql_parser::GraphQLParserConfig;
278    ///
279    /// let source = "type Query { hello: String }";
280    /// let config = GraphQLParserConfig::lean();
281    /// let parser = GraphQLParser::with_config(source, config);
282    /// let result = parser.parse_schema_document();
283    /// assert!(!result.has_errors());
284    /// ```
285    pub fn with_config<S: AsRef<str> + ?Sized>(
286        source: &'src S,
287        config: GraphQLParserConfig,
288    ) -> Self {
289        let token_source =
290            StrGraphQLTokenSource::new(source.as_ref());
291        Self::from_token_source_with_config(token_source, config)
292    }
293}
294
295impl<'src, TTokenSource: GraphQLTokenSource<'src>> GraphQLParser<'src, TTokenSource> {
296    /// Maximum nesting depth for recursive parsing (values, selection
297    /// sets, and type annotations).
298    ///
299    /// Prevents stack overflow from adversarial inputs like `[[[[[...`
300    /// with hundreds of unclosed brackets. 32 levels is far beyond any
301    /// realistic GraphQL document (most real-world documents nest
302    /// fewer than 15 levels) while staying safe on the default 2 MiB
303    /// thread stack in debug builds where AST types and stack frames
304    /// are larger than in release builds.
305    const MAX_RECURSION_DEPTH: usize = 32;
306
307    /// Creates a new parser from a token source with the default
308    /// configuration.
309    pub fn from_token_source(
310        token_source: TTokenSource,
311    ) -> Self {
312        Self::from_token_source_with_config(
313            token_source,
314            GraphQLParserConfig::default(),
315        )
316    }
317
318    /// Creates a new parser from a token source with the given
319    /// configuration.
320    pub fn from_token_source_with_config(
321        token_source: TTokenSource,
322        config: GraphQLParserConfig,
323    ) -> Self {
324        Self {
325            config,
326            token_stream: GraphQLTokenStream::new(token_source),
327            errors: Vec::new(),
328            delimiter_stack: SmallVec::new(),
329            recursion_depth: 0,
330            last_end_position: None,
331        }
332    }
333
334    // =========================================================================
335    // Error recording and recovery
336    // =========================================================================
337
338    /// Records a parse error.
339    fn record_error(&mut self, error: GraphQLParseError) {
340        self.errors.push(error);
341    }
342
343    /// Resolves a `ByteSpan` to a `SourceSpan` using the current
344    /// token source's `SourceMap`.
345    ///
346    /// Falls back to `SourceSpan::zero()` when
347    /// `SourceMap::resolve_span()` returns `None` (e.g. empty
348    /// source map, out-of-bounds offset). This ensures every
349    /// `GraphQLParseError` always has *some* `SourceSpan` —
350    /// callers that need to distinguish "unresolved" from "byte 0"
351    /// should use `SourceMap::resolve_span()` directly.
352    fn resolve_span(&self, span: ByteSpan) -> SourceSpan {
353        self.token_stream.source_map().resolve_span(span)
354            .unwrap_or_else(SourceSpan::zero)
355    }
356
357    /// Push an open delimiter onto the stack.
358    fn push_delimiter(
359        &mut self,
360        span: ByteSpan,
361        context: DelimiterContext,
362    ) {
363        self.delimiter_stack.push(OpenDelimiter { span, context });
364    }
365
366    /// Pop the most recent open delimiter.
367    fn pop_delimiter(&mut self) -> Option<OpenDelimiter> {
368        self.delimiter_stack.pop()
369    }
370
371    /// Skip tokens until we find the start of a new definition.
372    ///
373    /// Definition keywords: `type`, `interface`, `union`, `enum`, `scalar`,
374    /// `input`, `directive`, `schema`, `extend`, `query`, `mutation`,
375    /// `subscription`, `fragment`, or `{` (anonymous query).
376    fn recover_to_next_definition(&mut self) {
377        loop {
378            // Extract info from peek without holding the borrow
379            let action = match self.token_stream.peek() {
380                None => RecoveryAction::Stop,
381                Some(token) => match &token.kind {
382                    GraphQLTokenKind::Eof => RecoveryAction::Stop,
383                    GraphQLTokenKind::CurlyBraceOpen => RecoveryAction::Stop,
384                    GraphQLTokenKind::Name(name) => {
385                        let name_owned = name.to_string();
386                        RecoveryAction::CheckKeyword(name_owned)
387                    }
388                    GraphQLTokenKind::StringValue(_) => {
389                        RecoveryAction::CheckDescription
390                    }
391                    _ => RecoveryAction::Skip,
392                },
393            };
394
395            match action {
396                RecoveryAction::Stop => break,
397                RecoveryAction::Skip => {
398                    self.consume_token();
399                }
400                RecoveryAction::CheckKeyword(keyword) => {
401                    if self.looks_like_definition_start(&keyword) {
402                        break;
403                    }
404                    self.consume_token();
405                }
406                RecoveryAction::CheckDescription => {
407                    // Check if next token after string is a definition keyword
408                    let is_description_for_def =
409                        if let Some(next) = self.token_stream.peek_nth(1)
410                            && let GraphQLTokenKind::Name(name) = &next.kind {
411                            matches!(
412                                name.as_ref(),
413                                "type"
414                                    | "interface"
415                                    | "union"
416                                    | "enum"
417                                    | "scalar"
418                                    | "input"
419                                    | "directive"
420                                    | "schema"
421                                    | "extend"
422                            )
423                        } else {
424                            false
425                        };
426                    if is_description_for_def {
427                        break;
428                    }
429                    self.consume_token();
430                }
431            }
432        }
433        // Clear delimiter stack since we're starting fresh
434        self.delimiter_stack.clear();
435    }
436
437    /// Checks if the current keyword looks like the start of a definition by
438    /// peeking at the next token.
439    ///
440    /// This helps avoid false recovery points like "type: String" where `type`
441    /// appears as a field name rather than a type definition keyword.
442    fn looks_like_definition_start(&mut self, keyword: &str) -> bool {
443        let next = self.token_stream.peek_nth(1);
444
445        match keyword {
446            // Type definitions: `type Name`, `interface Name`, etc.
447            // Next token should be a Name (the type name)
448            "type" | "interface" | "union" | "enum" | "scalar" | "input" => {
449                matches!(
450                    next.map(|t| &t.kind),
451                    Some(
452                        GraphQLTokenKind::Name(_)
453                            | GraphQLTokenKind::True
454                            | GraphQLTokenKind::False
455                            | GraphQLTokenKind::Null
456                    )
457                )
458            }
459
460            // `directive @Name` - next should be @
461            "directive" => {
462                matches!(next.map(|t| &t.kind), Some(GraphQLTokenKind::At))
463            }
464
465            // `schema { ... }` or `schema @directive` - next should be { or @
466            "schema" => {
467                matches!(
468                    next.map(|t| &t.kind),
469                    Some(GraphQLTokenKind::CurlyBraceOpen | GraphQLTokenKind::At)
470                )
471            }
472
473            // `extend type ...` - next should be a type keyword
474            "extend" => {
475                if let Some(next_token) = next {
476                    if let GraphQLTokenKind::Name(n) = &next_token.kind {
477                        matches!(
478                            n.as_ref(),
479                            "type"
480                                | "interface"
481                                | "union"
482                                | "enum"
483                                | "scalar"
484                                | "input"
485                                | "schema"
486                        )
487                    } else {
488                        false
489                    }
490                } else {
491                    false
492                }
493            }
494
495            // Operations: `query Name`, `query {`, `query(`, `query @`
496            "query" | "mutation" | "subscription" => {
497                matches!(
498                    next.map(|t| &t.kind),
499                    Some(
500                        GraphQLTokenKind::Name(_)
501                            | GraphQLTokenKind::True
502                            | GraphQLTokenKind::False
503                            | GraphQLTokenKind::Null
504                            | GraphQLTokenKind::CurlyBraceOpen
505                            | GraphQLTokenKind::ParenOpen
506                            | GraphQLTokenKind::At
507                    )
508                ) || next.is_none() // `query` at EOF is still a recovery point
509            }
510
511            // `fragment Name on Type` - next should be a name (not "on")
512            "fragment" => {
513                if let Some(next_token) = next {
514                    if let GraphQLTokenKind::Name(n) = &next_token.kind {
515                        // Fragment name cannot be "on"
516                        n.as_ref() != "on"
517                    } else {
518                        matches!(
519                            &next_token.kind,
520                            GraphQLTokenKind::True
521                                | GraphQLTokenKind::False
522                                | GraphQLTokenKind::Null
523                        )
524                    }
525                } else {
526                    false
527                }
528            }
529
530            _ => false,
531        }
532    }
533
534    // =========================================================================
535    // Token expectation helpers
536    // =========================================================================
537
538    /// Expects a specific token kind and consumes it.
539    ///
540    /// Returns the owned token if it matches, or records an error
541    /// and returns `Err(())`.
542    fn expect(
543        &mut self,
544        expected_kind: &GraphQLTokenKind,
545    ) -> Result<GraphQLToken<'src>, ()> {
546        // Check token kind via peek (scoped borrow). We extract
547        // what we need for the error path before dropping the
548        // borrow so that consume_token() can be called on the
549        // success path without a clone.
550        let mismatch_info = match self.token_stream.peek() {
551            None => {
552                let span = self.eof_span();
553                self.record_error(GraphQLParseError::new(
554                    format!(
555                        "expected `{}`",
556                        Self::token_kind_display(expected_kind),
557                    ),
558                    GraphQLParseErrorKind::UnexpectedEof {
559                        expected: vec![
560                            Self::token_kind_display(
561                                expected_kind,
562                            ),
563                        ],
564                    },
565                    self.resolve_span(span),
566                ));
567                return Err(());
568            },
569            Some(token) => {
570                if Self::token_kinds_match(
571                    &token.kind,
572                    expected_kind,
573                ) {
574                    None
575                } else {
576                    Some((
577                        token.span,
578                        Self::token_kind_display(&token.kind),
579                    ))
580                }
581            },
582        };
583        // Peek borrow is dropped — safe to mutate.
584        if let Some((span, found)) = mismatch_info {
585            self.record_error(GraphQLParseError::new(
586                format!(
587                    "expected `{}`, found `{}`",
588                    Self::token_kind_display(expected_kind),
589                    found,
590                ),
591                GraphQLParseErrorKind::UnexpectedToken {
592                    expected: vec![
593                        Self::token_kind_display(expected_kind),
594                    ],
595                    found,
596                },
597                self.resolve_span(span),
598            ));
599            Err(())
600        } else {
601            Ok(self.consume_token().unwrap())
602        }
603    }
604
605    /// Expects a name token and returns an `ast::Name`.
606    ///
607    /// Moves the span from the consumed token (zero-cost).
608    /// On error, does NOT consume the mismatched token (see
609    /// error recovery convention in plan).
610    fn expect_ast_name(&mut self) -> Result<ast::Name<'src>, ()> {
611        let mismatch = match self.token_stream.peek() {
612            None => {
613                let span = self.eof_span();
614                self.record_error(GraphQLParseError::new(
615                    "expected name",
616                    GraphQLParseErrorKind::UnexpectedEof {
617                        expected: vec!["name".to_string()],
618                    },
619                    self.resolve_span(span),
620                ));
621                return Err(());
622            },
623            Some(token) => match &token.kind {
624                GraphQLTokenKind::Name(_)
625                | GraphQLTokenKind::True
626                | GraphQLTokenKind::False
627                | GraphQLTokenKind::Null => None,
628                _ => Some((token.span, Self::token_kind_display(&token.kind))),
629            },
630        };
631        if let Some((span, found)) = mismatch {
632            self.record_error(GraphQLParseError::new(
633                format!("expected name, found `{found}`"),
634                GraphQLParseErrorKind::UnexpectedToken {
635                    expected: vec!["name".to_string()],
636                    found,
637                },
638                self.resolve_span(span),
639            ));
640            return Err(());
641        }
642        let token = self.consume_token().unwrap();
643        if self.config.retain_syntax {
644            let value = match &token.kind {
645                GraphQLTokenKind::Name(s) => s.clone(),
646                GraphQLTokenKind::True => Cow::Borrowed("true"),
647                GraphQLTokenKind::False => Cow::Borrowed("false"),
648                GraphQLTokenKind::Null => Cow::Borrowed("null"),
649                _ => unreachable!(),
650            };
651            let span = token.span;
652            Ok(ast::Name {
653                span,
654                syntax: Some(Box::new(ast::NameSyntax { token })),
655                value,
656            })
657        } else {
658            let value = match token.kind {
659                GraphQLTokenKind::Name(s) => s,
660                GraphQLTokenKind::True => Cow::Borrowed("true"),
661                GraphQLTokenKind::False => Cow::Borrowed("false"),
662                GraphQLTokenKind::Null => Cow::Borrowed("null"),
663                _ => unreachable!(),
664            };
665            Ok(ast::Name {
666                span: token.span,
667                syntax: None,
668                value,
669            })
670        }
671    }
672
673    /// Expects a specific keyword (a Name token with specific text).
674    ///
675    /// This is used for GraphQL structural keywords like `query`, `mutation`,
676    /// `type`, `interface`, etc.
677    ///
678    /// # Note on `true`, `false`, `null`
679    ///
680    /// This function does **not** match `True`, `False`, or `Null` tokens.
681    /// Those are lexed as distinct token kinds, not as `Name` tokens. This is
682    /// intentional: `expect_keyword()` is for structural keywords, not for
683    /// boolean/null literals. If you need to accept `true`/`false`/`null` as
684    /// names, use [`expect_name()`](Self::expect_name) instead.
685    // TODO: Ensure test coverage verifies expect_keyword("true") does NOT
686    // match a True token.
687    fn expect_keyword(
688        &mut self,
689        keyword: &str,
690    ) -> Result<GraphQLToken<'src>, ()> {
691        let mismatch = match self.token_stream.peek() {
692            None => {
693                let span = self.eof_span();
694                self.record_error(GraphQLParseError::new(
695                    format!("expected `{keyword}`"),
696                    GraphQLParseErrorKind::UnexpectedEof {
697                        expected: vec![keyword.to_string()],
698                    },
699                    self.resolve_span(span),
700                ));
701                return Err(());
702            },
703            Some(token) => {
704                if let GraphQLTokenKind::Name(name) = &token.kind
705                    && name.as_ref() == keyword {
706                    None
707                } else {
708                    Some((
709                        token.span,
710                        Self::token_kind_display(
711                            &token.kind,
712                        ),
713                    ))
714                }
715            },
716        };
717        if let Some((span, found)) = mismatch {
718            self.record_error(GraphQLParseError::new(
719                format!(
720                    "expected `{keyword}`, found `{found}`"
721                ),
722                GraphQLParseErrorKind::UnexpectedToken {
723                    expected: vec![keyword.to_string()],
724                    found,
725                },
726                self.resolve_span(span),
727            ));
728            return Err(());
729        }
730        Ok(self.consume_token().unwrap())
731    }
732
733    /// Checks if the current token is a specific keyword without consuming.
734    ///
735    /// This is used for GraphQL structural keywords like `query`, `mutation`,
736    /// `type`, `interface`, etc.
737    ///
738    /// # Note on `true`, `false`, `null`
739    ///
740    /// This function returns `false` for `True`, `False`, and `Null` tokens,
741    /// even if you call `peek_is_keyword("true")`. Those are lexed as distinct
742    /// token kinds, not as `Name` tokens. This is intentional:
743    /// `peek_is_keyword()` is for structural keywords, not for boolean/null
744    /// literals.
745    // TODO: Ensure test coverage verifies peek_is_keyword("true") returns
746    // false when looking at a True token.
747    fn peek_is_keyword(&mut self, keyword: &str) -> bool {
748        match self.token_stream.peek() {
749            Some(token) => {
750                if let GraphQLTokenKind::Name(name) = &token.kind {
751                    name.as_ref() == keyword
752                } else {
753                    false
754                }
755            }
756            None => false,
757        }
758    }
759
760    /// Checks if the current token matches the given kind without consuming.
761    fn peek_is(&mut self, kind: &GraphQLTokenKind) -> bool {
762        match self.token_stream.peek() {
763            Some(token) => Self::token_kinds_match(&token.kind, kind),
764            None => false,
765        }
766    }
767
768    // =========================================================================
769    // Helper methods
770    // =========================================================================
771
772    /// Consumes the next token from the stream and tracks its end
773    /// position for EOF error reporting.
774    fn consume_token(
775        &mut self,
776    ) -> Option<GraphQLToken<'src>> {
777        let token = self.token_stream.consume();
778        if let Some(ref t) = token {
779            self.last_end_position = Some(t.span.end);
780        }
781        token
782    }
783
784    /// Returns a span for EOF errors, anchored to the end of the
785    /// last consumed token if available.
786    fn eof_span(&self) -> ByteSpan {
787        if let Some(pos) = self.last_end_position {
788            ByteSpan::new(pos, pos)
789        } else {
790            ByteSpan::new(0, 0)
791        }
792    }
793
794    /// Builds a `ByteSpan` from a start span and the parser's
795    /// last-consumed end position.
796    fn make_span(&self, start: ByteSpan) -> ByteSpan {
797        let end = self.last_end_position.unwrap_or(start.start);
798        ByteSpan::new(start.start, end)
799    }
800
801    /// Like [`make_span`](Self::make_span), but borrows `start`
802    /// instead of moving it.
803    ///
804    /// Used on the `retain_syntax == true` path where the token
805    /// (and its span) must survive to be moved into a syntax
806    /// struct.
807    fn make_span_ref(&self, start: &ByteSpan) -> ByteSpan {
808        let end = self.last_end_position.unwrap_or(start.start);
809        ByteSpan::new(start.start, end)
810    }
811
812    /// Returns a human-readable display string for a token kind.
813    fn token_kind_display(kind: &GraphQLTokenKind) -> String {
814        match kind {
815            GraphQLTokenKind::Ampersand => "&".to_string(),
816            GraphQLTokenKind::At => "@".to_string(),
817            GraphQLTokenKind::Bang => "!".to_string(),
818            GraphQLTokenKind::Colon => ":".to_string(),
819            GraphQLTokenKind::CurlyBraceClose => "}".to_string(),
820            GraphQLTokenKind::CurlyBraceOpen => "{".to_string(),
821            GraphQLTokenKind::Dollar => "$".to_string(),
822            GraphQLTokenKind::Ellipsis => "...".to_string(),
823            GraphQLTokenKind::Equals => "=".to_string(),
824            GraphQLTokenKind::ParenClose => ")".to_string(),
825            GraphQLTokenKind::ParenOpen => "(".to_string(),
826            GraphQLTokenKind::Pipe => "|".to_string(),
827            GraphQLTokenKind::SquareBracketClose => "]".to_string(),
828            GraphQLTokenKind::SquareBracketOpen => "[".to_string(),
829            GraphQLTokenKind::Name(s) => s.to_string(),
830            GraphQLTokenKind::IntValue(s) => s.to_string(),
831            GraphQLTokenKind::FloatValue(s) => s.to_string(),
832            GraphQLTokenKind::StringValue(_) => "string".to_string(),
833            GraphQLTokenKind::True => "true".to_string(),
834            GraphQLTokenKind::False => "false".to_string(),
835            GraphQLTokenKind::Null => "null".to_string(),
836            GraphQLTokenKind::Eof => "end of input".to_string(),
837            GraphQLTokenKind::Error(err) => {
838                format!("tokenization error: {}", err.message)
839            }
840        }
841    }
842
843    /// Compares token kinds for equality, ignoring payload for variant
844    /// matching.
845    ///
846    /// # Structure Note
847    ///
848    /// This function intentionally uses an exhaustive match on `actual` rather
849    /// than a wildcard. This ensures that if a new `GraphQLTokenKind` variant
850    /// is added, the compiler will produce an exhaustive-matching error,
851    /// forcing us to explicitly handle the new variant. Do not refactor this
852    /// to use catch-all match cases.
853    fn token_kinds_match(
854        actual: &GraphQLTokenKind,
855        expected: &GraphQLTokenKind,
856    ) -> bool {
857        match actual {
858            // For payload-carrying variants, we just check the variant matches
859            // (not the payload) since we're checking "is this a Name?" not "is
860            // this the specific name 'foo'?"
861            GraphQLTokenKind::Name(_) => matches!(expected, GraphQLTokenKind::Name(_)),
862            GraphQLTokenKind::IntValue(_) => {
863                matches!(expected, GraphQLTokenKind::IntValue(_))
864            }
865            GraphQLTokenKind::FloatValue(_) => {
866                matches!(expected, GraphQLTokenKind::FloatValue(_))
867            }
868            GraphQLTokenKind::StringValue(_) => {
869                matches!(expected, GraphQLTokenKind::StringValue(_))
870            }
871            GraphQLTokenKind::Error(_) => {
872                matches!(expected, GraphQLTokenKind::Error(_))
873            }
874            // Unit variants - exhaustive to catch new variants at compile time
875            GraphQLTokenKind::Ampersand => actual == expected,
876            GraphQLTokenKind::At => actual == expected,
877            GraphQLTokenKind::Bang => actual == expected,
878            GraphQLTokenKind::Colon => actual == expected,
879            GraphQLTokenKind::CurlyBraceClose => actual == expected,
880            GraphQLTokenKind::CurlyBraceOpen => actual == expected,
881            GraphQLTokenKind::Dollar => actual == expected,
882            GraphQLTokenKind::Ellipsis => actual == expected,
883            GraphQLTokenKind::Equals => actual == expected,
884            GraphQLTokenKind::ParenClose => actual == expected,
885            GraphQLTokenKind::ParenOpen => actual == expected,
886            GraphQLTokenKind::Pipe => actual == expected,
887            GraphQLTokenKind::SquareBracketClose => actual == expected,
888            GraphQLTokenKind::SquareBracketOpen => actual == expected,
889            GraphQLTokenKind::True => actual == expected,
890            GraphQLTokenKind::False => actual == expected,
891            GraphQLTokenKind::Null => actual == expected,
892            GraphQLTokenKind::Eof => actual == expected,
893        }
894    }
895
896    /// Handles a lexer error token by converting it to a parse error.
897    fn handle_lexer_error(&mut self, token: &GraphQLToken<'src>) {
898        if let GraphQLTokenKind::Error(err) = &token.kind {
899            self.record_error(GraphQLParseError::from_lexer_error(
900                err.message.clone(),
901                err.error_notes.clone(),
902                self.resolve_span(token.span),
903            ));
904        }
905    }
906
907    // =========================================================================
908    // Value parsing
909    // =========================================================================
910
911    /// Checks recursion depth and returns an error if the limit is
912    /// exceeded. On success, increments the depth counter; the caller
913    /// must call `exit_recursion()` when done (use the wrapper pattern
914    /// to guarantee this).
915    fn enter_recursion(&mut self) -> Result<(), ()> {
916        self.recursion_depth += 1;
917        if self.recursion_depth > Self::MAX_RECURSION_DEPTH {
918            let span = self
919                .token_stream.peek()
920                .map(|t| t.span)
921                .unwrap_or_else(|| self.eof_span());
922            self.consume_token();
923            self.record_error(GraphQLParseError::new(
924                "maximum nesting depth exceeded",
925                GraphQLParseErrorKind::InvalidSyntax,
926                self.resolve_span(span),
927            ));
928            self.recursion_depth -= 1;
929            return Err(());
930        }
931        Ok(())
932    }
933
934    /// Decrements the recursion depth counter.
935    fn exit_recursion(&mut self) {
936        self.recursion_depth -= 1;
937    }
938
939    /// Parses a value (literal or variable reference).
940    ///
941    /// The `context` parameter specifies whether variables are allowed and
942    /// provides context for error messages when they're not.
943    fn parse_value(
944        &mut self,
945        context: ConstContext,
946    ) -> Result<ast::Value<'src>, ()> {
947        self.enter_recursion()?;
948        let result = self.parse_value_impl(context);
949        self.exit_recursion();
950        result
951    }
952
953    /// Inner implementation of value parsing.
954    fn parse_value_impl(
955        &mut self, context: ConstContext,
956    ) -> Result<ast::Value<'src>, ()> {
957        match self.token_stream.peek() {
958            None => {
959                let span = self.eof_span();
960                self.record_error(GraphQLParseError::new(
961                    "expected value",
962                    GraphQLParseErrorKind::UnexpectedEof {
963                        expected: vec![
964                            "value".to_string(),
965                        ],
966                    },
967                    self.resolve_span(span),
968                ));
969                Err(())
970            },
971            Some(token) => {
972                let span = token.span;
973                match &token.kind {
974                    // Variable reference: $name
975                    GraphQLTokenKind::Dollar => {
976                        if !matches!(context, ConstContext::AllowVariables) {
977                            self.consume_token();
978                            self.record_error(GraphQLParseError::new(
979                                format!("variables are not allowed in {}", context.description()),
980                                GraphQLParseErrorKind::InvalidSyntax,
981                                self.resolve_span(span),
982                            ));
983                            return Err(());
984                        }
985                        let dollar = self.consume_token().unwrap();
986                        let name = self.expect_ast_name()?;
987                        if self.config.retain_syntax {
988                            let var_span = self.make_span_ref(&dollar.span);
989                            Ok(ast::Value::Variable(ast::VariableReference {
990                                name, span: var_span,
991                                syntax: Some(Box::new(ast::VariableReferenceSyntax { dollar })),
992                            }))
993                        } else {
994                            let var_span = self.make_span(dollar.span);
995                            Ok(ast::Value::Variable(ast::VariableReference {
996                                name, span: var_span, syntax: None,
997                            }))
998                        }
999                    },
1000
1001                    // Integer literal
1002                    GraphQLTokenKind::IntValue(raw) => {
1003                        let parse_result =
1004                            token.kind.parse_int_value();
1005                        match parse_result {
1006                            Some(Ok(val)) => {
1007                                if val > i32::MAX as i64
1008                                    || val
1009                                        < i32::MIN
1010                                            as i64
1011                                {
1012                                    let raw_str = raw
1013                                        .clone()
1014                                        .into_owned();
1015                                    self.consume_token();
1016                                    self.record_error(
1017                                        GraphQLParseError::new(
1018                                            format!(
1019                                                "integer \
1020                                                `{raw_str}` \
1021                                                overflows \
1022                                                32-bit \
1023                                                integer",
1024                                            ),
1025                                            GraphQLParseErrorKind::InvalidValue(
1026                                                ValueParsingError::Int(
1027                                                    raw_str,
1028                                                ),
1029                                            ),
1030                                            self.resolve_span(span),
1031                                        ),
1032                                    );
1033                                    Err(())
1034                                } else {
1035                                    let token = self.consume_token().unwrap();
1036                                    if self.config.retain_syntax {
1037                                        let span = token.span;
1038                                        Ok(ast::Value::Int(ast::IntValue {
1039                                            span,
1040                                            syntax: Some(Box::new(ast::IntValueSyntax { token })),
1041                                            value: val as i32,
1042                                        }))
1043                                    } else {
1044                                        Ok(ast::Value::Int(ast::IntValue {
1045                                            span: token.span,
1046                                            syntax: None,
1047                                            value: val as i32,
1048                                        }))
1049                                    }
1050                                }
1051                            },
1052                            Some(Err(_)) => {
1053                                let raw_str = raw
1054                                    .clone()
1055                                    .into_owned();
1056                                self.consume_token();
1057                                self.record_error(
1058                                    GraphQLParseError::new(
1059                                        format!(
1060                                            "invalid \
1061                                            integer \
1062                                            `{raw_str}`",
1063                                        ),
1064                                        GraphQLParseErrorKind::InvalidValue(
1065                                            ValueParsingError::Int(
1066                                                raw_str,
1067                                            ),
1068                                        ),
1069                                        self.resolve_span(span),
1070                                    ),
1071                                );
1072                                Err(())
1073                            },
1074                            None => unreachable!(
1075                                "parse_int_value on \
1076                                IntValue token",
1077                            ),
1078                        }
1079                    },
1080
1081                    // Float literal
1082                    GraphQLTokenKind::FloatValue(
1083                        raw,
1084                    ) => {
1085                        let parse_result =
1086                            token.kind
1087                                .parse_float_value();
1088                        match parse_result {
1089                            Some(Ok(val)) => {
1090                                if val.is_infinite()
1091                                    || val.is_nan()
1092                                {
1093                                    let raw_str = raw
1094                                        .clone()
1095                                        .into_owned();
1096                                    self.consume_token();
1097                                    self.record_error(
1098                                        GraphQLParseError::new(
1099                                            format!(
1100                                                "float \
1101                                                `{raw_str}` \
1102                                                is not a \
1103                                                finite \
1104                                                number",
1105                                            ),
1106                                            GraphQLParseErrorKind::InvalidValue(
1107                                                ValueParsingError::Float(
1108                                                    raw_str,
1109                                                ),
1110                                            ),
1111                                            self.resolve_span(span),
1112                                        ),
1113                                    );
1114                                    Err(())
1115                                } else {
1116                                    let token = self.consume_token().unwrap();
1117                                    if self.config.retain_syntax {
1118                                        let span = token.span;
1119                                        Ok(ast::Value::Float(ast::FloatValue {
1120                                            span,
1121                                            syntax: Some(Box::new(
1122                                                ast::FloatValueSyntax { token },
1123                                            )),
1124                                            value: val,
1125                                        }))
1126                                    } else {
1127                                        Ok(ast::Value::Float(ast::FloatValue {
1128                                            span: token.span,
1129                                            syntax: None,
1130                                            value: val,
1131                                        }))
1132                                    }
1133                                }
1134                            },
1135                            Some(Err(_)) => {
1136                                let raw_str = raw
1137                                    .clone()
1138                                    .into_owned();
1139                                self.consume_token();
1140                                self.record_error(
1141                                    GraphQLParseError::new(
1142                                        format!(
1143                                            "invalid \
1144                                            float \
1145                                            `{raw_str}`",
1146                                        ),
1147                                        GraphQLParseErrorKind::InvalidValue(
1148                                            ValueParsingError::Float(
1149                                                raw_str,
1150                                            ),
1151                                        ),
1152                                        self.resolve_span(span),
1153                                    ),
1154                                );
1155                                Err(())
1156                            },
1157                            None => unreachable!(
1158                                "parse_float_value \
1159                                on FloatValue token",
1160                            ),
1161                        }
1162                    },
1163
1164                    // String literal
1165                    GraphQLTokenKind::StringValue(raw) => {
1166                        let is_block = raw.starts_with("\"\"\"");
1167                        let parse_result = token.kind.parse_string_value();
1168                        let consumed = self.consume_token().unwrap();
1169                        match parse_result {
1170                            Some(Ok(parsed)) => {
1171                                if self.config.retain_syntax {
1172                                    let s = consumed.span;
1173                                    Ok(ast::Value::String(ast::StringValue {
1174                                        is_block, span: s,
1175                                        syntax: Some(Box::new(
1176                                            ast::StringValueSyntax { token: consumed },
1177                                        )),
1178                                        value: Cow::Owned(parsed),
1179                                    }))
1180                                } else {
1181                                    Ok(ast::Value::String(ast::StringValue {
1182                                        is_block, span: consumed.span,
1183                                        syntax: None, value: Cow::Owned(parsed),
1184                                    }))
1185                                }
1186                            },
1187                            Some(Err(e)) => {
1188                                self.record_error(GraphQLParseError::new(
1189                                    format!("invalid string: {e}"),
1190                                    GraphQLParseErrorKind::InvalidValue(
1191                                        ValueParsingError::String(e),
1192                                    ),
1193                                    self.resolve_span(span),
1194                                ));
1195                                Err(())
1196                            },
1197                            None => {
1198                                self.record_error(GraphQLParseError::new(
1199                                    "invalid string", GraphQLParseErrorKind::InvalidSyntax,
1200                                    self.resolve_span(span),
1201                                ));
1202                                Err(())
1203                            },
1204                        }
1205                    },
1206
1207                    // Boolean literals
1208                    GraphQLTokenKind::True => {
1209                        let token = self.consume_token().unwrap();
1210                        if self.config.retain_syntax {
1211                            let span = token.span;
1212                            Ok(ast::Value::Boolean(ast::BooleanValue {
1213                                span,
1214                                syntax: Some(Box::new(
1215                                    ast::BooleanValueSyntax { token },
1216                                )),
1217                                value: true,
1218                            }))
1219                        } else {
1220                            Ok(ast::Value::Boolean(ast::BooleanValue {
1221                                span: token.span, syntax: None, value: true,
1222                            }))
1223                        }
1224                    },
1225                    GraphQLTokenKind::False => {
1226                        let token = self.consume_token().unwrap();
1227                        if self.config.retain_syntax {
1228                            let span = token.span;
1229                            Ok(ast::Value::Boolean(ast::BooleanValue {
1230                                span,
1231                                syntax: Some(Box::new(
1232                                    ast::BooleanValueSyntax { token },
1233                                )),
1234                                value: false,
1235                            }))
1236                        } else {
1237                            Ok(ast::Value::Boolean(ast::BooleanValue {
1238                                span: token.span, syntax: None, value: false,
1239                            }))
1240                        }
1241                    },
1242
1243                    // Null literal
1244                    GraphQLTokenKind::Null => {
1245                        let token = self.consume_token().unwrap();
1246                        if self.config.retain_syntax {
1247                            let span = token.span;
1248                            Ok(ast::Value::Null(ast::NullValue {
1249                                span,
1250                                syntax: Some(Box::new(ast::NullValueSyntax { token })),
1251                            }))
1252                        } else {
1253                            Ok(ast::Value::Null(ast::NullValue {
1254                                span: token.span, syntax: None,
1255                            }))
1256                        }
1257                    },
1258
1259                    // List literal: [value, ...]
1260                    GraphQLTokenKind::SquareBracketOpen => {
1261                        self.parse_list_value(context)
1262                    },
1263
1264                    // Object literal: {field: value, ...}
1265                    GraphQLTokenKind::CurlyBraceOpen => {
1266                        self.parse_object_value(context)
1267                    },
1268
1269                    // Enum value (any other name)
1270                    GraphQLTokenKind::Name(_) => {
1271                        let token = self.consume_token().unwrap();
1272                        if self.config.retain_syntax {
1273                            let value = match &token.kind {
1274                                GraphQLTokenKind::Name(s) => s.clone(),
1275                                _ => unreachable!(),
1276                            };
1277                            let span = token.span;
1278                            Ok(ast::Value::Enum(ast::EnumValue {
1279                                span,
1280                                syntax: Some(Box::new(ast::EnumValueSyntax { token })),
1281                                value,
1282                            }))
1283                        } else {
1284                            let value = match token.kind {
1285                                GraphQLTokenKind::Name(s) => s,
1286                                _ => unreachable!(),
1287                            };
1288                            Ok(ast::Value::Enum(ast::EnumValue {
1289                                span: token.span, syntax: None, value,
1290                            }))
1291                        }
1292                    },
1293
1294                    // Lexer error
1295                    GraphQLTokenKind::Error(_) => {
1296                        let token = token.clone();
1297                        self.handle_lexer_error(
1298                            &token,
1299                        );
1300                        self.consume_token();
1301                        Err(())
1302                    },
1303
1304                    // Unexpected token
1305                    _ => {
1306                        let found =
1307                            Self::token_kind_display(
1308                                &token.kind,
1309                            );
1310                        self.record_error(
1311                            GraphQLParseError::new(
1312                                format!(
1313                                    "expected value, \
1314                                    found `{found}`",
1315                                ),
1316                                GraphQLParseErrorKind::UnexpectedToken {
1317                                    expected: vec![
1318                                        "value"
1319                                            .to_string(),
1320                                    ],
1321                                    found,
1322                                },
1323                                self.resolve_span(span),
1324                            ),
1325                        );
1326                        Err(())
1327                    },
1328                }
1329            },
1330        }
1331    }
1332
1333    /// Parses a list value: `[value, value, ...]`
1334    fn parse_list_value(
1335        &mut self, context: ConstContext,
1336    ) -> Result<ast::Value<'src>, ()> {
1337        let open_token = self.expect(&GraphQLTokenKind::SquareBracketOpen)?;
1338        self.push_delimiter(open_token.span, DelimiterContext::ListValue);
1339        let mut values = Vec::new();
1340        loop {
1341            if self.peek_is(&GraphQLTokenKind::SquareBracketClose) {
1342                break;
1343            }
1344            if self.token_stream.is_at_end() {
1345                let span = self.eof_span();
1346                let open_delim = self.pop_delimiter();
1347                let mut error = GraphQLParseError::new(
1348                    "unclosed `[`",
1349                    GraphQLParseErrorKind::UnclosedDelimiter {
1350                        delimiter: "[".to_string(),
1351                    },
1352                    self.resolve_span(span),
1353                );
1354                if let Some(delim) = open_delim {
1355                    error.add_note_with_span("opening `[` here", self.resolve_span(delim.span));
1356                }
1357                self.record_error(error);
1358                return Err(());
1359            }
1360            match self.parse_value(context) {
1361                Ok(value) => values.push(value),
1362                Err(()) => {
1363                    self.skip_to_list_recovery_point();
1364                    if self.peek_is(&GraphQLTokenKind::SquareBracketClose) {
1365                        break;
1366                    }
1367                },
1368            }
1369        }
1370        let close_token = self.expect(&GraphQLTokenKind::SquareBracketClose)?;
1371        self.pop_delimiter();
1372        if self.config.retain_syntax {
1373            let span = self.make_span_ref(&open_token.span);
1374            Ok(ast::Value::List(ast::ListValue {
1375                span,
1376                syntax: Some(Box::new(ast::ListValueSyntax {
1377                    brackets: ast::DelimiterPair { close: close_token, open: open_token },
1378                })),
1379                values,
1380            }))
1381        } else {
1382            let span = self.make_span(open_token.span);
1383            Ok(ast::Value::List(ast::ListValue { span, syntax: None, values }))
1384        }
1385    }
1386
1387    /// Parses an object value: `{ field: value, ... }`
1388    fn parse_object_value(
1389        &mut self, context: ConstContext,
1390    ) -> Result<ast::Value<'src>, ()> {
1391        let open_token = self.expect(&GraphQLTokenKind::CurlyBraceOpen)?;
1392        self.push_delimiter(open_token.span, DelimiterContext::ObjectValue);
1393        let mut fields = Vec::new();
1394        loop {
1395            if self.peek_is(&GraphQLTokenKind::CurlyBraceClose) {
1396                break;
1397            }
1398            if self.token_stream.is_at_end() {
1399                let span = self.eof_span();
1400                let open_delim = self.pop_delimiter();
1401                let mut error = GraphQLParseError::new(
1402                    "unclosed `{`",
1403                    GraphQLParseErrorKind::UnclosedDelimiter {
1404                        delimiter: "{".to_string(),
1405                    },
1406                    self.resolve_span(span),
1407                );
1408                if let Some(delim) = open_delim {
1409                    error.add_note_with_span(
1410                        format!("opening `{{` in {} here", delim.context.description()),
1411                        self.resolve_span(delim.span),
1412                    );
1413                }
1414                self.record_error(error);
1415                return Err(());
1416            }
1417            let field_name = self.expect_ast_name()?;
1418            let colon_token = self.expect(&GraphQLTokenKind::Colon)?;
1419            let value = self.parse_value(context)?;
1420            let field_span = ByteSpan::new(
1421                field_name.span.start,
1422                self.last_end_position
1423                    .unwrap_or(field_name.span.end),
1424            );
1425            let field_syntax = if self.config.retain_syntax {
1426                Some(Box::new(ast::ObjectFieldSyntax { colon: colon_token }))
1427            } else {
1428                None
1429            };
1430            fields.push(ast::ObjectField {
1431                name: field_name, span: field_span, syntax: field_syntax, value,
1432            });
1433        }
1434        let close_token = self.expect(&GraphQLTokenKind::CurlyBraceClose)?;
1435        self.pop_delimiter();
1436        if self.config.retain_syntax {
1437            let span = self.make_span_ref(&open_token.span);
1438            Ok(ast::Value::Object(ast::ObjectValue {
1439                fields, span,
1440                syntax: Some(Box::new(ast::ObjectValueSyntax {
1441                    braces: ast::DelimiterPair { close: close_token, open: open_token },
1442                })),
1443            }))
1444        } else {
1445            let span = self.make_span(open_token.span);
1446            Ok(ast::Value::Object(ast::ObjectValue { fields, span, syntax: None }))
1447        }
1448    }
1449
1450    /// Skip tokens to find a recovery point within a list value.
1451    ///
1452    /// # Structure Note
1453    ///
1454    /// This function intentionally uses an exhaustive match rather than a
1455    /// wildcard. This ensures that if a new `GraphQLTokenKind` variant is
1456    /// added, the compiler will produce an exhaustive-matching error, forcing
1457    /// us to explicitly decide whether the new variant is a recovery point.
1458    /// Do not refactor this to use catch-all match cases.
1459    fn skip_to_list_recovery_point(&mut self) {
1460        loop {
1461            match self.token_stream.peek() {
1462                None => break,
1463                Some(token) => match &token.kind {
1464                    // End of list or input - stop
1465                    GraphQLTokenKind::SquareBracketClose | GraphQLTokenKind::Eof => break,
1466                    // Value starters - potential recovery point
1467                    GraphQLTokenKind::Dollar
1468                    | GraphQLTokenKind::IntValue(_)
1469                    | GraphQLTokenKind::FloatValue(_)
1470                    | GraphQLTokenKind::StringValue(_)
1471                    | GraphQLTokenKind::True
1472                    | GraphQLTokenKind::False
1473                    | GraphQLTokenKind::Null
1474                    | GraphQLTokenKind::SquareBracketOpen
1475                    | GraphQLTokenKind::CurlyBraceOpen
1476                    | GraphQLTokenKind::Name(_) => break,
1477                    // Skip these tokens (not valid value starters)
1478                    GraphQLTokenKind::Ampersand
1479                    | GraphQLTokenKind::At
1480                    | GraphQLTokenKind::Bang
1481                    | GraphQLTokenKind::Colon
1482                    | GraphQLTokenKind::CurlyBraceClose
1483                    | GraphQLTokenKind::Ellipsis
1484                    | GraphQLTokenKind::Equals
1485                    | GraphQLTokenKind::ParenClose
1486                    | GraphQLTokenKind::ParenOpen
1487                    | GraphQLTokenKind::Pipe
1488                    | GraphQLTokenKind::Error(_) => {
1489                        self.consume_token();
1490                    }
1491                },
1492            }
1493        }
1494    }
1495
1496    // =========================================================================
1497    // Type annotation parsing
1498    // =========================================================================
1499
1500    /// Parses a type annotation: `TypeName`, `[Type]`, `Type!`, `[Type]!`, etc.
1501    fn parse_type_annotation(&mut self) -> Result<ast::TypeAnnotation<'src>, ()> {
1502        self.enter_recursion()?;
1503        let result = self.parse_type_annotation_impl();
1504        self.exit_recursion();
1505        result
1506    }
1507
1508    /// Inner implementation of type annotation parsing.
1509    fn parse_type_annotation_impl(&mut self) -> Result<ast::TypeAnnotation<'src>, ()> {
1510        if self.peek_is(&GraphQLTokenKind::SquareBracketOpen) {
1511            self.parse_list_type_annotation()
1512        } else {
1513            self.parse_named_type_annotation()
1514        }
1515    }
1516
1517    /// Parses a named type annotation: `TypeName` or `TypeName!`
1518    fn parse_named_type_annotation(&mut self) -> Result<ast::TypeAnnotation<'src>, ()> {
1519        let name = self.expect_ast_name()?;
1520        let (nullability, span_end) = if self.peek_is(&GraphQLTokenKind::Bang) {
1521            let bang = self.consume_token().unwrap();
1522            let end = bang.span.end;
1523            let syntax = if self.config.retain_syntax { Some(bang) } else { None };
1524            (ast::Nullability::NonNull { syntax }, end)
1525        } else {
1526            (ast::Nullability::Nullable, name.span.end)
1527        };
1528        let span = ByteSpan::new(name.span.start, span_end);
1529        Ok(ast::TypeAnnotation::Named(
1530            ast::NamedTypeAnnotation { name, nullability, span },
1531        ))
1532    }
1533
1534    /// Parses a list type annotation: `[InnerType]` or `[InnerType]!`
1535    fn parse_list_type_annotation(&mut self) -> Result<ast::TypeAnnotation<'src>, ()> {
1536        let open_token = self.expect(&GraphQLTokenKind::SquareBracketOpen)?;
1537        self.push_delimiter(open_token.span, DelimiterContext::ListType);
1538        let element_type = Box::new(self.parse_type_annotation()?);
1539        let close_token = self.expect(&GraphQLTokenKind::SquareBracketClose)?;
1540        self.pop_delimiter();
1541        let (nullability, span_end) = if self.peek_is(&GraphQLTokenKind::Bang) {
1542            let bang = self.consume_token().unwrap();
1543            let end = bang.span.end;
1544            let syntax = if self.config.retain_syntax { Some(bang) } else { None };
1545            (ast::Nullability::NonNull { syntax }, end)
1546        } else {
1547            let end = self.last_end_position
1548                .unwrap_or(open_token.span.start);
1549            (ast::Nullability::Nullable, end)
1550        };
1551        if self.config.retain_syntax {
1552            let span = ByteSpan::new(
1553                open_token.span.start, span_end,
1554            );
1555            Ok(ast::TypeAnnotation::List(ast::ListTypeAnnotation {
1556                element_type, nullability, span,
1557                syntax: Some(Box::new(ast::ListTypeAnnotationSyntax {
1558                    brackets: ast::DelimiterPair { close: close_token, open: open_token },
1559                })),
1560            }))
1561        } else {
1562            let span = ByteSpan::new(open_token.span.start, span_end);
1563            Ok(ast::TypeAnnotation::List(
1564                ast::ListTypeAnnotation { element_type, nullability, span, syntax: None },
1565            ))
1566        }
1567    }
1568
1569    // =========================================================================
1570    // Directive annotation parsing
1571    // =========================================================================
1572
1573    /// Parses zero or more directive annotations: `@directive(args)...`
1574    fn parse_directive_annotations(
1575        &mut self,
1576    ) -> Result<Vec<ast::DirectiveAnnotation<'src>>, ()> {
1577        let mut directives = Vec::new();
1578        while self.peek_is(&GraphQLTokenKind::At) {
1579            directives.push(self.parse_directive_annotation()?);
1580        }
1581        Ok(directives)
1582    }
1583
1584    /// Parses a single directive annotation: `@name` or `@name(args)`
1585    fn parse_directive_annotation(&mut self) -> Result<ast::DirectiveAnnotation<'src>, ()> {
1586        let at_token = self.expect(&GraphQLTokenKind::At)?;
1587        let name = self.expect_ast_name()?;
1588        let (arguments, argument_delimiters) = if self.peek_is(&GraphQLTokenKind::ParenOpen) {
1589            self.parse_ast_arguments(
1590                DelimiterContext::DirectiveArguments,
1591                ConstContext::AllowVariables,
1592            )?
1593        } else {
1594            (Vec::new(), None)
1595        };
1596        if self.config.retain_syntax {
1597            let span = self.make_span_ref(&at_token.span);
1598            Ok(ast::DirectiveAnnotation {
1599                arguments, name, span,
1600                syntax: Some(Box::new(ast::DirectiveAnnotationSyntax {
1601                    argument_parens: argument_delimiters, at_sign: at_token,
1602                })),
1603            })
1604        } else {
1605            let span = self.make_span(at_token.span);
1606            Ok(ast::DirectiveAnnotation { arguments, name, span, syntax: None })
1607        }
1608    }
1609
1610    /// Parses directive annotations in const contexts (arguments must be const values).
1611    fn parse_const_directive_annotations(
1612        &mut self,
1613    ) -> Result<Vec<ast::DirectiveAnnotation<'src>>, ()> {
1614        let mut directives = Vec::new();
1615        while self.peek_is(&GraphQLTokenKind::At) {
1616            directives.push(self.parse_const_directive_annotation()?);
1617        }
1618        Ok(directives)
1619    }
1620
1621    /// Parses a directive annotation with const-only arguments.
1622    fn parse_const_directive_annotation(&mut self) -> Result<ast::DirectiveAnnotation<'src>, ()> {
1623        let at_token = self.expect(&GraphQLTokenKind::At)?;
1624        let name = self.expect_ast_name()?;
1625        let (arguments, argument_delimiters) = if self.peek_is(&GraphQLTokenKind::ParenOpen) {
1626            self.parse_ast_arguments(
1627                DelimiterContext::DirectiveArguments,
1628                ConstContext::DirectiveArgument,
1629            )?
1630        } else {
1631            (Vec::new(), None)
1632        };
1633        if self.config.retain_syntax {
1634            let span = self.make_span_ref(&at_token.span);
1635            Ok(ast::DirectiveAnnotation {
1636                arguments, name, span,
1637                syntax: Some(Box::new(ast::DirectiveAnnotationSyntax {
1638                    argument_parens: argument_delimiters, at_sign: at_token,
1639                })),
1640            })
1641        } else {
1642            let span = self.make_span(at_token.span);
1643            Ok(ast::DirectiveAnnotation { arguments, name, span, syntax: None })
1644        }
1645    }
1646
1647    // =========================================================
1648    // Arguments parsing
1649    // =========================================================
1650
1651    /// Parses arguments: `(name: value, ...)`
1652    fn parse_ast_arguments(
1653        &mut self, delim_context: DelimiterContext, const_context: ConstContext,
1654    ) -> Result<(Vec<ast::Argument<'src>>, Option<ast::DelimiterPair<'src>>), ()> {
1655        let open_token = self.expect(&GraphQLTokenKind::ParenOpen)?;
1656        self.push_delimiter(open_token.span, delim_context);
1657        let mut arguments = Vec::new();
1658        if self.peek_is(&GraphQLTokenKind::ParenClose) {
1659            let span = open_token.span;
1660            self.record_error(GraphQLParseError::new(
1661                "argument list cannot be empty; omit the parentheses instead",
1662                GraphQLParseErrorKind::InvalidEmptyConstruct {
1663                    construct: "argument list".to_string(),
1664                },
1665                self.resolve_span(span),
1666            ));
1667        }
1668        loop {
1669            if self.peek_is(&GraphQLTokenKind::ParenClose) {
1670                break;
1671            }
1672            if self.token_stream.is_at_end() {
1673                self.handle_unclosed_paren();
1674                return Err(());
1675            }
1676            let arg_name = self.expect_ast_name()?;
1677            let colon_token = self.expect(&GraphQLTokenKind::Colon)?;
1678            let value = self.parse_value(const_context)?;
1679            let span = ByteSpan::new(
1680                arg_name.span.start,
1681                self.last_end_position
1682                    .unwrap_or(arg_name.span.end),
1683            );
1684            let syntax = if self.config.retain_syntax {
1685                Some(Box::new(ast::ArgumentSyntax { colon: colon_token }))
1686            } else {
1687                None
1688            };
1689            arguments.push(ast::Argument {
1690                name: arg_name, span, syntax, value,
1691            });
1692        }
1693        let close_token = self.expect(&GraphQLTokenKind::ParenClose)?;
1694        self.pop_delimiter();
1695        let delimiters = if self.config.retain_syntax {
1696            Some(ast::DelimiterPair { close: close_token, open: open_token })
1697        } else {
1698            None
1699        };
1700        Ok((arguments, delimiters))
1701    }
1702
1703    /// Helper for unclosed parenthesis errors.
1704    fn handle_unclosed_paren(&mut self) {
1705        let span = self.eof_span();
1706        let open_delim = self.pop_delimiter();
1707        let mut error = GraphQLParseError::new(
1708            "unclosed `(`",
1709            GraphQLParseErrorKind::UnclosedDelimiter {
1710                delimiter: "(".to_string(),
1711            },
1712            self.resolve_span(span),
1713        );
1714        if let Some(delim) = open_delim {
1715            error.add_note_with_span(
1716                format!("opening `(` in {} here", delim.context.description()),
1717                self.resolve_span(delim.span),
1718            );
1719        }
1720        self.record_error(error);
1721    }
1722
1723    // =========================================================================
1724    // Selection set parsing
1725    // =========================================================================
1726
1727    /// Parses a selection set: `{ selection... }`
1728    fn parse_selection_set(&mut self) -> Result<ast::SelectionSet<'src>, ()> {
1729        self.enter_recursion()?;
1730        let result = self.parse_selection_set_impl();
1731        self.exit_recursion();
1732        result
1733    }
1734
1735    /// Inner implementation of selection set parsing.
1736    fn parse_selection_set_impl(&mut self) -> Result<ast::SelectionSet<'src>, ()> {
1737        let open_token = self.expect(&GraphQLTokenKind::CurlyBraceOpen)?;
1738        self.push_delimiter(open_token.span, DelimiterContext::SelectionSet);
1739        let mut selections = Vec::new();
1740        if self.peek_is(&GraphQLTokenKind::CurlyBraceClose) {
1741            let span = open_token.span;
1742            self.record_error(GraphQLParseError::new(
1743                "selection set cannot be empty",
1744                GraphQLParseErrorKind::InvalidEmptyConstruct {
1745                    construct: "selection set".to_string(),
1746                },
1747                self.resolve_span(span),
1748            ));
1749        }
1750        loop {
1751            if self.peek_is(&GraphQLTokenKind::CurlyBraceClose) {
1752                break;
1753            }
1754            if self.token_stream.is_at_end() {
1755                self.handle_unclosed_brace();
1756                return Err(());
1757            }
1758            match self.parse_selection() {
1759                Ok(sel) => selections.push(sel),
1760                Err(()) => {
1761                    self.skip_to_selection_recovery_point();
1762                },
1763            }
1764        }
1765        let close_token = self.expect(&GraphQLTokenKind::CurlyBraceClose)?;
1766        self.pop_delimiter();
1767        if self.config.retain_syntax {
1768            let span = self.make_span_ref(&open_token.span);
1769            Ok(ast::SelectionSet {
1770                selections, span,
1771                syntax: Some(Box::new(ast::SelectionSetSyntax {
1772                    braces: ast::DelimiterPair { close: close_token, open: open_token },
1773                })),
1774            })
1775        } else {
1776            let span = self.make_span(open_token.span);
1777            Ok(ast::SelectionSet { selections, span, syntax: None })
1778        }
1779    }
1780
1781    /// Parses a single selection (field, fragment spread, or inline fragment).
1782    fn parse_selection(&mut self) -> Result<ast::Selection<'src>, ()> {
1783        if self.peek_is(&GraphQLTokenKind::Ellipsis) {
1784            let ellipsis_token = self.expect(&GraphQLTokenKind::Ellipsis)?;
1785            if self.peek_is_keyword("on")
1786                || self.peek_is(&GraphQLTokenKind::At)
1787                || self.peek_is(&GraphQLTokenKind::CurlyBraceOpen)
1788            {
1789                self.parse_inline_fragment(ellipsis_token)
1790            } else {
1791                self.parse_fragment_spread(ellipsis_token)
1792            }
1793        } else {
1794            self.parse_field().map(ast::Selection::Field)
1795        }
1796    }
1797
1798    /// Parses a field: `alias: name(args) @directives { selections }`
1799    fn parse_field(&mut self) -> Result<ast::FieldSelection<'src>, ()> {
1800        let first_name = self.expect_ast_name()?;
1801        let (alias, alias_colon, name) = if self.peek_is(&GraphQLTokenKind::Colon) {
1802            let colon_token = self.consume_token().unwrap();
1803            let field_name = self.expect_ast_name()?;
1804            (Some(first_name), Some(colon_token), field_name)
1805        } else {
1806            (None, None, first_name)
1807        };
1808        let (arguments, argument_delimiters) = if self.peek_is(&GraphQLTokenKind::ParenOpen) {
1809            self.parse_ast_arguments(
1810                DelimiterContext::FieldArguments,
1811                ConstContext::AllowVariables,
1812            )?
1813        } else {
1814            (Vec::new(), None)
1815        };
1816        let directives = self.parse_directive_annotations()?;
1817        let selection_set = if self.peek_is(&GraphQLTokenKind::CurlyBraceOpen) {
1818            Some(self.parse_selection_set()?)
1819        } else {
1820            None
1821        };
1822        let start = match &alias {
1823            Some(a) => a.span.start,
1824            None => name.span.start,
1825        };
1826        let end = self.last_end_position
1827            .unwrap_or(name.span.end);
1828        let span = ByteSpan::new(start, end);
1829        let syntax = if self.config.retain_syntax {
1830            Some(Box::new(ast::FieldSelectionSyntax {
1831                alias_colon,
1832                argument_parens: argument_delimiters,
1833            }))
1834        } else {
1835            None
1836        };
1837        Ok(ast::FieldSelection { alias, arguments, directives, name, selection_set, span, syntax })
1838    }
1839
1840    /// Parses a fragment spread: `...FragmentName @directives` (called after consuming `...`)
1841    fn parse_fragment_spread(
1842        &mut self, ellipsis_token: GraphQLToken<'src>,
1843    ) -> Result<ast::Selection<'src>, ()> {
1844        let name = self.expect_ast_name()?;
1845        let directives = self.parse_directive_annotations()?;
1846        if self.config.retain_syntax {
1847            let span = self.make_span_ref(&ellipsis_token.span);
1848            Ok(ast::Selection::FragmentSpread(ast::FragmentSpread {
1849                directives, name, span,
1850                syntax: Some(Box::new(ast::FragmentSpreadSyntax {
1851                    ellipsis: ellipsis_token,
1852                })),
1853            }))
1854        } else {
1855            let span = self.make_span(ellipsis_token.span);
1856            Ok(ast::Selection::FragmentSpread(
1857                ast::FragmentSpread { directives, name, span, syntax: None },
1858            ))
1859        }
1860    }
1861
1862    /// Parses an inline fragment: `... on Type @directives { sel }` or `... @directives { sel }`
1863    /// (called after consuming `...`)
1864    fn parse_inline_fragment(
1865        &mut self, ellipsis_token: GraphQLToken<'src>,
1866    ) -> Result<ast::Selection<'src>, ()> {
1867        let type_condition = if self.peek_is_keyword("on") {
1868            Some(self.parse_type_condition()?)
1869        } else {
1870            None
1871        };
1872        let directives = self.parse_directive_annotations()?;
1873        let selection_set = self.parse_selection_set()?;
1874        if self.config.retain_syntax {
1875            let span = self.make_span_ref(&ellipsis_token.span);
1876            Ok(ast::Selection::InlineFragment(ast::InlineFragment {
1877                directives, selection_set, span,
1878                syntax: Some(Box::new(ast::InlineFragmentSyntax {
1879                    ellipsis: ellipsis_token,
1880                })),
1881                type_condition,
1882            }))
1883        } else {
1884            let span = self.make_span(ellipsis_token.span);
1885            Ok(ast::Selection::InlineFragment(ast::InlineFragment {
1886                directives, selection_set, span, syntax: None, type_condition,
1887            }))
1888        }
1889    }
1890
1891    /// Skip tokens to find a recovery point within a selection set.
1892    fn skip_to_selection_recovery_point(&mut self) {
1893        loop {
1894            match self.token_stream.peek() {
1895                None => break,
1896                Some(token) => match &token.kind {
1897                    GraphQLTokenKind::CurlyBraceClose | GraphQLTokenKind::Eof => break,
1898                    // Selection starters
1899                    GraphQLTokenKind::Ellipsis | GraphQLTokenKind::Name(_) => break,
1900                    // Also treat true/false/null as potential field names
1901                    GraphQLTokenKind::True
1902                    | GraphQLTokenKind::False
1903                    | GraphQLTokenKind::Null => break,
1904                    _ => {
1905                        self.consume_token();
1906                    }
1907                },
1908            }
1909        }
1910    }
1911
1912    /// Helper for unclosed brace errors.
1913    fn handle_unclosed_brace(&mut self) {
1914        let span = self.eof_span();
1915        let open_delim = self.pop_delimiter();
1916        let mut error = GraphQLParseError::new(
1917            "unclosed `{`",
1918            GraphQLParseErrorKind::UnclosedDelimiter {
1919                delimiter: "{".to_string(),
1920            },
1921            self.resolve_span(span),
1922        );
1923        if let Some(delim) = open_delim {
1924            error.add_note_with_span(
1925                format!(
1926                    "opening `{{` in {} here",
1927                    delim.context.description()
1928                ),
1929                self.resolve_span(delim.span),
1930            );
1931        }
1932        self.record_error(error);
1933    }
1934
1935    // =========================================================================
1936    // Operation parsing
1937    // =========================================================================
1938
1939    /// Parses an operation definition.
1940    fn parse_operation_definition(&mut self) -> Result<ast::OperationDefinition<'src>, ()> {
1941        // Shorthand query: just a selection set with no keyword
1942        if self.peek_is(&GraphQLTokenKind::CurlyBraceOpen) {
1943            let selection_set = self.parse_selection_set()?;
1944            let span = selection_set.span;
1945            return Ok(ast::OperationDefinition {
1946                description: None, directives: Vec::new(), name: None,
1947                operation_kind: ast::OperationKind::Query, selection_set,
1948                shorthand: true, span, syntax: None,
1949                variable_definitions: Vec::new(),
1950            });
1951        }
1952
1953        // Parse operation type keyword
1954        let (op_kind, keyword_token) = if self.peek_is_keyword("query") {
1955            (ast::OperationKind::Query, self.expect_keyword("query")?)
1956        } else if self.peek_is_keyword("mutation") {
1957            (ast::OperationKind::Mutation, self.expect_keyword("mutation")?)
1958        } else if self.peek_is_keyword("subscription") {
1959            (ast::OperationKind::Subscription, self.expect_keyword("subscription")?)
1960        } else {
1961            let span = self
1962                .token_stream.peek()
1963                .map(|t| t.span)
1964                .unwrap_or_else(|| self.eof_span());
1965            let found = self
1966                .token_stream.peek()
1967                .map(|t| Self::token_kind_display(&t.kind))
1968                .unwrap_or_else(|| "end of input".to_string());
1969            self.record_error(GraphQLParseError::new(
1970                format!(
1971                    "expected operation type (`query`, `mutation`, or \
1972                    `subscription`), found `{found}`"
1973                ),
1974                GraphQLParseErrorKind::UnexpectedToken {
1975                    expected: vec![
1976                        "query".to_string(),
1977                        "mutation".to_string(),
1978                        "subscription".to_string(),
1979                    ],
1980                    found,
1981                },
1982                self.resolve_span(span),
1983            ));
1984            return Err(());
1985        };
1986
1987        // Optional operation name
1988        let name = if !self.peek_is(&GraphQLTokenKind::ParenOpen)
1989            && !self.peek_is(&GraphQLTokenKind::At)
1990            && !self.peek_is(&GraphQLTokenKind::CurlyBraceOpen)
1991        {
1992            if let Some(token) = self.token_stream.peek() {
1993                match &token.kind {
1994                    GraphQLTokenKind::Name(_) | GraphQLTokenKind::True
1995                    | GraphQLTokenKind::False | GraphQLTokenKind::Null => {
1996                        Some(self.expect_ast_name()?)
1997                    },
1998                    _ => None,
1999                }
2000            } else {
2001                None
2002            }
2003        } else {
2004            None
2005        };
2006
2007        // Optional variable definitions
2008        let (variable_definitions, variable_definition_delimiters) =
2009            if self.peek_is(&GraphQLTokenKind::ParenOpen) {
2010                self.parse_variable_definitions()?
2011            } else {
2012                (Vec::new(), None)
2013            };
2014
2015        // Directives and selection set
2016        let directives = self.parse_directive_annotations()?;
2017        let selection_set = self.parse_selection_set()?;
2018        if self.config.retain_syntax {
2019            let span = self.make_span_ref(&keyword_token.span);
2020            Ok(ast::OperationDefinition {
2021                description: None, directives, name, operation_kind: op_kind,
2022                selection_set, shorthand: false, span,
2023                syntax: Some(Box::new(ast::OperationDefinitionSyntax {
2024                    operation_keyword: Some(keyword_token),
2025                    variable_definition_parens: variable_definition_delimiters,
2026                })),
2027                variable_definitions,
2028            })
2029        } else {
2030            let span = self.make_span(keyword_token.span);
2031            Ok(ast::OperationDefinition {
2032                description: None, directives, name, operation_kind: op_kind,
2033                selection_set, shorthand: false, span, syntax: None,
2034                variable_definitions,
2035            })
2036        }
2037    }
2038
2039    /// Parses variable definitions: `($var: Type = default, ...)`
2040    fn parse_variable_definitions(
2041        &mut self,
2042    ) -> Result<(Vec<ast::VariableDefinition<'src>>, Option<ast::DelimiterPair<'src>>), ()> {
2043        let open_token = self.expect(&GraphQLTokenKind::ParenOpen)?;
2044        self.push_delimiter(
2045            open_token.span,
2046            DelimiterContext::VariableDefinitions,
2047        );
2048
2049        let mut definitions = Vec::new();
2050
2051        if self.peek_is(&GraphQLTokenKind::ParenClose) {
2052            let span = open_token.span;
2053            self.record_error(GraphQLParseError::new(
2054                "variable definitions cannot be empty; omit the parentheses \
2055                instead",
2056                GraphQLParseErrorKind::InvalidEmptyConstruct {
2057                    construct: "variable definitions".to_string(),
2058                },
2059                self.resolve_span(span),
2060            ));
2061        }
2062
2063        loop {
2064            if self.peek_is(&GraphQLTokenKind::ParenClose) {
2065                break;
2066            }
2067            if self.token_stream.is_at_end() {
2068                self.handle_unclosed_paren();
2069                return Err(());
2070            }
2071
2072            definitions.push(self.parse_variable_definition()?);
2073        }
2074
2075        let close_token = self.expect(&GraphQLTokenKind::ParenClose)?;
2076        self.pop_delimiter();
2077        let delimiters = if self.config.retain_syntax {
2078            Some(ast::DelimiterPair { close: close_token, open: open_token })
2079        } else {
2080            None
2081        };
2082        Ok((definitions, delimiters))
2083    }
2084
2085    /// Parses a single variable definition: `$name: Type = default @directives`
2086    fn parse_variable_definition(&mut self) -> Result<ast::VariableDefinition<'src>, ()> {
2087        let dollar_token = self.expect(&GraphQLTokenKind::Dollar)?;
2088        let variable = self.expect_ast_name()?;
2089        let colon_token = self.expect(&GraphQLTokenKind::Colon)?;
2090        let var_type = self.parse_type_annotation()?;
2091        let (default_value, equals_token) = if self.peek_is(&GraphQLTokenKind::Equals) {
2092            let eq = self.consume_token().unwrap();
2093            (Some(self.parse_value(ConstContext::VariableDefaultValue)?), Some(eq))
2094        } else {
2095            (None, None)
2096        };
2097        let directives = self.parse_const_directive_annotations()?;
2098        if self.config.retain_syntax {
2099            let span = self.make_span_ref(&dollar_token.span);
2100            Ok(ast::VariableDefinition {
2101                default_value, description: None, directives, span,
2102                syntax: Some(Box::new(ast::VariableDefinitionSyntax {
2103                    colon: colon_token, dollar: dollar_token, equals: equals_token,
2104                })),
2105                var_type, variable,
2106            })
2107        } else {
2108            let span = self.make_span(dollar_token.span);
2109            Ok(ast::VariableDefinition {
2110                default_value, description: None, directives, span,
2111                syntax: None, var_type, variable,
2112            })
2113        }
2114    }
2115
2116    // =========================================================================
2117    // Fragment parsing
2118    // =========================================================================
2119
2120    /// Parses a fragment definition: `fragment Name on Type @directives { ... }`
2121    fn parse_fragment_definition(&mut self) -> Result<ast::FragmentDefinition<'src>, ()> {
2122        let keyword_token = self.expect_keyword("fragment")?;
2123        let name = self.expect_ast_name()?;
2124        if name.value == "on" {
2125            let mut error = GraphQLParseError::new(
2126                "fragment name cannot be `on`",
2127                GraphQLParseErrorKind::ReservedName {
2128                    name: "on".to_string(), context: ReservedNameContext::FragmentName,
2129                },
2130                self.resolve_span(name.span),
2131            );
2132            error.add_spec(
2133                "https://spec.graphql.org/October2021/#sec-Fragment-Name-Uniqueness",
2134            );
2135            self.record_error(error);
2136        }
2137        let type_condition = self.parse_type_condition()?;
2138        let directives = self.parse_directive_annotations()?;
2139        let selection_set = self.parse_selection_set()?;
2140        if self.config.retain_syntax {
2141            let span = self.make_span_ref(&keyword_token.span);
2142            Ok(ast::FragmentDefinition {
2143                description: None, directives, name, selection_set, span,
2144                syntax: Some(Box::new(ast::FragmentDefinitionSyntax {
2145                    fragment_keyword: keyword_token,
2146                })),
2147                type_condition,
2148            })
2149        } else {
2150            let span = self.make_span(keyword_token.span);
2151            Ok(ast::FragmentDefinition {
2152                description: None, directives, name, selection_set, span,
2153                syntax: None, type_condition,
2154            })
2155        }
2156    }
2157
2158    /// Parses a type condition: `on TypeName`
2159    fn parse_type_condition(&mut self) -> Result<ast::TypeCondition<'src>, ()> {
2160        let on_token = self.expect_keyword("on")?;
2161        let named_type = self.expect_ast_name()?;
2162        if self.config.retain_syntax {
2163            let span = ByteSpan::new(
2164                on_token.span.start,
2165                named_type.span.end,
2166            );
2167            Ok(ast::TypeCondition {
2168                named_type, span,
2169                syntax: Some(Box::new(ast::TypeConditionSyntax {
2170                    on_keyword: on_token,
2171                })),
2172            })
2173        } else {
2174            let span = ByteSpan::new(
2175                on_token.span.start, named_type.span.end,
2176            );
2177            Ok(ast::TypeCondition { named_type, span, syntax: None })
2178        }
2179    }
2180
2181    // =========================================================================
2182    // Type definition parsing
2183    // =========================================================================
2184
2185    /// Parses an optional description, returning an
2186    /// `ast::StringValue` with the span moved from the
2187    /// consumed token.
2188    fn parse_ast_description(&mut self) -> Option<ast::StringValue<'src>> {
2189        if let Some(token) = self.token_stream.peek()
2190            && matches!(&token.kind, GraphQLTokenKind::StringValue(_)) {
2191            let is_block = match &token.kind {
2192                GraphQLTokenKind::StringValue(raw) => {
2193                    raw.starts_with("\"\"\"")
2194                },
2195                _ => false,
2196            };
2197            let token = self.consume_token().unwrap();
2198            match token.kind.parse_string_value() {
2199                Some(Ok(parsed)) => {
2200                    if self.config.retain_syntax {
2201                        let span = token.span;
2202                        return Some(ast::StringValue {
2203                            is_block,
2204                            span,
2205                            syntax: Some(Box::new(
2206                                ast::StringValueSyntax { token },
2207                            )),
2208                            value: Cow::Owned(parsed),
2209                        });
2210                    } else {
2211                        return Some(ast::StringValue {
2212                            is_block,
2213                            span: token.span,
2214                            syntax: None,
2215                            value: Cow::Owned(parsed),
2216                        });
2217                    }
2218                },
2219                Some(Err(err)) => {
2220                    self.record_error(GraphQLParseError::new(
2221                        format!("invalid string in description: {err}"),
2222                        GraphQLParseErrorKind::InvalidSyntax,
2223                        self.resolve_span(token.span),
2224                    ));
2225                },
2226                None => unreachable!(),
2227            }
2228        }
2229        None
2230    }
2231
2232    /// Parses a schema definition: `schema @directives { query: Query, ... }`
2233    fn parse_schema_definition(
2234        &mut self,
2235        description: Option<ast::StringValue<'src>>,
2236    ) -> Result<ast::SchemaDefinition<'src>, ()> {
2237        let keyword_token = self.expect_keyword("schema")?;
2238        let directives = self.parse_const_directive_annotations()?;
2239        let open_token = self.expect(&GraphQLTokenKind::CurlyBraceOpen)?;
2240        self.push_delimiter(open_token.span, DelimiterContext::SchemaDefinition);
2241        let mut root_operations = Vec::new();
2242        loop {
2243            if self.peek_is(&GraphQLTokenKind::CurlyBraceClose) {
2244                break;
2245            }
2246            if self.token_stream.is_at_end() {
2247                self.handle_unclosed_brace();
2248                return Err(());
2249            }
2250            let op_name = self.expect_ast_name()?;
2251            let op_kind = match &*op_name.value {
2252                "query" => ast::OperationKind::Query,
2253                "mutation" => ast::OperationKind::Mutation,
2254                "subscription" => ast::OperationKind::Subscription,
2255                _ => {
2256                    self.record_error(GraphQLParseError::new(
2257                        format!(
2258                            "unknown operation type `{}`; expected `query`, `mutation`, \
2259                             or `subscription`",
2260                            op_name.value,
2261                        ),
2262                        GraphQLParseErrorKind::InvalidSyntax,
2263                        self.resolve_span(op_name.span),
2264                    ));
2265                    continue;
2266                },
2267            };
2268            let colon_token = self.expect(&GraphQLTokenKind::Colon)?;
2269            let named_type = self.expect_ast_name()?;
2270            let root_span = ByteSpan::new(
2271                op_name.span.start,
2272                named_type.span.end,
2273            );
2274            let root_syntax = if self.config.retain_syntax {
2275                Some(Box::new(ast::RootOperationTypeDefinitionSyntax {
2276                    colon: colon_token,
2277                }))
2278            } else {
2279                None
2280            };
2281            root_operations.push(ast::RootOperationTypeDefinition {
2282                named_type, operation_kind: op_kind, span: root_span, syntax: root_syntax,
2283            });
2284        }
2285        let close_token = self.expect(&GraphQLTokenKind::CurlyBraceClose)?;
2286        self.pop_delimiter();
2287        if self.config.retain_syntax {
2288            let span = self.make_span_ref(&keyword_token.span);
2289            Ok(ast::SchemaDefinition {
2290                description, directives, root_operations, span,
2291                syntax: Some(Box::new(ast::SchemaDefinitionSyntax {
2292                    braces: ast::DelimiterPair { close: close_token, open: open_token },
2293                    schema_keyword: keyword_token,
2294                })),
2295            })
2296        } else {
2297            let span = self.make_span(keyword_token.span);
2298            Ok(ast::SchemaDefinition {
2299                description, directives, root_operations, span, syntax: None,
2300            })
2301        }
2302    }
2303
2304    /// Parses a scalar type definition: `scalar Name @directives`
2305    fn parse_scalar_type_definition(
2306        &mut self,
2307        description: Option<ast::StringValue<'src>>,
2308    ) -> Result<ast::TypeDefinition<'src>, ()> {
2309        let keyword_token = self.expect_keyword("scalar")?;
2310        let name = self.expect_ast_name()?;
2311        let directives = self.parse_const_directive_annotations()?;
2312        if self.config.retain_syntax {
2313            let span = self.make_span_ref(&keyword_token.span);
2314            Ok(ast::TypeDefinition::Scalar(ast::ScalarTypeDefinition {
2315                description, directives, name, span,
2316                syntax: Some(Box::new(ast::ScalarTypeDefinitionSyntax {
2317                    scalar_keyword: keyword_token,
2318                })),
2319            }))
2320        } else {
2321            let span = self.make_span(keyword_token.span);
2322            Ok(ast::TypeDefinition::Scalar(ast::ScalarTypeDefinition {
2323                description, directives, name, span, syntax: None,
2324            }))
2325        }
2326    }
2327
2328    /// Parses an object type definition: `type Name implements I & J
2329    /// @directives { fields }`
2330    fn parse_object_type_definition(
2331        &mut self,
2332        description: Option<ast::StringValue<'src>>,
2333    ) -> Result<ast::TypeDefinition<'src>, ()> {
2334        let keyword_token = self.expect_keyword("type")?;
2335        let name = self.expect_ast_name()?;
2336        let (implements_tokens, implements) = if self.peek_is_keyword("implements") {
2337            let clause = self.parse_ast_implements_interfaces()?;
2338            (Some(ImplementsClauseTokens {
2339                ampersands: clause.ampersands,
2340                implements_keyword: clause.implements_keyword,
2341                leading_ampersand: clause.leading_ampersand,
2342            }), clause.interfaces)
2343        } else {
2344            (None, Vec::new())
2345        };
2346        let directives = self.parse_const_directive_annotations()?;
2347        let (fields, field_delimiters) = if self.peek_is(&GraphQLTokenKind::CurlyBraceOpen) {
2348            self.parse_ast_fields_definition(DelimiterContext::ObjectTypeDefinition)?
2349        } else {
2350            (Vec::new(), None)
2351        };
2352        if self.config.retain_syntax {
2353            let span = self.make_span_ref(&keyword_token.span);
2354            let (impl_kw, leading_amp, amps) = match implements_tokens {
2355                Some(c) => (Some(c.implements_keyword), c.leading_ampersand, c.ampersands),
2356                None => (None, None, Vec::new()),
2357            };
2358            Ok(ast::TypeDefinition::Object(ast::ObjectTypeDefinition {
2359                description, directives, fields, implements, name, span,
2360                syntax: Some(Box::new(ast::ObjectTypeDefinitionSyntax {
2361                    ampersands: amps, braces: field_delimiters,
2362                    implements_keyword: impl_kw, leading_ampersand: leading_amp,
2363                    type_keyword: keyword_token,
2364                })),
2365            }))
2366        } else {
2367            let span = self.make_span(keyword_token.span);
2368            Ok(ast::TypeDefinition::Object(ast::ObjectTypeDefinition {
2369                description, directives, fields, implements, name, span, syntax: None,
2370            }))
2371        }
2372    }
2373
2374    /// Parses an interface type definition.
2375    fn parse_interface_type_definition(
2376        &mut self,
2377        description: Option<ast::StringValue<'src>>,
2378    ) -> Result<ast::TypeDefinition<'src>, ()> {
2379        let keyword_token = self.expect_keyword("interface")?;
2380        let name = self.expect_ast_name()?;
2381        let (implements_tokens, implements) = if self.peek_is_keyword("implements") {
2382            let clause = self.parse_ast_implements_interfaces()?;
2383            (Some(ImplementsClauseTokens {
2384                ampersands: clause.ampersands,
2385                implements_keyword: clause.implements_keyword,
2386                leading_ampersand: clause.leading_ampersand,
2387            }), clause.interfaces)
2388        } else {
2389            (None, Vec::new())
2390        };
2391        let directives = self.parse_const_directive_annotations()?;
2392        let (fields, field_delimiters) = if self.peek_is(&GraphQLTokenKind::CurlyBraceOpen) {
2393            self.parse_ast_fields_definition(DelimiterContext::InterfaceDefinition)?
2394        } else {
2395            (Vec::new(), None)
2396        };
2397        if self.config.retain_syntax {
2398            let span = self.make_span_ref(&keyword_token.span);
2399            let (impl_kw, leading_amp, amps) = match implements_tokens {
2400                Some(c) => (Some(c.implements_keyword), c.leading_ampersand, c.ampersands),
2401                None => (None, None, Vec::new()),
2402            };
2403            Ok(ast::TypeDefinition::Interface(ast::InterfaceTypeDefinition {
2404                description, directives, fields, implements, name, span,
2405                syntax: Some(Box::new(ast::InterfaceTypeDefinitionSyntax {
2406                    ampersands: amps, braces: field_delimiters,
2407                    implements_keyword: impl_kw, interface_keyword: keyword_token,
2408                    leading_ampersand: leading_amp,
2409                })),
2410            }))
2411        } else {
2412            let span = self.make_span(keyword_token.span);
2413            Ok(ast::TypeDefinition::Interface(ast::InterfaceTypeDefinition {
2414                description, directives, fields, implements, name, span, syntax: None,
2415            }))
2416        }
2417    }
2418
2419    /// Parses a union type definition: `union Name @directives = A | B | C`
2420    fn parse_union_type_definition(
2421        &mut self,
2422        description: Option<ast::StringValue<'src>>,
2423    ) -> Result<ast::TypeDefinition<'src>, ()> {
2424        let keyword_token = self.expect_keyword("union")?;
2425        let name = self.expect_ast_name()?;
2426        let directives = self.parse_const_directive_annotations()?;
2427        let mut members = Vec::new();
2428        let mut equals_token = None;
2429        let mut leading_pipe = None;
2430        let mut pipes = Vec::new();
2431        if self.peek_is(&GraphQLTokenKind::Equals) {
2432            equals_token = Some(self.consume_token().unwrap());
2433            // Optional leading |
2434            if self.peek_is(&GraphQLTokenKind::Pipe) {
2435                leading_pipe = Some(self.consume_token().unwrap());
2436            }
2437            members.push(self.expect_ast_name()?);
2438            while self.peek_is(&GraphQLTokenKind::Pipe) {
2439                pipes.push(self.consume_token().unwrap());
2440                members.push(self.expect_ast_name()?);
2441            }
2442        }
2443        if self.config.retain_syntax {
2444            let span = self.make_span_ref(&keyword_token.span);
2445            Ok(ast::TypeDefinition::Union(ast::UnionTypeDefinition {
2446                description, directives, members, name, span,
2447                syntax: Some(Box::new(ast::UnionTypeDefinitionSyntax {
2448                    equals: equals_token, leading_pipe, pipes,
2449                    union_keyword: keyword_token,
2450                })),
2451            }))
2452        } else {
2453            let span = self.make_span(keyword_token.span);
2454            Ok(ast::TypeDefinition::Union(ast::UnionTypeDefinition {
2455                description, directives, members, name, span, syntax: None,
2456            }))
2457        }
2458    }
2459
2460    /// Parses an enum type definition: `enum Name @directives { VALUES }`
2461    fn parse_enum_type_definition(
2462        &mut self,
2463        description: Option<ast::StringValue<'src>>,
2464    ) -> Result<ast::TypeDefinition<'src>, ()> {
2465        let keyword_token = self.expect_keyword("enum")?;
2466        let name = self.expect_ast_name()?;
2467        let directives = self.parse_const_directive_annotations()?;
2468        let (values, value_delimiters) = if self.peek_is(&GraphQLTokenKind::CurlyBraceOpen) {
2469            self.parse_enum_values_definition()?
2470        } else {
2471            (Vec::new(), None)
2472        };
2473        if self.config.retain_syntax {
2474            let span = self.make_span_ref(&keyword_token.span);
2475            Ok(ast::TypeDefinition::Enum(ast::EnumTypeDefinition {
2476                description, directives, name, span,
2477                syntax: Some(Box::new(ast::EnumTypeDefinitionSyntax {
2478                    braces: value_delimiters, enum_keyword: keyword_token,
2479                })),
2480                values,
2481            }))
2482        } else {
2483            let span = self.make_span(keyword_token.span);
2484            Ok(ast::TypeDefinition::Enum(ast::EnumTypeDefinition {
2485                description, directives, name, span, syntax: None, values,
2486            }))
2487        }
2488    }
2489
2490    /// Parses an input object type definition.
2491    fn parse_input_object_type_definition(
2492        &mut self,
2493        description: Option<ast::StringValue<'src>>,
2494    ) -> Result<ast::TypeDefinition<'src>, ()> {
2495        let keyword_token = self.expect_keyword("input")?;
2496        let name = self.expect_ast_name()?;
2497        let directives = self.parse_const_directive_annotations()?;
2498        let (fields, field_delimiters) = if self.peek_is(&GraphQLTokenKind::CurlyBraceOpen) {
2499            self.parse_input_fields_definition()?
2500        } else {
2501            (Vec::new(), None)
2502        };
2503        if self.config.retain_syntax {
2504            let span = self.make_span_ref(&keyword_token.span);
2505            Ok(ast::TypeDefinition::InputObject(ast::InputObjectTypeDefinition {
2506                description, directives, fields, name, span,
2507                syntax: Some(Box::new(ast::InputObjectTypeDefinitionSyntax {
2508                    braces: field_delimiters, input_keyword: keyword_token,
2509                })),
2510            }))
2511        } else {
2512            let span = self.make_span(keyword_token.span);
2513            Ok(ast::TypeDefinition::InputObject(ast::InputObjectTypeDefinition {
2514                description, directives, fields, name, span, syntax: None,
2515            }))
2516        }
2517    }
2518
2519    /// Parses a directive definition.
2520    fn parse_directive_definition(
2521        &mut self,
2522        description: Option<ast::StringValue<'src>>,
2523    ) -> Result<ast::DirectiveDefinition<'src>, ()> {
2524        let keyword_token = self.expect_keyword("directive")?;
2525        let at_token = self.expect(&GraphQLTokenKind::At)?;
2526        let name = self.expect_ast_name()?;
2527        let (arguments, argument_delimiters) =
2528            if self.peek_is(&GraphQLTokenKind::ParenOpen) {
2529                self.parse_arguments_definition()?
2530            } else {
2531                (Vec::new(), None)
2532            };
2533        let repeatable_token = if self.peek_is_keyword("repeatable") {
2534            Some(self.consume_token().unwrap())
2535        } else {
2536            None
2537        };
2538        let repeatable = repeatable_token.is_some();
2539        let on_token = self.expect_keyword("on")?;
2540        let locations = self.parse_directive_locations()?;
2541        if self.config.retain_syntax {
2542            let span = self.make_span_ref(&keyword_token.span);
2543            Ok(ast::DirectiveDefinition {
2544                arguments, description, locations, name, repeatable, span,
2545                syntax: Some(Box::new(ast::DirectiveDefinitionSyntax {
2546                    argument_parens: argument_delimiters, at_sign: at_token,
2547                    directive_keyword: keyword_token, on_keyword: on_token,
2548                    repeatable_keyword: repeatable_token,
2549                })),
2550            })
2551        } else {
2552            let span = self.make_span(keyword_token.span);
2553            Ok(ast::DirectiveDefinition {
2554                arguments, description, locations, name, repeatable, span, syntax: None,
2555            })
2556        }
2557    }
2558
2559    /// Parses implements interfaces: `implements A & B & C`
2560    fn parse_ast_implements_interfaces(&mut self) -> Result<ImplementsClause<'src>, ()> {
2561        let implements_keyword = self.expect_keyword("implements")?;
2562        // Optional leading &
2563        let leading_ampersand = if self.peek_is(&GraphQLTokenKind::Ampersand) {
2564            Some(self.consume_token().unwrap())
2565        } else {
2566            None
2567        };
2568        let mut interfaces = Vec::new();
2569        let mut ampersands = Vec::new();
2570        interfaces.push(self.expect_ast_name()?);
2571        while self.peek_is(&GraphQLTokenKind::Ampersand) {
2572            ampersands.push(self.consume_token().unwrap());
2573            interfaces.push(self.expect_ast_name()?);
2574        }
2575        Ok(ImplementsClause {
2576            ampersands, implements_keyword, interfaces, leading_ampersand,
2577        })
2578    }
2579
2580    /// Parses field definitions: `{ field: Type, ... }`
2581    fn parse_ast_fields_definition(
2582        &mut self,
2583        context: DelimiterContext,
2584    ) -> Result<(Vec<ast::FieldDefinition<'src>>, Option<ast::DelimiterPair<'src>>), ()> {
2585        let open_token = self.expect(&GraphQLTokenKind::CurlyBraceOpen)?;
2586        self.push_delimiter(open_token.span, context);
2587        let mut fields = Vec::new();
2588        loop {
2589            if self.peek_is(&GraphQLTokenKind::CurlyBraceClose) {
2590                break;
2591            }
2592            if self.token_stream.is_at_end() {
2593                self.handle_unclosed_brace();
2594                return Err(());
2595            }
2596            fields.push(self.parse_field_definition()?);
2597        }
2598        let close_token = self.expect(&GraphQLTokenKind::CurlyBraceClose)?;
2599        self.pop_delimiter();
2600        let delimiters = if self.config.retain_syntax {
2601            Some(ast::DelimiterPair { close: close_token, open: open_token })
2602        } else {
2603            None
2604        };
2605        Ok((fields, delimiters))
2606    }
2607
2608    /// Parses a single field definition.
2609    fn parse_field_definition(&mut self) -> Result<ast::FieldDefinition<'src>, ()> {
2610        let description = self.parse_ast_description();
2611        let name = self.expect_ast_name()?;
2612        let (arguments, argument_delimiters) =
2613            if self.peek_is(&GraphQLTokenKind::ParenOpen) {
2614                self.parse_arguments_definition()?
2615            } else {
2616                (Vec::new(), None)
2617            };
2618        let colon_token = self.expect(&GraphQLTokenKind::Colon)?;
2619        let field_type = self.parse_type_annotation()?;
2620        let directives = self.parse_const_directive_annotations()?;
2621        let span = ByteSpan::new(
2622            name.span.start,
2623            self.last_end_position.unwrap_or(name.span.end),
2624        );
2625        let syntax = if self.config.retain_syntax {
2626            Some(Box::new(ast::FieldDefinitionSyntax {
2627                argument_parens: argument_delimiters, colon: colon_token,
2628            }))
2629        } else {
2630            None
2631        };
2632        Ok(ast::FieldDefinition {
2633            parameters: arguments, description, directives, field_type, name, span, syntax,
2634        })
2635    }
2636
2637    /// Parses argument definitions: `(arg: Type = default, ...)`
2638    fn parse_arguments_definition(
2639        &mut self,
2640    ) -> Result<(Vec<ast::InputValueDefinition<'src>>, Option<ast::DelimiterPair<'src>>), ()> {
2641        let open_token = self.expect(&GraphQLTokenKind::ParenOpen)?;
2642        self.push_delimiter(open_token.span, DelimiterContext::ArgumentDefinitions);
2643        let mut arguments = Vec::new();
2644        loop {
2645            if self.peek_is(&GraphQLTokenKind::ParenClose) {
2646                break;
2647            }
2648            if self.token_stream.is_at_end() {
2649                self.handle_unclosed_paren();
2650                return Err(());
2651            }
2652            arguments.push(self.parse_input_value_definition()?);
2653        }
2654        let close_token = self.expect(&GraphQLTokenKind::ParenClose)?;
2655        self.pop_delimiter();
2656        let delimiters = if self.config.retain_syntax {
2657            Some(ast::DelimiterPair { close: close_token, open: open_token })
2658        } else {
2659            None
2660        };
2661        Ok((arguments, delimiters))
2662    }
2663
2664    /// Parses input fields definition (for input objects).
2665    fn parse_input_fields_definition(
2666        &mut self,
2667    ) -> Result<(Vec<ast::InputValueDefinition<'src>>, Option<ast::DelimiterPair<'src>>), ()> {
2668        let open_token = self.expect(&GraphQLTokenKind::CurlyBraceOpen)?;
2669        self.push_delimiter(open_token.span, DelimiterContext::InputObjectDefinition);
2670        let mut fields = Vec::new();
2671        loop {
2672            if self.peek_is(&GraphQLTokenKind::CurlyBraceClose) {
2673                break;
2674            }
2675            if self.token_stream.is_at_end() {
2676                self.handle_unclosed_brace();
2677                return Err(());
2678            }
2679            fields.push(self.parse_input_value_definition()?);
2680        }
2681        let close_token = self.expect(&GraphQLTokenKind::CurlyBraceClose)?;
2682        self.pop_delimiter();
2683        let delimiters = if self.config.retain_syntax {
2684            Some(ast::DelimiterPair { close: close_token, open: open_token })
2685        } else {
2686            None
2687        };
2688        Ok((fields, delimiters))
2689    }
2690
2691    /// Parses an input value definition (used for arguments and input fields).
2692    fn parse_input_value_definition(&mut self) -> Result<ast::InputValueDefinition<'src>, ()> {
2693        let description = self.parse_ast_description();
2694        let name = self.expect_ast_name()?;
2695        let colon_token = self.expect(&GraphQLTokenKind::Colon)?;
2696        let value_type = self.parse_type_annotation()?;
2697        let (default_value, equals_token) = if self.peek_is(&GraphQLTokenKind::Equals) {
2698            let eq = self.consume_token().unwrap();
2699            (Some(self.parse_value(ConstContext::InputDefaultValue)?), Some(eq))
2700        } else {
2701            (None, None)
2702        };
2703        let directives = self.parse_const_directive_annotations()?;
2704        let span = ByteSpan::new(
2705            name.span.start,
2706            self.last_end_position.unwrap_or(name.span.end),
2707        );
2708        let syntax = if self.config.retain_syntax {
2709            Some(Box::new(ast::InputValueDefinitionSyntax {
2710                colon: colon_token, equals: equals_token,
2711            }))
2712        } else {
2713            None
2714        };
2715        Ok(ast::InputValueDefinition {
2716            default_value, description, directives, name, span, syntax, value_type,
2717        })
2718    }
2719
2720    /// Parses enum value definitions.
2721    fn parse_enum_values_definition(
2722        &mut self,
2723    ) -> Result<(Vec<ast::EnumValueDefinition<'src>>, Option<ast::DelimiterPair<'src>>), ()> {
2724        let open_token = self.expect(&GraphQLTokenKind::CurlyBraceOpen)?;
2725        self.push_delimiter(open_token.span, DelimiterContext::EnumDefinition);
2726        let mut values = Vec::new();
2727        loop {
2728            if self.peek_is(&GraphQLTokenKind::CurlyBraceClose) {
2729                break;
2730            }
2731            if self.token_stream.is_at_end() {
2732                self.handle_unclosed_brace();
2733                return Err(());
2734            }
2735            values.push(self.parse_enum_value_definition()?);
2736        }
2737        let close_token = self.expect(&GraphQLTokenKind::CurlyBraceClose)?;
2738        self.pop_delimiter();
2739        let delimiters = if self.config.retain_syntax {
2740            Some(ast::DelimiterPair { close: close_token, open: open_token })
2741        } else {
2742            None
2743        };
2744        Ok((values, delimiters))
2745    }
2746
2747    /// Parses a single enum value definition.
2748    fn parse_enum_value_definition(&mut self) -> Result<ast::EnumValueDefinition<'src>, ()> {
2749        let description = self.parse_ast_description();
2750        let name = self.expect_ast_name()?;
2751        if matches!(&*name.value, "true" | "false" | "null") {
2752            let mut error = GraphQLParseError::new(
2753                format!("enum value cannot be `{}`", name.value),
2754                GraphQLParseErrorKind::ReservedName {
2755                    name: name.value.clone().into_owned(),
2756                    context: ReservedNameContext::EnumValue,
2757                },
2758                self.resolve_span(name.span),
2759            );
2760            error.add_spec(
2761                "https://spec.graphql.org/October2021/#sec-Enum-Value-Uniqueness",
2762            );
2763            self.record_error(error);
2764        }
2765        let directives = self.parse_const_directive_annotations()?;
2766        let span = ByteSpan::new(
2767            name.span.start,
2768            self.last_end_position.unwrap_or(name.span.end),
2769        );
2770        Ok(ast::EnumValueDefinition { description, directives, name, span })
2771    }
2772
2773    /// Parses directive locations: `FIELD | OBJECT | ...`
2774    fn parse_directive_locations(&mut self) -> Result<Vec<ast::DirectiveLocation<'src>>, ()> {
2775        // Optional leading |
2776        let leading_pipe = if self.peek_is(&GraphQLTokenKind::Pipe) {
2777            Some(self.consume_token().unwrap())
2778        } else {
2779            None
2780        };
2781
2782        let mut locations = Vec::new();
2783        locations.push(self.parse_directive_location(leading_pipe)?);
2784
2785        while self.peek_is(&GraphQLTokenKind::Pipe) {
2786            let pipe = self.consume_token().unwrap();
2787            locations.push(self.parse_directive_location(Some(pipe))?);
2788        }
2789
2790        Ok(locations)
2791    }
2792
2793    /// Parses a single directive location.
2794    fn parse_directive_location(
2795        &mut self, pipe: Option<GraphQLToken<'src>>,
2796    ) -> Result<ast::DirectiveLocation<'src>, ()> {
2797        let name = self.expect_ast_name()?;
2798        let kind = match &*name.value {
2799            // Executable locations
2800            "QUERY" => ast::DirectiveLocationKind::Query,
2801            "MUTATION" => ast::DirectiveLocationKind::Mutation,
2802            "SUBSCRIPTION" => ast::DirectiveLocationKind::Subscription,
2803            "FIELD" => ast::DirectiveLocationKind::Field,
2804            "FRAGMENT_DEFINITION" => ast::DirectiveLocationKind::FragmentDefinition,
2805            "FRAGMENT_SPREAD" => ast::DirectiveLocationKind::FragmentSpread,
2806            "INLINE_FRAGMENT" => ast::DirectiveLocationKind::InlineFragment,
2807            "VARIABLE_DEFINITION" => ast::DirectiveLocationKind::VariableDefinition,
2808            // Type system locations
2809            "SCHEMA" => ast::DirectiveLocationKind::Schema,
2810            "SCALAR" => ast::DirectiveLocationKind::Scalar,
2811            "OBJECT" => ast::DirectiveLocationKind::Object,
2812            "FIELD_DEFINITION" => ast::DirectiveLocationKind::FieldDefinition,
2813            "ARGUMENT_DEFINITION" => ast::DirectiveLocationKind::ArgumentDefinition,
2814            "INTERFACE" => ast::DirectiveLocationKind::Interface,
2815            "UNION" => ast::DirectiveLocationKind::Union,
2816            "ENUM" => ast::DirectiveLocationKind::Enum,
2817            "ENUM_VALUE" => ast::DirectiveLocationKind::EnumValue,
2818            "INPUT_OBJECT" => ast::DirectiveLocationKind::InputObject,
2819            "INPUT_FIELD_DEFINITION" => ast::DirectiveLocationKind::InputFieldDefinition,
2820            _ => {
2821                let mut error = GraphQLParseError::new(
2822                    format!("unknown directive location `{}`", name.value),
2823                    GraphQLParseErrorKind::InvalidSyntax,
2824                    self.resolve_span(name.span),
2825                );
2826                if let Some(suggestion) = Self::suggest_directive_location(&name.value) {
2827                    error.add_help(format!("did you mean `{suggestion}`?"));
2828                }
2829                self.record_error(error);
2830                return Err(());
2831            },
2832        };
2833        let syntax = if self.config.retain_syntax {
2834            Some(Box::new(ast::DirectiveLocationSyntax {
2835                pipe,
2836                token: name.syntax.unwrap().token,
2837            }))
2838        } else {
2839            None
2840        };
2841        Ok(ast::DirectiveLocation { kind, span: name.span, syntax })
2842    }
2843
2844    /// Suggests the closest directive location for a typo.
2845    fn suggest_directive_location(input: &str) -> Option<&'static str> {
2846        const LOCATIONS: &[&str] = &[
2847            "QUERY",
2848            "MUTATION",
2849            "SUBSCRIPTION",
2850            "FIELD",
2851            "FRAGMENT_DEFINITION",
2852            "FRAGMENT_SPREAD",
2853            "INLINE_FRAGMENT",
2854            "VARIABLE_DEFINITION",
2855            "SCHEMA",
2856            "SCALAR",
2857            "OBJECT",
2858            "FIELD_DEFINITION",
2859            "ARGUMENT_DEFINITION",
2860            "INTERFACE",
2861            "UNION",
2862            "ENUM",
2863            "ENUM_VALUE",
2864            "INPUT_OBJECT",
2865            "INPUT_FIELD_DEFINITION",
2866        ];
2867
2868        // Simple edit distance for suggestions
2869        let input_upper = input.to_uppercase();
2870        let mut best_match: Option<&'static str> = None;
2871        let mut best_distance = usize::MAX;
2872
2873        for &location in LOCATIONS {
2874            let distance = Self::edit_distance(&input_upper, location);
2875            if distance < best_distance && distance <= 3 {
2876                best_distance = distance;
2877                best_match = Some(location);
2878            }
2879        }
2880
2881        best_match
2882    }
2883
2884    /// Simple Levenshtein edit distance.
2885    fn edit_distance(a: &str, b: &str) -> usize {
2886        let a_chars: Vec<char> = a.chars().collect();
2887        let b_chars: Vec<char> = b.chars().collect();
2888        let m = a_chars.len();
2889        let n = b_chars.len();
2890
2891        if m == 0 {
2892            return n;
2893        }
2894        if n == 0 {
2895            return m;
2896        }
2897
2898        let mut prev: Vec<usize> = (0..=n).collect();
2899        let mut curr = vec![0; n + 1];
2900
2901        for i in 1..=m {
2902            curr[0] = i;
2903            for j in 1..=n {
2904                let cost = if a_chars[i - 1] == b_chars[j - 1] {
2905                    0
2906                } else {
2907                    1
2908                };
2909                curr[j] = (prev[j] + 1)
2910                    .min(curr[j - 1] + 1)
2911                    .min(prev[j - 1] + cost);
2912            }
2913            std::mem::swap(&mut prev, &mut curr);
2914        }
2915
2916        prev[n]
2917    }
2918
2919    // =========================================================================
2920    // Type extension parsing
2921    // =========================================================================
2922
2923    /// Parses a type extension.
2924    ///
2925    /// Parses a type or schema extension: `extend <keyword> ...`
2926    fn parse_type_extension(&mut self) -> Result<ast::Definition<'src>, ()> {
2927        let extend_token = self.expect_keyword("extend")?;
2928        if self.peek_is_keyword("schema") {
2929            self.parse_schema_extension(extend_token)
2930        } else if self.peek_is_keyword("scalar") {
2931            self.parse_scalar_type_extension(extend_token)
2932        } else if self.peek_is_keyword("type") {
2933            self.parse_object_type_extension(extend_token)
2934        } else if self.peek_is_keyword("interface") {
2935            self.parse_interface_type_extension(extend_token)
2936        } else if self.peek_is_keyword("union") {
2937            self.parse_union_type_extension(extend_token)
2938        } else if self.peek_is_keyword("enum") {
2939            self.parse_enum_type_extension(extend_token)
2940        } else if self.peek_is_keyword("input") {
2941            self.parse_input_object_type_extension(extend_token)
2942        } else {
2943            let span = self.token_stream.peek()
2944                .map(|t| t.span)
2945                .unwrap_or_else(|| self.eof_span());
2946            let found = self.token_stream.peek()
2947                .map(|t| Self::token_kind_display(&t.kind))
2948                .unwrap_or_else(|| "end of input".to_string());
2949            self.record_error(GraphQLParseError::new(
2950                format!(
2951                    "expected type extension keyword (`schema`, `scalar`, `type`, \
2952                     `interface`, `union`, `enum`, `input`), found `{found}`"
2953                ),
2954                GraphQLParseErrorKind::UnexpectedToken {
2955                    expected: vec![
2956                        "schema".to_string(),
2957                        "scalar".to_string(),
2958                        "type".to_string(),
2959                        "interface".to_string(),
2960                        "union".to_string(),
2961                        "enum".to_string(),
2962                        "input".to_string(),
2963                    ],
2964                    found,
2965                },
2966                self.resolve_span(span),
2967            ));
2968            Err(())
2969        }
2970    }
2971
2972    /// Parses a schema extension: `extend schema @directives { query: Query }`
2973    fn parse_schema_extension(
2974        &mut self,
2975        extend_token: GraphQLToken<'src>,
2976    ) -> Result<ast::Definition<'src>, ()> {
2977        let schema_token = self.expect_keyword("schema")?;
2978        let directives = self.parse_const_directive_annotations()?;
2979        let mut root_operations = Vec::new();
2980        let mut braces = None;
2981        if self.peek_is(&GraphQLTokenKind::CurlyBraceOpen) {
2982            let open_token = self.expect(&GraphQLTokenKind::CurlyBraceOpen)?;
2983            self.push_delimiter(open_token.span, DelimiterContext::SchemaDefinition);
2984            loop {
2985                if self.peek_is(&GraphQLTokenKind::CurlyBraceClose) {
2986                    break;
2987                }
2988                if self.token_stream.is_at_end() {
2989                    self.handle_unclosed_brace();
2990                    return Err(());
2991                }
2992                let op_name = self.expect_ast_name()?;
2993                let op_kind = match &*op_name.value {
2994                    "query" => ast::OperationKind::Query,
2995                    "mutation" => ast::OperationKind::Mutation,
2996                    "subscription" => ast::OperationKind::Subscription,
2997                    _ => {
2998                        self.record_error(GraphQLParseError::new(
2999                            format!(
3000                                "unknown operation type `{}`; expected `query`, `mutation`, \
3001                                 or `subscription`",
3002                                op_name.value,
3003                            ),
3004                            GraphQLParseErrorKind::InvalidSyntax,
3005                            self.resolve_span(op_name.span),
3006                        ));
3007                        continue;
3008                    },
3009                };
3010                let colon_token = self.expect(&GraphQLTokenKind::Colon)?;
3011                let named_type = self.expect_ast_name()?;
3012                let root_span = ByteSpan::new(
3013                    op_name.span.start,
3014                    named_type.span.end,
3015                );
3016                let root_syntax = if self.config.retain_syntax {
3017                    Some(Box::new(ast::RootOperationTypeDefinitionSyntax {
3018                        colon: colon_token,
3019                    }))
3020                } else {
3021                    None
3022                };
3023                root_operations.push(ast::RootOperationTypeDefinition {
3024                    named_type, operation_kind: op_kind, span: root_span, syntax: root_syntax,
3025                });
3026            }
3027            let close_token = self.expect(&GraphQLTokenKind::CurlyBraceClose)?;
3028            self.pop_delimiter();
3029            if self.config.retain_syntax {
3030                braces = Some(ast::DelimiterPair { close: close_token, open: open_token });
3031            }
3032        }
3033        if self.config.retain_syntax {
3034            let span = self.make_span_ref(&extend_token.span);
3035            Ok(ast::Definition::SchemaExtension(ast::SchemaExtension {
3036                directives, root_operations, span,
3037                syntax: Some(Box::new(ast::SchemaExtensionSyntax {
3038                    braces, extend_keyword: extend_token, schema_keyword: schema_token,
3039                })),
3040            }))
3041        } else {
3042            let span = self.make_span(extend_token.span);
3043            Ok(ast::Definition::SchemaExtension(ast::SchemaExtension {
3044                directives, root_operations, span, syntax: None,
3045            }))
3046        }
3047    }
3048
3049    /// Parses a scalar type extension: `extend scalar Name @directives`
3050    fn parse_scalar_type_extension(
3051        &mut self,
3052        extend_token: GraphQLToken<'src>,
3053    ) -> Result<ast::Definition<'src>, ()> {
3054        let scalar_token = self.expect_keyword("scalar")?;
3055        let name = self.expect_ast_name()?;
3056        let directives = self.parse_const_directive_annotations()?;
3057        if self.config.retain_syntax {
3058            let span = self.make_span_ref(&extend_token.span);
3059            Ok(ast::Definition::TypeExtension(ast::TypeExtension::Scalar(
3060                ast::ScalarTypeExtension {
3061                    directives, name, span,
3062                    syntax: Some(Box::new(ast::ScalarTypeExtensionSyntax {
3063                        extend_keyword: extend_token, scalar_keyword: scalar_token,
3064                    })),
3065                },
3066            )))
3067        } else {
3068            let span = self.make_span(extend_token.span);
3069            Ok(ast::Definition::TypeExtension(ast::TypeExtension::Scalar(
3070                ast::ScalarTypeExtension { directives, name, span, syntax: None },
3071            )))
3072        }
3073    }
3074
3075    /// Parses an object type extension: `extend type Name implements I & J
3076    /// @directives { fields }`
3077    fn parse_object_type_extension(
3078        &mut self,
3079        extend_token: GraphQLToken<'src>,
3080    ) -> Result<ast::Definition<'src>, ()> {
3081        let type_token = self.expect_keyword("type")?;
3082        let name = self.expect_ast_name()?;
3083        let (implements_tokens, implements) = if self.peek_is_keyword("implements") {
3084            let clause = self.parse_ast_implements_interfaces()?;
3085            (Some(ImplementsClauseTokens {
3086                ampersands: clause.ampersands,
3087                implements_keyword: clause.implements_keyword,
3088                leading_ampersand: clause.leading_ampersand,
3089            }), clause.interfaces)
3090        } else {
3091            (None, Vec::new())
3092        };
3093        let directives = self.parse_const_directive_annotations()?;
3094        let (fields, field_delimiters) = if self.peek_is(&GraphQLTokenKind::CurlyBraceOpen) {
3095            self.parse_ast_fields_definition(DelimiterContext::ObjectTypeDefinition)?
3096        } else {
3097            (Vec::new(), None)
3098        };
3099        if self.config.retain_syntax {
3100            let span = self.make_span_ref(&extend_token.span);
3101            let (impl_kw, leading_amp, amps) = match implements_tokens {
3102                Some(c) => (Some(c.implements_keyword), c.leading_ampersand, c.ampersands),
3103                None => (None, None, Vec::new()),
3104            };
3105            Ok(ast::Definition::TypeExtension(ast::TypeExtension::Object(
3106                ast::ObjectTypeExtension {
3107                    directives, fields, implements, name, span,
3108                    syntax: Some(Box::new(ast::ObjectTypeExtensionSyntax {
3109                        ampersands: amps, braces: field_delimiters,
3110                        extend_keyword: extend_token, implements_keyword: impl_kw,
3111                        leading_ampersand: leading_amp, type_keyword: type_token,
3112                    })),
3113                },
3114            )))
3115        } else {
3116            let span = self.make_span(extend_token.span);
3117            Ok(ast::Definition::TypeExtension(ast::TypeExtension::Object(
3118                ast::ObjectTypeExtension {
3119                    directives, fields, implements, name, span, syntax: None,
3120                },
3121            )))
3122        }
3123    }
3124
3125    /// Parses an interface type extension.
3126    fn parse_interface_type_extension(
3127        &mut self,
3128        extend_token: GraphQLToken<'src>,
3129    ) -> Result<ast::Definition<'src>, ()> {
3130        let interface_token = self.expect_keyword("interface")?;
3131        let name = self.expect_ast_name()?;
3132        let (implements_tokens, implements) = if self.peek_is_keyword("implements") {
3133            let clause = self.parse_ast_implements_interfaces()?;
3134            (Some(ImplementsClauseTokens {
3135                ampersands: clause.ampersands,
3136                implements_keyword: clause.implements_keyword,
3137                leading_ampersand: clause.leading_ampersand,
3138            }), clause.interfaces)
3139        } else {
3140            (None, Vec::new())
3141        };
3142        let directives = self.parse_const_directive_annotations()?;
3143        let (fields, field_delimiters) = if self.peek_is(&GraphQLTokenKind::CurlyBraceOpen) {
3144            self.parse_ast_fields_definition(DelimiterContext::InterfaceDefinition)?
3145        } else {
3146            (Vec::new(), None)
3147        };
3148        if self.config.retain_syntax {
3149            let span = self.make_span_ref(&extend_token.span);
3150            let (impl_kw, leading_amp, amps) = match implements_tokens {
3151                Some(c) => (Some(c.implements_keyword), c.leading_ampersand, c.ampersands),
3152                None => (None, None, Vec::new()),
3153            };
3154            Ok(ast::Definition::TypeExtension(ast::TypeExtension::Interface(
3155                ast::InterfaceTypeExtension {
3156                    directives, fields, implements, name, span,
3157                    syntax: Some(Box::new(ast::InterfaceTypeExtensionSyntax {
3158                        ampersands: amps, braces: field_delimiters,
3159                        extend_keyword: extend_token, implements_keyword: impl_kw,
3160                        interface_keyword: interface_token, leading_ampersand: leading_amp,
3161                    })),
3162                },
3163            )))
3164        } else {
3165            let span = self.make_span(extend_token.span);
3166            Ok(ast::Definition::TypeExtension(ast::TypeExtension::Interface(
3167                ast::InterfaceTypeExtension {
3168                    directives, fields, implements, name, span, syntax: None,
3169                },
3170            )))
3171        }
3172    }
3173
3174    /// Parses a union type extension: `extend union Name @directives = A | B`
3175    fn parse_union_type_extension(
3176        &mut self,
3177        extend_token: GraphQLToken<'src>,
3178    ) -> Result<ast::Definition<'src>, ()> {
3179        let union_token = self.expect_keyword("union")?;
3180        let name = self.expect_ast_name()?;
3181        let directives = self.parse_const_directive_annotations()?;
3182        let mut members = Vec::new();
3183        let mut equals_token = None;
3184        let mut leading_pipe = None;
3185        let mut pipes = Vec::new();
3186        if self.peek_is(&GraphQLTokenKind::Equals) {
3187            equals_token = Some(self.consume_token().unwrap());
3188            if self.peek_is(&GraphQLTokenKind::Pipe) {
3189                leading_pipe = Some(self.consume_token().unwrap());
3190            }
3191            members.push(self.expect_ast_name()?);
3192            while self.peek_is(&GraphQLTokenKind::Pipe) {
3193                pipes.push(self.consume_token().unwrap());
3194                members.push(self.expect_ast_name()?);
3195            }
3196        }
3197        if self.config.retain_syntax {
3198            let span = self.make_span_ref(&extend_token.span);
3199            Ok(ast::Definition::TypeExtension(ast::TypeExtension::Union(
3200                ast::UnionTypeExtension {
3201                    directives, members, name, span,
3202                    syntax: Some(Box::new(ast::UnionTypeExtensionSyntax {
3203                        equals: equals_token, extend_keyword: extend_token,
3204                        leading_pipe, pipes, union_keyword: union_token,
3205                    })),
3206                },
3207            )))
3208        } else {
3209            let span = self.make_span(extend_token.span);
3210            Ok(ast::Definition::TypeExtension(ast::TypeExtension::Union(
3211                ast::UnionTypeExtension { directives, members, name, span, syntax: None },
3212            )))
3213        }
3214    }
3215
3216    /// Parses an enum type extension: `extend enum Name @directives { VALUES }`
3217    fn parse_enum_type_extension(
3218        &mut self,
3219        extend_token: GraphQLToken<'src>,
3220    ) -> Result<ast::Definition<'src>, ()> {
3221        let enum_token = self.expect_keyword("enum")?;
3222        let name = self.expect_ast_name()?;
3223        let directives = self.parse_const_directive_annotations()?;
3224        let (values, value_delimiters) = if self.peek_is(&GraphQLTokenKind::CurlyBraceOpen) {
3225            self.parse_enum_values_definition()?
3226        } else {
3227            (Vec::new(), None)
3228        };
3229        if self.config.retain_syntax {
3230            let span = self.make_span_ref(&extend_token.span);
3231            Ok(ast::Definition::TypeExtension(ast::TypeExtension::Enum(
3232                ast::EnumTypeExtension {
3233                    directives, name, span,
3234                    syntax: Some(Box::new(ast::EnumTypeExtensionSyntax {
3235                        braces: value_delimiters, enum_keyword: enum_token,
3236                        extend_keyword: extend_token,
3237                    })),
3238                    values,
3239                },
3240            )))
3241        } else {
3242            let span = self.make_span(extend_token.span);
3243            Ok(ast::Definition::TypeExtension(ast::TypeExtension::Enum(
3244                ast::EnumTypeExtension { directives, name, span, syntax: None, values },
3245            )))
3246        }
3247    }
3248
3249    /// Parses an input object type extension.
3250    fn parse_input_object_type_extension(
3251        &mut self,
3252        extend_token: GraphQLToken<'src>,
3253    ) -> Result<ast::Definition<'src>, ()> {
3254        let input_token = self.expect_keyword("input")?;
3255        let name = self.expect_ast_name()?;
3256        let directives = self.parse_const_directive_annotations()?;
3257        let (fields, field_delimiters) = if self.peek_is(&GraphQLTokenKind::CurlyBraceOpen) {
3258            self.parse_input_fields_definition()?
3259        } else {
3260            (Vec::new(), None)
3261        };
3262        if self.config.retain_syntax {
3263            let span = self.make_span_ref(&extend_token.span);
3264            Ok(ast::Definition::TypeExtension(ast::TypeExtension::InputObject(
3265                ast::InputObjectTypeExtension {
3266                    directives, fields, name, span,
3267                    syntax: Some(Box::new(ast::InputObjectTypeExtensionSyntax {
3268                        braces: field_delimiters, extend_keyword: extend_token,
3269                        input_keyword: input_token,
3270                    })),
3271                },
3272            )))
3273        } else {
3274            let span = self.make_span(extend_token.span);
3275            Ok(ast::Definition::TypeExtension(ast::TypeExtension::InputObject(
3276                ast::InputObjectTypeExtension { directives, fields, name, span, syntax: None },
3277            )))
3278        }
3279    }
3280
3281    // =========================================================================
3282    // Document parsing (public API)
3283    // =========================================================================
3284
3285    /// Returns a span covering the entire document (byte 0 to last token end).
3286    fn document_span(&self) -> ByteSpan {
3287        let end = self.last_end_position.unwrap_or(0);
3288        ByteSpan::new(0, end)
3289    }
3290
3291    /// Parses a schema document (type system definitions only).
3292    pub fn parse_schema_document(mut self) -> ParseResult<'src, ast::Document<'src>> {
3293        let mut definitions = Vec::new();
3294        while !self.token_stream.is_at_end() {
3295            match self.parse_schema_definition_item() {
3296                Ok(def) => definitions.push(def),
3297                Err(()) => self.recover_to_next_definition(),
3298            }
3299        }
3300        self.finish_document(definitions)
3301    }
3302
3303    /// Parses an executable document (operations and fragments only).
3304    pub fn parse_executable_document(mut self) -> ParseResult<'src, ast::Document<'src>> {
3305        let mut definitions = Vec::new();
3306        while !self.token_stream.is_at_end() {
3307            match self.parse_executable_definition_item() {
3308                Ok(def) => definitions.push(def),
3309                Err(()) => self.recover_to_next_definition(),
3310            }
3311        }
3312        self.finish_document(definitions)
3313    }
3314
3315    /// Parses a mixed document (both type system and executable definitions).
3316    pub fn parse_mixed_document(mut self) -> ParseResult<'src, ast::Document<'src>> {
3317        let mut definitions = Vec::new();
3318        while !self.token_stream.is_at_end() {
3319            match self.parse_mixed_definition_item() {
3320                Ok(def) => definitions.push(def),
3321                Err(()) => self.recover_to_next_definition(),
3322            }
3323        }
3324        self.finish_document(definitions)
3325    }
3326
3327    /// Finishes document construction: extracts trailing trivia,
3328    /// builds the Document AST node, consumes the token stream for
3329    /// its SourceMap, and returns the final ParseResult.
3330    fn finish_document(
3331        mut self,
3332        definitions: Vec<ast::Definition<'src>>,
3333    ) -> ParseResult<'src, ast::Document<'src>> {
3334        let span = self.document_span();
3335        let syntax = if self.config.retain_syntax {
3336            let trailing_trivia = self.token_stream.peek()
3337                .map(|eof| eof.preceding_trivia.to_vec())
3338                .unwrap_or_default();
3339            Some(Box::new(ast::DocumentSyntax { trailing_trivia }))
3340        } else {
3341            None
3342        };
3343        let document = ast::Document { definitions, span, syntax };
3344        let source_map = self.token_stream.into_source_map();
3345        if self.errors.is_empty() {
3346            ParseResult::new_ok(document, source_map)
3347        } else {
3348            ParseResult::new_recovered(document, self.errors, source_map)
3349        }
3350    }
3351
3352    /// Parses a single schema definition item.
3353    fn parse_schema_definition_item(&mut self) -> Result<ast::Definition<'src>, ()> {
3354        // Handle lexer errors
3355        if let Some(token) = self.token_stream.peek()
3356            && let GraphQLTokenKind::Error(_) = &token.kind {
3357                let token = token.clone();
3358                self.handle_lexer_error(&token);
3359                self.consume_token();
3360                return Err(());
3361            }
3362
3363        let description = self.parse_ast_description();
3364
3365        if self.peek_is_keyword("schema") {
3366            Ok(ast::Definition::SchemaDefinition(self.parse_schema_definition(description)?))
3367        } else if self.peek_is_keyword("scalar") {
3368            Ok(ast::Definition::TypeDefinition(self.parse_scalar_type_definition(description)?))
3369        } else if self.peek_is_keyword("type") {
3370            Ok(ast::Definition::TypeDefinition(self.parse_object_type_definition(description)?))
3371        } else if self.peek_is_keyword("interface") {
3372            Ok(ast::Definition::TypeDefinition(
3373                self.parse_interface_type_definition(description)?,
3374            ))
3375        } else if self.peek_is_keyword("union") {
3376            Ok(ast::Definition::TypeDefinition(self.parse_union_type_definition(description)?))
3377        } else if self.peek_is_keyword("enum") {
3378            Ok(ast::Definition::TypeDefinition(self.parse_enum_type_definition(description)?))
3379        } else if self.peek_is_keyword("input") {
3380            Ok(ast::Definition::TypeDefinition(
3381                self.parse_input_object_type_definition(description)?,
3382            ))
3383        } else if self.peek_is_keyword("directive") {
3384            Ok(ast::Definition::DirectiveDefinition(
3385                self.parse_directive_definition(description)?,
3386            ))
3387        } else if self.peek_is_keyword("extend") {
3388            self.parse_type_extension()
3389        } else if self.peek_is_keyword("query")
3390            || self.peek_is_keyword("mutation")
3391            || self.peek_is_keyword("subscription")
3392            || self.peek_is_keyword("fragment")
3393            || self.peek_is(&GraphQLTokenKind::CurlyBraceOpen) {
3394            // Executable definition in schema document - record error
3395            let span = self
3396                .token_stream.peek()
3397                .map(|t| t.span)
3398                .unwrap_or_else(|| self.eof_span());
3399            let kind = if self.peek_is_keyword("fragment") {
3400                ast::DefinitionKind::Fragment
3401            } else {
3402                ast::DefinitionKind::Operation
3403            };
3404            self.record_error(GraphQLParseError::new(
3405                format!(
3406                    "{} not allowed in schema document",
3407                    match kind {
3408                        ast::DefinitionKind::Fragment => "fragment definition",
3409                        ast::DefinitionKind::Operation => "operation definition",
3410                        _ => "definition",
3411                    }
3412                ),
3413                GraphQLParseErrorKind::WrongDocumentKind {
3414                    found: kind,
3415                    document_kind: ast::DocumentKind::Schema,
3416                },
3417                self.resolve_span(span),
3418            ));
3419            // Consume the token to ensure forward progress during error
3420            // recovery. Without this, recovery sees `fragment`/`query`/etc.
3421            // as a definition start and breaks without consuming, causing
3422            // an infinite loop.
3423            self.consume_token();
3424            Err(())
3425        } else {
3426            let span = self
3427                .token_stream.peek()
3428                .map(|t| t.span)
3429                .unwrap_or_else(|| self.eof_span());
3430            let found = self
3431                .token_stream.peek()
3432                .map(|t| Self::token_kind_display(&t.kind))
3433                .unwrap_or_else(|| "end of input".to_string());
3434            // Consume the token to ensure forward progress during error
3435            // recovery. Without this, recovery sees the unconsumed token
3436            // as a potential definition start and stops immediately,
3437            // causing an infinite loop.
3438            self.consume_token();
3439            self.record_error(GraphQLParseError::new(
3440                format!("expected schema definition, found `{found}`"),
3441                GraphQLParseErrorKind::UnexpectedToken {
3442                    expected: vec![
3443                        "type".to_string(),
3444                        "interface".to_string(),
3445                        "union".to_string(),
3446                        "enum".to_string(),
3447                        "scalar".to_string(),
3448                        "input".to_string(),
3449                        "directive".to_string(),
3450                        "schema".to_string(),
3451                        "extend".to_string(),
3452                    ],
3453                    found,
3454                },
3455                self.resolve_span(span),
3456            ));
3457            Err(())
3458        }
3459    }
3460
3461    /// Parses a single executable definition item.
3462    fn parse_executable_definition_item(&mut self) -> Result<ast::Definition<'src>, ()> {
3463        // Handle lexer errors
3464        if let Some(token) = self.token_stream.peek()
3465            && let GraphQLTokenKind::Error(_) = &token.kind {
3466                let token = token.clone();
3467                self.handle_lexer_error(&token);
3468                self.consume_token();
3469                return Err(());
3470            }
3471
3472        if self.peek_is_keyword("query")
3473            || self.peek_is_keyword("mutation")
3474            || self.peek_is_keyword("subscription")
3475            || self.peek_is(&GraphQLTokenKind::CurlyBraceOpen) {
3476            Ok(ast::Definition::OperationDefinition(self.parse_operation_definition()?))
3477        } else if self.peek_is_keyword("fragment") {
3478            Ok(ast::Definition::FragmentDefinition(self.parse_fragment_definition()?))
3479        } else if self.peek_is_keyword("type")
3480            || self.peek_is_keyword("interface")
3481            || self.peek_is_keyword("union")
3482            || self.peek_is_keyword("enum")
3483            || self.peek_is_keyword("scalar")
3484            || self.peek_is_keyword("input")
3485            || self.peek_is_keyword("directive")
3486            || self.peek_is_keyword("schema")
3487            || self.peek_is_keyword("extend") {
3488            // Schema definition in executable document - record error
3489            let span = self
3490                .token_stream.peek()
3491                .map(|t| t.span)
3492                .unwrap_or_else(|| self.eof_span());
3493            let kind = if self.peek_is_keyword("directive") {
3494                ast::DefinitionKind::DirectiveDefinition
3495            } else if self.peek_is_keyword("schema") || self.peek_is_keyword("extend") {
3496                ast::DefinitionKind::Schema
3497            } else {
3498                ast::DefinitionKind::TypeDefinition
3499            };
3500            self.consume_token();
3501            self.record_error(GraphQLParseError::new(
3502                format!(
3503                    "{} not allowed in executable document",
3504                    match kind {
3505                        ast::DefinitionKind::TypeDefinition => "type definition",
3506                        ast::DefinitionKind::DirectiveDefinition => "directive definition",
3507                        ast::DefinitionKind::Schema => "schema definition",
3508                        _ => "definition",
3509                    }
3510                ),
3511                GraphQLParseErrorKind::WrongDocumentKind {
3512                    found: kind,
3513                    document_kind: ast::DocumentKind::Executable,
3514                },
3515                self.resolve_span(span),
3516            ));
3517            Err(())
3518        } else {
3519            // Check for description followed by type definition (common mistake)
3520            // Extract info from first peek before taking second peek to avoid
3521            // double borrow.
3522            let first_is_string = self
3523                .token_stream.peek()
3524                .map(|t| matches!(&t.kind, GraphQLTokenKind::StringValue(_)))
3525                .unwrap_or(false);
3526
3527            if first_is_string {
3528                // Might be a description - peek ahead to check for type keyword
3529                let is_type_def = self.token_stream.peek_nth(1).is_some_and(|next| {
3530                    if let GraphQLTokenKind::Name(name) = &next.kind {
3531                        matches!(
3532                            name.as_ref(),
3533                            "type"
3534                                | "interface"
3535                                | "union"
3536                                | "enum"
3537                                | "scalar"
3538                                | "input"
3539                                | "directive"
3540                                | "schema"
3541                                | "extend"
3542                        )
3543                    } else {
3544                        false
3545                    }
3546                });
3547
3548                if is_type_def {
3549                    let span = self
3550                        .token_stream.peek()
3551                        .map(|t| t.span)
3552                        .unwrap_or_else(|| self.eof_span());
3553                    self.consume_token();
3554                    self.record_error(GraphQLParseError::new(
3555                        "type definition not allowed in executable document",
3556                        GraphQLParseErrorKind::WrongDocumentKind {
3557                            found: ast::DefinitionKind::TypeDefinition,
3558                            document_kind: ast::DocumentKind::Executable,
3559                        },
3560                        self.resolve_span(span),
3561                    ));
3562                    return Err(());
3563                }
3564            }
3565
3566            let span = self
3567                .token_stream.peek()
3568                .map(|t| t.span)
3569                .unwrap_or_else(|| self.eof_span());
3570            let found = self
3571                .token_stream.peek()
3572                .map(|t| Self::token_kind_display(&t.kind))
3573                .unwrap_or_else(|| "end of input".to_string());
3574            // Consume the token to ensure forward progress during error
3575            // recovery. Without this, recovery sees the unconsumed token
3576            // as a potential definition start and stops immediately,
3577            // causing an infinite loop.
3578            self.consume_token();
3579            self.record_error(GraphQLParseError::new(
3580                format!(
3581                    "expected operation or fragment definition, found `{found}`"
3582                ),
3583                GraphQLParseErrorKind::UnexpectedToken {
3584                    expected: vec![
3585                        "query".to_string(),
3586                        "mutation".to_string(),
3587                        "subscription".to_string(),
3588                        "fragment".to_string(),
3589                        "{".to_string(),
3590                    ],
3591                    found,
3592                },
3593                self.resolve_span(span),
3594            ));
3595            Err(())
3596        }
3597    }
3598
3599    /// Parses a definition for mixed documents.
3600    fn parse_mixed_definition_item(
3601        &mut self,
3602    ) -> Result<ast::Definition<'src>, ()> {
3603        // Handle lexer errors
3604        if let Some(token) = self.token_stream.peek()
3605            && let GraphQLTokenKind::Error(_) = &token.kind {
3606                let token = token.clone();
3607                self.handle_lexer_error(&token);
3608                self.consume_token();
3609                return Err(());
3610            }
3611
3612        let description = self.parse_ast_description();
3613
3614        if self.peek_is_keyword("schema") {
3615            Ok(ast::Definition::SchemaDefinition(
3616                self.parse_schema_definition(description)?,
3617            ))
3618        } else if self.peek_is_keyword("scalar") {
3619            Ok(ast::Definition::TypeDefinition(
3620                self.parse_scalar_type_definition(description)?,
3621            ))
3622        } else if self.peek_is_keyword("type") {
3623            Ok(ast::Definition::TypeDefinition(
3624                self.parse_object_type_definition(description)?,
3625            ))
3626        } else if self.peek_is_keyword("interface") {
3627            Ok(ast::Definition::TypeDefinition(
3628                self.parse_interface_type_definition(description)?,
3629            ))
3630        } else if self.peek_is_keyword("union") {
3631            Ok(ast::Definition::TypeDefinition(
3632                self.parse_union_type_definition(description)?,
3633            ))
3634        } else if self.peek_is_keyword("enum") {
3635            Ok(ast::Definition::TypeDefinition(
3636                self.parse_enum_type_definition(description)?,
3637            ))
3638        } else if self.peek_is_keyword("input") {
3639            Ok(ast::Definition::TypeDefinition(
3640                self.parse_input_object_type_definition(description)?,
3641            ))
3642        } else if self.peek_is_keyword("directive") {
3643            Ok(ast::Definition::DirectiveDefinition(
3644                self.parse_directive_definition(description)?,
3645            ))
3646        } else if self.peek_is_keyword("extend") {
3647            self.parse_type_extension()
3648        } else if self.peek_is_keyword("query")
3649            || self.peek_is_keyword("mutation")
3650            || self.peek_is_keyword("subscription")
3651            || self.peek_is(&GraphQLTokenKind::CurlyBraceOpen) {
3652            Ok(ast::Definition::OperationDefinition(
3653                self.parse_operation_definition()?,
3654            ))
3655        } else if self.peek_is_keyword("fragment") {
3656            Ok(ast::Definition::FragmentDefinition(
3657                self.parse_fragment_definition()?,
3658            ))
3659        } else {
3660            let span = self
3661                .token_stream.peek()
3662                .map(|t| t.span)
3663                .unwrap_or_else(|| self.eof_span());
3664            let found = self
3665                .token_stream.peek()
3666                .map(|t| Self::token_kind_display(&t.kind))
3667                .unwrap_or_else(|| "end of input".to_string());
3668            // Consume the token to ensure forward progress during
3669            // error recovery. Without this, recovery sees the
3670            // unconsumed token as a potential definition start and
3671            // stops immediately, causing an infinite loop.
3672            self.consume_token();
3673            self.record_error(GraphQLParseError::new(
3674                format!("expected definition, found `{found}`"),
3675                GraphQLParseErrorKind::UnexpectedToken {
3676                    expected: vec![
3677                        "type".to_string(),
3678                        "query".to_string(),
3679                        "fragment".to_string(),
3680                    ],
3681                    found,
3682                },
3683                self.resolve_span(span),
3684            ));
3685            Err(())
3686        }
3687    }
3688}