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