Skip to main content

libgraphql_parser/ast/
document.rs

1use crate::ast::ast_node::append_span_source_slice;
2use crate::ast::AstNode;
3use crate::ast::Definition;
4use crate::ByteSpan;
5use crate::SourceMap;
6use crate::SourceSpan;
7use crate::token::GraphQLTriviaToken;
8use inherent::inherent;
9
10/// Root AST node for any GraphQL document.
11///
12/// A document contains a list of [`Definition`]s which
13/// may be type-system definitions, type-system extensions,
14/// or executable definitions (operations and fragments).
15///
16/// The spec's
17/// [`Document`](https://spec.graphql.org/September2025/#sec-Document)
18/// grammar production allows both executable and
19/// type-system definitions to coexist:
20///
21/// ```text
22/// Document : Definition+
23/// Definition :
24///     ExecutableDefinition |
25///     TypeSystemDefinitionOrExtension
26/// ```
27///
28/// However, more constrained document types exist for
29/// specific use cases:
30/// [`ExecutableDocument`](https://spec.graphql.org/September2025/#ExecutableDocument)
31/// (operations and fragments only) and
32/// [`TypeSystemDocument`](https://spec.graphql.org/September2025/#TypeSystemDocument)
33/// (type-system definitions only). The spec
34/// [mandates](https://spec.graphql.org/September2025/#sec-Executable-Definitions)
35/// that a document submitted for execution must contain
36/// only executable definitions — but parsing is a separate
37/// concern from execution.
38///
39/// This AST uses a single unified `Document` type that
40/// can represent any of these document forms. This is
41/// useful because a parser library serves many tools
42/// beyond just execution services: schema linters,
43/// formatters, code generators, IDE language servers, and
44/// document merge/federation tools all benefit from being
45/// able to parse any valid GraphQL syntax without
46/// rejecting it at the parse level. Validation of which
47/// definition kinds are permitted is left to downstream
48/// consumers (e.g. an execution engine rejecting
49/// type-system definitions). The convenience methods
50/// [`schema_definitions()`](Document::schema_definitions)
51/// and
52/// [`executable_definitions()`](Document::executable_definitions)
53/// provide easy filtering when needed.
54///
55/// See
56/// [Document](https://spec.graphql.org/September2025/#sec-Document)
57/// in the spec.
58#[derive(Clone, Debug, PartialEq)]
59pub struct Document<'src> {
60    pub definitions: Vec<Definition<'src>>,
61    pub span: ByteSpan,
62    pub syntax: Option<Box<DocumentSyntax<'src>>>,
63}
64
65impl<'src> Document<'src> {
66    /// Iterate over only the executable definitions
67    /// (operations and fragments) in this document.
68    pub fn executable_definitions(
69        &self,
70    ) -> impl Iterator<Item = &Definition<'src>> {
71        self.definitions.iter().filter(|d| {
72            matches!(
73                d,
74                Definition::FragmentDefinition(_)
75                    | Definition::OperationDefinition(_)
76            )
77        })
78    }
79
80    /// Iterate over only the type-system definitions
81    /// and extensions in this document.
82    pub fn schema_definitions(
83        &self,
84    ) -> impl Iterator<Item = &Definition<'src>> {
85        self.definitions.iter().filter(|d| {
86            matches!(
87                d,
88                Definition::DirectiveDefinition(_)
89                    | Definition::SchemaDefinition(_)
90                    | Definition::SchemaExtension(_)
91                    | Definition::TypeDefinition(_)
92                    | Definition::TypeExtension(_)
93            )
94        })
95    }
96
97    /// Returns the trailing trivia tokens (whitespace,
98    /// comments) that appear after the last definition in
99    /// the document, if syntax detail was captured.
100    pub fn trailing_trivia(&self) -> Option<&[GraphQLTriviaToken<'src>]> {
101        self.syntax.as_ref().map(|s| s.trailing_trivia.as_slice())
102    }
103}
104
105// =========================================================
106// Document syntax
107// =========================================================
108
109/// Syntax detail for a [`Document`].
110#[derive(Clone, Debug, PartialEq)]
111pub struct DocumentSyntax<'src> {
112    /// Trailing trivia at end-of-file (after the last
113    /// definition). Captures whitespace, comments, etc.
114    /// that would otherwise be lost.
115    pub trailing_trivia:
116        Vec<GraphQLTriviaToken<'src>>,
117}
118
119#[inherent]
120impl AstNode for Document<'_> {
121    /// See [`AstNode::append_source()`](crate::ast::AstNode::append_source).
122    pub fn append_source(
123        &self,
124        sink: &mut String,
125        source: Option<&str>,
126    ) {
127        if let Some(src) = source {
128            append_span_source_slice(
129                self.span, sink, src,
130            );
131            // Append any trailing trivia (whitespace, comments)
132            // that follows the last definition. These fall
133            // outside the document span but are captured in the
134            // syntax node for lossless reconstruction.
135            if let Some(syntax) = &self.syntax {
136                for trivia in &syntax.trailing_trivia {
137                    let trivia_span = match trivia {
138                        GraphQLTriviaToken::Comment { span, .. }
139                        | GraphQLTriviaToken::Comma { span, .. }
140                        | GraphQLTriviaToken::Whitespace {
141                            span, ..
142                        } => span,
143                    };
144                    append_span_source_slice(
145                        *trivia_span, sink, src,
146                    );
147                }
148            }
149        }
150    }
151
152    /// Returns this document's byte-offset span within the
153    /// source text.
154    ///
155    /// The returned [`ByteSpan`] can be resolved to line/column
156    /// positions via [`source_span()`](Self::source_span) or
157    /// [`ByteSpan::resolve()`].
158    #[inline]
159    pub fn byte_span(&self) -> ByteSpan {
160        self.span
161    }
162
163    /// Resolves this document's position to line/column
164    /// coordinates using the given [`SourceMap`].
165    ///
166    /// Returns [`None`] if the byte offsets cannot be resolved
167    /// (e.g. the span was synthetically constructed without
168    /// valid position data).
169    #[inline]
170    pub fn source_span(
171        &self,
172        source_map: &SourceMap,
173    ) -> Option<SourceSpan> {
174        self.byte_span().resolve(source_map)
175    }
176}
177