Skip to main content

apollo_compiler/executable/
mod.rs

1//! High-level representation of an executable document,
2//! which can contain operations and fragments.
3//!
4//! Compared to an [`ast::Document`] which follows closely the structure of GraphQL syntax,
5//! an [`ExecutableDocument`] interpreted in the context of a valid [`Schema`]
6//! and contains typing information.
7//!
8//! In some cases like [`SelectionSet`], this module and the [`ast`] module
9//! define different Rust types with the same names.
10//! In other cases like [`Directive`] there is no data structure difference needed,
11//! so this module reuses and publicly re-exports some Rust types from the [`ast`] module.
12//!
13//! ## “Build” errors
14//!
15//! As a result of how `ExecutableDocument` containing typing information,
16//! not all AST documents (even if filtering out type system definitions) can be fully represented:
17//! creating a `ExecutableDocument` can cause errors (on top of any potential syntax error)
18//! for cases like selecting a field not defined in the schema.
19//!
20//! When such errors (or in [`ExecutableDocument::parse`], syntax errors) happen,
21//! a partial document is returned together with a list of diagnostics.
22//!
23//! ## Structural sharing and mutation
24//!
25//! Like in AST, many parts of a `ExecutableDocument` are reference-counted with [`Node`].
26//! This allows sharing nodes between documents without cloning entire subtrees.
27//! To modify a node, the [`make_mut`][Node::make_mut] method provides copy-on-write semantics.
28//!
29//! ## Validation
30//!
31//! The [Validation] section of the GraphQL specification defines validation rules
32//! beyond syntax errors and errors detected while constructing a `ExecutableDocument`.
33//! The [`validate`][ExecutableDocument::validate] method returns either:
34//!
35//! * An immutable [`Valid<ExecutableDocument>`] type wrapper, or
36//! * The document together with a list of diagnostics
37//!
38//! If there is no mutation needed between parsing and validation,
39//! [`ExecutableDocument::parse_and_validate`] does both in one step.
40//!
41//! [Validation]: https://spec.graphql.org/September2025/#sec-Validation
42//!
43//! ## Serialization
44//!
45//! `ExecutableDocument` and other types types implement [`Display`][std::fmt::Display]
46//! and [`ToString`] by serializing to GraphQL syntax with a default configuration.
47//! [`serialize`][ExecutableDocument::serialize] methods return a builder
48//! that has chaining methods for setting serialization configuration,
49//! and also implements `Display` and `ToString`.
50
51use crate::ast;
52use crate::collections::IndexMap;
53use crate::coordinate::FieldArgumentCoordinate;
54use crate::coordinate::TypeAttributeCoordinate;
55use crate::parser::Parser;
56use crate::parser::SourceMap;
57use crate::parser::SourceSpan;
58use crate::schema;
59use crate::validation::DiagnosticList;
60use crate::validation::Valid;
61use crate::validation::WithErrors;
62use crate::Node;
63use crate::Schema;
64use indexmap::map::Entry;
65use std::fmt;
66use std::path::Path;
67use std::sync::Arc;
68
69pub(crate) mod from_ast;
70mod serialize;
71pub(crate) mod validation;
72
73pub use self::from_ast::ExecutableDocumentBuilder;
74pub use crate::ast::Argument;
75use crate::ast::ArgumentByNameError;
76pub use crate::ast::Directive;
77pub use crate::ast::DirectiveList;
78pub use crate::ast::NamedType;
79pub use crate::ast::OperationType;
80pub use crate::ast::Type;
81pub use crate::ast::Value;
82pub use crate::ast::VariableDefinition;
83use crate::collections::HashSet;
84use crate::request::RequestError;
85pub use crate::Name;
86
87/// Executable definitions, annotated with type information
88#[derive(Debug, Clone, Default)]
89pub struct ExecutableDocument {
90    /// If this document was originally parsed from a source file,
91    /// this map contains one entry for that file and its ID.
92    ///
93    /// The document may have been modified since.
94    pub sources: SourceMap,
95
96    pub operations: OperationMap,
97    pub fragments: FragmentMap,
98}
99
100/// Operations definitions for a given executable document
101#[derive(Debug, Clone, Default, PartialEq)]
102pub struct OperationMap {
103    pub anonymous: Option<Node<Operation>>,
104    pub named: IndexMap<Name, Node<Operation>>,
105}
106
107/// Definitions of named fragments for a given executable document
108pub type FragmentMap = IndexMap<Name, Node<Fragment>>;
109
110/// FieldSet information created for FieldSet parsing in `@requires` directive.
111/// Annotated with type information.
112#[derive(Debug, Clone)]
113pub struct FieldSet {
114    /// If this document was originally parsed from a source file,
115    /// this map contains one entry for that file and its ID.
116    ///
117    /// The document may have been modified since.
118    pub sources: SourceMap,
119
120    pub selection_set: SelectionSet,
121}
122
123/// An [_OperationDefinition_](https://spec.graphql.org/September2025/#OperationDefinition)
124/// annotated with type information.
125#[derive(Debug, Clone, PartialEq, Eq)]
126pub struct Operation {
127    pub description: Option<Node<str>>,
128    pub operation_type: OperationType,
129    pub name: Option<Name>,
130    pub variables: Vec<Node<VariableDefinition>>,
131    pub directives: DirectiveList,
132    pub selection_set: SelectionSet,
133}
134
135/// A [_FragmentDefinition_](https://spec.graphql.org/September2025/#FragmentDefinition)
136/// annotated with type information.
137#[derive(Debug, Clone, PartialEq, Eq)]
138pub struct Fragment {
139    pub description: Option<Node<str>>,
140    pub name: Name,
141    pub directives: DirectiveList,
142    pub selection_set: SelectionSet,
143}
144
145/// A [_SelectionSet_](https://spec.graphql.org/September2025/#SelectionSet)
146/// annotated with type information.
147#[derive(Debug, Clone, PartialEq, Eq, Hash)]
148pub struct SelectionSet {
149    pub ty: NamedType,
150    pub selections: Vec<Selection>,
151}
152
153/// A [_Selection_](https://spec.graphql.org/September2025/#Selection)
154/// annotated with type information.
155#[derive(Debug, Clone, PartialEq, Eq, Hash)]
156pub enum Selection {
157    Field(Node<Field>),
158    FragmentSpread(Node<FragmentSpread>),
159    InlineFragment(Node<InlineFragment>),
160}
161
162/// A [_Field_](https://spec.graphql.org/September2025/#Field) selection,
163/// linked to the corresponding field definition in the schema.
164#[derive(Debug, Clone, PartialEq, Eq, Hash)]
165pub struct Field {
166    /// The definition of this field in an object type or interface type definition in the schema
167    pub definition: Node<schema::FieldDefinition>,
168    pub alias: Option<Name>,
169    pub name: Name,
170    pub arguments: Vec<Node<Argument>>,
171    pub directives: DirectiveList,
172    pub selection_set: SelectionSet,
173}
174
175/// A [_FragmentSpread_](https://spec.graphql.org/September2025/#FragmentSpread)
176/// annotated with type information.
177#[derive(Debug, Clone, PartialEq, Eq, Hash)]
178pub struct FragmentSpread {
179    pub fragment_name: Name,
180    pub directives: DirectiveList,
181}
182
183/// A [_InlineFragment_](https://spec.graphql.org/September2025/#InlineFragment)
184/// annotated with type information.
185#[derive(Debug, Clone, PartialEq, Eq, Hash)]
186pub struct InlineFragment {
187    pub type_condition: Option<NamedType>,
188    pub directives: DirectiveList,
189    pub selection_set: SelectionSet,
190}
191
192/// Errors that can occur during conversion from AST to executable document or
193/// validation of an executable document.
194#[derive(thiserror::Error, Debug, Clone)]
195pub(crate) enum BuildError {
196    #[error("an executable document must not contain {describe}")]
197    TypeSystemDefinition {
198        name: Option<Name>,
199        describe: &'static str,
200    },
201
202    #[error("anonymous operation cannot be selected when the document contains other operations")]
203    AmbiguousAnonymousOperation,
204
205    #[error(
206        "the operation `{name_at_previous_location}` is defined multiple times in the document"
207    )]
208    OperationNameCollision { name_at_previous_location: Name },
209
210    #[error(
211        "the fragment `{name_at_previous_location}` is defined multiple times in the document"
212    )]
213    FragmentNameCollision { name_at_previous_location: Name },
214
215    #[error("`{operation_type}` root operation type is not defined")]
216    UndefinedRootOperation { operation_type: &'static str },
217
218    #[error(
219        "type condition `{type_name}` of fragment `{fragment_name}` \
220         is not a type defined in the schema"
221    )]
222    UndefinedTypeInNamedFragmentTypeCondition {
223        type_name: NamedType,
224        fragment_name: Name,
225    },
226
227    #[error("type condition `{type_name}` of inline fragment is not a type defined in the schema")]
228    UndefinedTypeInInlineFragmentTypeCondition {
229        type_name: NamedType,
230        path: SelectionPath,
231    },
232
233    #[error("field selection of scalar type `{type_name}` must not have subselections")]
234    SubselectionOnScalarType {
235        type_name: NamedType,
236        path: SelectionPath,
237    },
238
239    #[error("field selection of enum type `{type_name}` must not have subselections")]
240    SubselectionOnEnumType {
241        type_name: NamedType,
242        path: SelectionPath,
243    },
244
245    #[error("type `{type_name}` does not have a field `{field_name}`")]
246    UndefinedField {
247        type_name: NamedType,
248        field_name: Name,
249        path: SelectionPath,
250    },
251
252    // Validation errors
253    #[error(
254        "{} can only have one root field",
255        subscription_name_or_anonymous(name)
256    )]
257    SubscriptionUsesMultipleFields {
258        name: Option<Name>,
259        fields: Vec<Name>,
260    },
261
262    #[error(
263        "{} can not have an introspection field as a root field",
264        subscription_name_or_anonymous(name)
265    )]
266    SubscriptionUsesIntrospection {
267        /// Name of the operation
268        name: Option<Name>,
269        /// Name of the introspection field
270        field: Name,
271    },
272    #[error(
273        "{} can not specify @skip or @include on root fields",
274        subscription_name_or_anonymous(name)
275    )]
276    SubscriptionUsesConditionalSelection {
277        /// Name of the operation
278        name: Option<Name>,
279    },
280
281    #[error("`@defer` label `{label}` is not unique within the document")]
282    DuplicateDeferLabel {
283        label: String,
284        original_location: Option<SourceSpan>,
285    },
286
287    #[error("`@defer` label argument must be a static String, not a variable")]
288    DeferLabelMustNotBeVariable,
289
290    #[error(
291        "`@defer` is not allowed on root selections of {} operations",
292        operation_type.name()
293    )]
294    DeferOnRootMutationOrSubscriptionField { operation_type: OperationType },
295
296    #[error("`@defer` in a subscription operation must be disabled with an `if` argument")]
297    DeferInSubscriptionMustBeConditional,
298
299    #[error("{0}")]
300    ConflictingFieldType(Box<ConflictingFieldType>),
301    #[error("{0}")]
302    ConflictingFieldArgument(Box<ConflictingFieldArgument>),
303    #[error("{0}")]
304    ConflictingFieldName(Box<ConflictingFieldName>),
305}
306
307#[derive(thiserror::Error, Debug, Clone)]
308#[error("operation must not select different types using the same name `{alias}`")]
309pub(crate) struct ConflictingFieldType {
310    /// Name or alias of the non-unique field.
311    pub(crate) alias: Name,
312    pub(crate) original_location: Option<SourceSpan>,
313    pub(crate) original_coordinate: TypeAttributeCoordinate,
314    pub(crate) original_type: Type,
315    pub(crate) conflicting_location: Option<SourceSpan>,
316    pub(crate) conflicting_coordinate: TypeAttributeCoordinate,
317    pub(crate) conflicting_type: Type,
318}
319
320#[derive(thiserror::Error, Debug, Clone)]
321#[error("operation must not provide conflicting field arguments for the same name `{alias}`")]
322pub(crate) struct ConflictingFieldArgument {
323    /// Name or alias of the non-unique field.
324    pub(crate) alias: Name,
325    pub(crate) original_location: Option<SourceSpan>,
326    pub(crate) original_coordinate: FieldArgumentCoordinate,
327    pub(crate) original_value: Option<Value>,
328    pub(crate) conflicting_location: Option<SourceSpan>,
329    pub(crate) conflicting_coordinate: FieldArgumentCoordinate,
330    pub(crate) conflicting_value: Option<Value>,
331}
332
333#[derive(thiserror::Error, Debug, Clone)]
334#[error("cannot select different fields into the same alias `{alias}`")]
335pub(crate) struct ConflictingFieldName {
336    /// Name of the non-unique field.
337    pub(crate) alias: Name,
338    pub(crate) original_location: Option<SourceSpan>,
339    pub(crate) original_selection: TypeAttributeCoordinate,
340    pub(crate) conflicting_location: Option<SourceSpan>,
341    pub(crate) conflicting_selection: TypeAttributeCoordinate,
342}
343
344fn subscription_name_or_anonymous(name: &Option<Name>) -> impl std::fmt::Display + '_ {
345    crate::validation::diagnostics::NameOrAnon {
346        name: name.as_ref(),
347        if_some_prefix: "subscription",
348        if_none: "anonymous subscription",
349    }
350}
351
352#[derive(Debug, Clone, PartialEq, Eq)]
353pub(crate) struct SelectionPath {
354    pub(crate) root: ExecutableDefinitionName,
355    pub(crate) nested_fields: Vec<Name>,
356}
357
358/// Designates by name a top-level definition in an executable document
359#[derive(Debug, Clone, PartialEq, Eq)]
360pub(crate) enum ExecutableDefinitionName {
361    AnonymousOperation(ast::OperationType),
362    NamedOperation(ast::OperationType, Name),
363    Fragment(Name),
364}
365
366impl ExecutableDocument {
367    /// Create an empty document, to be filled programatically
368    pub fn new() -> Self {
369        Self::default()
370    }
371
372    /// Returns a new builder for creating an ExecutableDocument from multiple AST documents.
373    ///
374    /// The builder allows you to parse and combine executable definitions (operations and fragments)
375    /// from multiple source files into a single [`ExecutableDocument`].
376    ///
377    /// # Example
378    ///
379    /// ```rust
380    /// use apollo_compiler::{Schema, ExecutableDocument};
381    /// use apollo_compiler::parser::Parser;
382    /// use apollo_compiler::validation::DiagnosticList;
383    /// # let schema_src = "type Query { user: User, post: Post } type User { id: ID } type Post { title: String }";
384    /// # let schema = Schema::parse_and_validate(schema_src, "schema.graphql").unwrap();
385    ///
386    /// let mut errors = DiagnosticList::new(Default::default());  
387    /// let doc = ExecutableDocument::builder(Some(&schema), &mut errors)  
388    ///     .parse("query GetUser { user { id } }", "query1.graphql")  
389    ///     .parse("query GetMore { user { id } }", "query2.graphql")  
390    ///     .build();  
391    ///  
392    /// assert!(errors.is_empty());  
393    /// assert_eq!(doc.operations.named.len(), 2);  
394    pub fn builder<'schema, 'errors>(
395        schema: Option<&'schema Valid<Schema>>,
396        errors: &'errors mut DiagnosticList,
397    ) -> from_ast::ExecutableDocumentBuilder<'schema, 'errors> {
398        from_ast::ExecutableDocumentBuilder::new(schema.map(|s| s.as_ref()), errors)
399    }
400
401    /// Parse an executable document with the default configuration.
402    ///
403    /// `path` is the filesystem path (or arbitrary string) used in diagnostics
404    /// to identify this source file to users.
405    ///
406    /// Create a [`Parser`] to use different parser configuration.
407    #[allow(clippy::result_large_err)] // Typically not called very often
408    pub fn parse(
409        schema: &Valid<Schema>,
410        source_text: impl Into<String>,
411        path: impl AsRef<Path>,
412    ) -> Result<Self, WithErrors<Self>> {
413        Parser::new().parse_executable(schema, source_text, path)
414    }
415
416    /// [`parse`][Self::parse] then [`validate`][Self::validate],
417    /// to get a `Valid<ExecutableDocument>` when mutating it isn’t needed.
418    #[allow(clippy::result_large_err)] // Typically not called very often
419    pub fn parse_and_validate(
420        schema: &Valid<Schema>,
421        source_text: impl Into<String>,
422        path: impl AsRef<Path>,
423    ) -> Result<Valid<Self>, WithErrors<Self>> {
424        let (doc, mut errors) = Parser::new().parse_executable_inner(schema, source_text, path);
425        Arc::make_mut(&mut errors.sources)
426            .extend(schema.sources.iter().map(|(k, v)| (*k, v.clone())));
427        validation::validate_executable_document(&mut errors, schema, &doc);
428        errors.into_valid_result(doc)
429    }
430
431    #[allow(clippy::result_large_err)] // Typically not called very often
432    pub fn validate(self, schema: &Valid<Schema>) -> Result<Valid<Self>, WithErrors<Self>> {
433        let mut sources = IndexMap::clone(&schema.sources);
434        sources.extend(self.sources.iter().map(|(k, v)| (*k, v.clone())));
435        let mut errors = DiagnosticList::new(Arc::new(sources));
436        validation::validate_executable_document(&mut errors, schema, &self);
437        errors.into_valid_result(self)
438    }
439
440    serialize_method!();
441}
442
443impl Eq for ExecutableDocument {}
444
445/// `sources` and `build_errors` are ignored for comparison
446impl PartialEq for ExecutableDocument {
447    fn eq(&self, other: &Self) -> bool {
448        let Self {
449            sources: _,
450            operations,
451            fragments,
452        } = self;
453        *operations == other.operations && *fragments == other.fragments
454    }
455}
456
457impl OperationMap {
458    /// Creates a new `OperationMap` containing one operation
459    pub fn from_one(operation: impl Into<Node<Operation>>) -> Self {
460        let mut map = Self::default();
461        map.insert(operation);
462        map
463    }
464
465    pub fn is_empty(&self) -> bool {
466        self.anonymous.is_none() && self.named.is_empty()
467    }
468
469    pub fn len(&self) -> usize {
470        self.anonymous.is_some() as usize + self.named.len()
471    }
472
473    /// Returns an iterator of operations, both anonymous and named
474    pub fn iter(&self) -> impl Iterator<Item = &'_ Node<Operation>> {
475        self.anonymous
476            .as_ref()
477            .into_iter()
478            .chain(self.named.values())
479    }
480
481    /// Return the relevant operation for a request, or a request error
482    ///
483    /// This is the [_GetOperation()_](https://spec.graphql.org/September2025/#GetOperation())
484    /// algorithm in the _Executing Requests_ section of the specification.
485    ///
486    /// A GraphQL request comes with a document (which may contain multiple operations)
487    /// an an optional operation name. When a name is given the request executes the operation
488    /// with that name, which is expected to exist. When it is not given / null / `None`,
489    /// the document is expected to contain a single operation (which may or may not be named)
490    /// to avoid ambiguity.
491    pub fn get(&self, name_request: Option<&str>) -> Result<&Node<Operation>, RequestError> {
492        if let Some(name) = name_request {
493            // Honor the request
494            self.named
495                .get(name)
496                .ok_or_else(|| format!("No operation named '{name}'"))
497        } else {
498            // No name request (`operationName` unspecified or null)
499            if let Some(op) = &self.anonymous {
500                // Return the anonymous operation if it’s the only operation
501                self.named.is_empty().then_some(op)
502            } else {
503                // No anonymous operation, return a named operation if it’s the only one
504                self.named
505                    .values()
506                    .next()
507                    .and_then(|op| (self.named.len() == 1).then_some(op))
508            }
509            .ok_or_else(|| {
510                "Ambiguous request: multiple operations but no specified `operationName`".to_owned()
511            })
512        }
513        .map_err(|message| RequestError {
514            message,
515            location: None,
516            is_suspected_validation_bug: false,
517        })
518    }
519
520    /// Similar to [`get`][Self::get] but returns a mutable reference.
521    pub fn get_mut(&mut self, name_request: Option<&str>) -> Result<&mut Operation, RequestError> {
522        if let Some(name) = name_request {
523            // Honor the request
524            self.named
525                .get_mut(name)
526                .ok_or_else(|| format!("No operation named '{name}'"))
527        } else {
528            // No name request (`operationName` unspecified or null)
529            if let Some(op) = &mut self.anonymous {
530                // Return the anonymous operation if it’s the only operation
531                self.named.is_empty().then_some(op)
532            } else {
533                // No anonymous operation, return a named operation if it’s the only one
534                let len = self.named.len();
535                self.named
536                    .values_mut()
537                    .next()
538                    .and_then(|op| (len == 1).then_some(op))
539            }
540            .ok_or_else(|| {
541                "Ambiguous request: multiple operations but no specified `operationName`".to_owned()
542            })
543        }
544        .map(Node::make_mut)
545        .map_err(|message| RequestError {
546            message,
547            location: None,
548            is_suspected_validation_bug: false,
549        })
550    }
551
552    /// Insert the given operation in either `named_operations` or `anonymous_operation`
553    /// as appropriate, and return the old operation (if any) with that name (or lack thereof).
554    pub fn insert(&mut self, operation: impl Into<Node<Operation>>) -> Option<Node<Operation>> {
555        let operation = operation.into();
556        if let Some(name) = &operation.name {
557            self.named.insert(name.clone(), operation)
558        } else {
559            self.anonymous.replace(operation)
560        }
561    }
562}
563
564impl Operation {
565    /// Returns the name of the schema type this operation selects against.
566    pub fn object_type(&self) -> &NamedType {
567        &self.selection_set.ty
568    }
569
570    /// Returns true if this is a query operation.
571    pub fn is_query(&self) -> bool {
572        self.operation_type == OperationType::Query
573    }
574
575    /// Returns true if this is a mutation operation.
576    pub fn is_mutation(&self) -> bool {
577        self.operation_type == OperationType::Mutation
578    }
579
580    /// Returns true if this is a subscription operation.
581    pub fn is_subscription(&self) -> bool {
582        self.operation_type == OperationType::Subscription
583    }
584
585    /// Return whether this operation is a query that only selects introspection meta-fields:
586    /// `__type`, `__schema`, and `__typename`
587    pub fn is_introspection(&self, document: &ExecutableDocument) -> bool {
588        self.is_query()
589            && self
590                .root_fields(document)
591                .all(|field| matches!(field.name.as_str(), "__type" | "__schema" | "__typename"))
592    }
593
594    /// Returns an iterator of field selections that are at the root of the response.
595    /// That is, inline fragments and fragment spreads at the root are traversed,
596    /// but field sub-selections are not.
597    ///
598    /// See also [`all_fields`][Self::all_fields].
599    ///
600    /// `document` is used to look up fragment definitions.
601    ///
602    /// This does **not** perform [field merging],
603    /// so multiple items in this iterator may have the same response name
604    /// or point to the same field definition.
605    /// Named fragments however are only traversed once even if spread multiple times.
606    ///
607    /// [field merging]: https://spec.graphql.org/September2025/#sec-Field-Selection-Merging
608    pub fn root_fields<'doc>(
609        &'doc self,
610        document: &'doc ExecutableDocument,
611    ) -> impl Iterator<Item = &'doc Node<Field>> {
612        self.selection_set.root_fields(document)
613    }
614
615    /// Returns an iterator of all field selections in this operation.
616    ///
617    /// See also [`root_fields`][Self::root_fields].
618    ///
619    /// `document` is used to look up fragment definitions.
620    ///
621    /// This does **not** perform [field merging],
622    /// so multiple items in this iterator may have the same response name
623    /// or point to the same field definition.
624    /// Named fragments however are only traversed once even if spread multiple times.
625    ///
626    /// [field merging]: https://spec.graphql.org/September2025/#sec-Field-Selection-Merging
627    pub fn all_fields<'doc>(
628        &'doc self,
629        document: &'doc ExecutableDocument,
630    ) -> impl Iterator<Item = &'doc Node<Field>> {
631        self.selection_set.all_fields(document)
632    }
633
634    serialize_method!();
635}
636
637impl Fragment {
638    pub fn type_condition(&self) -> &NamedType {
639        &self.selection_set.ty
640    }
641
642    serialize_method!();
643}
644
645impl SelectionSet {
646    /// Create a new selection set
647    pub fn new(ty: NamedType) -> Self {
648        Self {
649            ty,
650            selections: Vec::new(),
651        }
652    }
653
654    pub fn is_empty(&self) -> bool {
655        self.selections.is_empty()
656    }
657
658    pub fn push(&mut self, selection: impl Into<Selection>) {
659        self.selections.push(selection.into())
660    }
661
662    pub fn extend(&mut self, selections: impl IntoIterator<Item = impl Into<Selection>>) {
663        self.selections
664            .extend(selections.into_iter().map(|sel| sel.into()))
665    }
666
667    /// Create a new field to be added to this selection set with [`push`][Self::push]
668    ///
669    /// Returns an error if the type of this selection set is not defined
670    /// or does not have a field named `name`.
671    pub fn new_field<'schema>(
672        &self,
673        schema: &'schema Schema,
674        name: Name,
675    ) -> Result<Field, schema::FieldLookupError<'schema>> {
676        let definition = schema.type_field(&self.ty, &name)?.node.clone();
677        Ok(Field::new(name, definition))
678    }
679
680    /// Create a new inline fragment to be added to this selection set with [`push`][Self::push]
681    pub fn new_inline_fragment(&self, opt_type_condition: Option<NamedType>) -> InlineFragment {
682        if let Some(type_condition) = opt_type_condition {
683            InlineFragment::with_type_condition(type_condition)
684        } else {
685            InlineFragment::without_type_condition(self.ty.clone())
686        }
687    }
688
689    /// Create a new fragment spread to be added to this selection set with [`push`][Self::push]
690    pub fn new_fragment_spread(&self, fragment_name: Name) -> FragmentSpread {
691        FragmentSpread::new(fragment_name)
692    }
693
694    /// Returns an iterator of field selections directly in this selection set.
695    ///
696    /// Does not recur into inline fragments or fragment spreads.
697    pub fn fields(&self) -> impl Iterator<Item = &Node<Field>> {
698        self.selections.iter().filter_map(|sel| sel.as_field())
699    }
700
701    /// Returns an iterator of field selections that are at the root of the response.
702    /// That is, inline fragments and fragment spreads at the root are traversed,
703    /// but field sub-selections are not.
704    ///
705    /// See also [`all_fields`][Self::all_fields].
706    ///
707    /// `document` is used to look up fragment definitions.
708    ///
709    /// This does **not** perform [field merging],
710    /// so multiple items in this iterator may have the same response name
711    /// or point to the same field definition.
712    /// Named fragments however are only traversed once even if spread multiple times.
713    ///
714    /// [field merging]: https://spec.graphql.org/September2025/#sec-Field-Selection-Merging
715    pub fn root_fields<'doc>(
716        &'doc self,
717        document: &'doc ExecutableDocument,
718    ) -> impl Iterator<Item = &'doc Node<Field>> {
719        let mut stack = vec![self.selections.iter()];
720        let mut fragments_seen = HashSet::default();
721        std::iter::from_fn(move || {
722            while let Some(selection_set_iter) = stack.last_mut() {
723                match selection_set_iter.next() {
724                    Some(Selection::Field(field)) => {
725                        // Yield one item from the `root_fields()` iterator
726                        // but ignore its sub-selections in `field.selection_set`
727                        return Some(field);
728                    }
729                    Some(Selection::InlineFragment(inline)) => {
730                        stack.push(inline.selection_set.selections.iter())
731                    }
732                    Some(Selection::FragmentSpread(spread)) => {
733                        if let Some(def) = document.fragments.get(&spread.fragment_name) {
734                            let new = fragments_seen.insert(&spread.fragment_name);
735                            if new {
736                                stack.push(def.selection_set.selections.iter())
737                            }
738                        } else {
739                            // Undefined fragments are silently ignored.
740                            // They should never happen in a valid document.
741                        }
742                    }
743                    None => {
744                        // Remove an empty iterator from the stack
745                        // and continue with the parent selection set
746                        stack.pop();
747                    }
748                }
749            }
750            None
751        })
752    }
753
754    /// Returns an iterator of all field selections in this operation.
755    ///
756    /// See also [`root_fields`][Self::root_fields].
757    ///
758    /// `document` is used to look up fragment definitions.
759    ///
760    /// This does **not** perform [field merging],
761    /// so multiple items in this iterator may have the same response name
762    /// or point to the same field definition.
763    /// Named fragments however are only traversed once even if spread multiple times.
764    ///
765    /// [field merging]: https://spec.graphql.org/September2025/#sec-Field-Selection-Merging
766    pub fn all_fields<'doc>(
767        &'doc self,
768        document: &'doc ExecutableDocument,
769    ) -> impl Iterator<Item = &'doc Node<Field>> {
770        let mut stack = vec![self.selections.iter()];
771        let mut fragments_seen = HashSet::default();
772        std::iter::from_fn(move || {
773            while let Some(selection_set_iter) = stack.last_mut() {
774                match selection_set_iter.next() {
775                    Some(Selection::Field(field)) => {
776                        if !field.selection_set.is_empty() {
777                            // Will be considered for the next call
778                            stack.push(field.selection_set.selections.iter())
779                        }
780                        // Yield one item from the `all_fields()` iterator
781                        return Some(field);
782                    }
783                    Some(Selection::InlineFragment(inline)) => {
784                        stack.push(inline.selection_set.selections.iter())
785                    }
786                    Some(Selection::FragmentSpread(spread)) => {
787                        if let Some(def) = document.fragments.get(&spread.fragment_name) {
788                            let new = fragments_seen.insert(&spread.fragment_name);
789                            if new {
790                                stack.push(def.selection_set.selections.iter())
791                            }
792                        } else {
793                            // Undefined fragments are silently ignored.
794                            // They should never happen in a valid document.
795                        }
796                    }
797                    None => {
798                        // Remove an empty iterator from the stack
799                        // and continue with the parent selection set
800                        stack.pop();
801                    }
802                }
803            }
804            None
805        })
806    }
807
808    serialize_method!();
809}
810
811impl Selection {
812    pub fn directives(&self) -> &DirectiveList {
813        match self {
814            Self::Field(sel) => &sel.directives,
815            Self::FragmentSpread(sel) => &sel.directives,
816            Self::InlineFragment(sel) => &sel.directives,
817        }
818    }
819
820    pub fn as_field(&self) -> Option<&Node<Field>> {
821        if let Self::Field(field) = self {
822            Some(field)
823        } else {
824            None
825        }
826    }
827
828    pub fn as_inline_fragment(&self) -> Option<&Node<InlineFragment>> {
829        if let Self::InlineFragment(inline) = self {
830            Some(inline)
831        } else {
832            None
833        }
834    }
835
836    pub fn as_fragment_spread(&self) -> Option<&Node<FragmentSpread>> {
837        if let Self::FragmentSpread(spread) = self {
838            Some(spread)
839        } else {
840            None
841        }
842    }
843
844    serialize_method!();
845}
846
847impl From<Node<Field>> for Selection {
848    fn from(node: Node<Field>) -> Self {
849        Self::Field(node)
850    }
851}
852
853impl From<Node<InlineFragment>> for Selection {
854    fn from(node: Node<InlineFragment>) -> Self {
855        Self::InlineFragment(node)
856    }
857}
858
859impl From<Node<FragmentSpread>> for Selection {
860    fn from(node: Node<FragmentSpread>) -> Self {
861        Self::FragmentSpread(node)
862    }
863}
864
865impl From<Field> for Selection {
866    fn from(value: Field) -> Self {
867        Self::Field(Node::new(value))
868    }
869}
870
871impl From<InlineFragment> for Selection {
872    fn from(value: InlineFragment) -> Self {
873        Self::InlineFragment(Node::new(value))
874    }
875}
876
877impl From<FragmentSpread> for Selection {
878    fn from(value: FragmentSpread) -> Self {
879        Self::FragmentSpread(Node::new(value))
880    }
881}
882
883impl Field {
884    /// Create a new field with the given name and type.
885    ///
886    /// See [`SelectionSet::new_field`] too look up the type in a schema instead.
887    pub fn new(name: Name, definition: Node<schema::FieldDefinition>) -> Self {
888        let selection_set = SelectionSet::new(definition.ty.inner_named_type().clone());
889        Field {
890            definition,
891            alias: None,
892            name,
893            arguments: Vec::new(),
894            directives: DirectiveList::new(),
895            selection_set,
896        }
897    }
898
899    pub fn with_alias(mut self, alias: Name) -> Self {
900        self.alias = Some(alias);
901        self
902    }
903
904    pub fn with_opt_alias(mut self, alias: Option<Name>) -> Self {
905        self.alias = alias;
906        self
907    }
908
909    pub fn with_directive(mut self, directive: impl Into<Node<Directive>>) -> Self {
910        self.directives.push(directive.into());
911        self
912    }
913
914    pub fn with_directives(
915        mut self,
916        directives: impl IntoIterator<Item = Node<Directive>>,
917    ) -> Self {
918        self.directives.extend(directives);
919        self
920    }
921
922    pub fn with_argument(mut self, name: Name, value: impl Into<Node<Value>>) -> Self {
923        self.arguments.push((name, value).into());
924        self
925    }
926
927    pub fn with_arguments(mut self, arguments: impl IntoIterator<Item = Node<Argument>>) -> Self {
928        self.arguments.extend(arguments);
929        self
930    }
931
932    pub fn with_selection(mut self, selection: impl Into<Selection>) -> Self {
933        self.selection_set.push(selection);
934        self
935    }
936
937    pub fn with_selections(
938        mut self,
939        selections: impl IntoIterator<Item = impl Into<Selection>>,
940    ) -> Self {
941        self.selection_set.extend(selections);
942        self
943    }
944
945    /// Returns the response name for this field: the alias if there is one, or the name
946    pub fn response_name(&self) -> &Name {
947        self.alias.as_ref().unwrap_or(&self.name)
948    }
949
950    /// The type of this field, from the field definition
951    pub fn ty(&self) -> &Type {
952        &self.definition.ty
953    }
954
955    /// Look up in `schema` the definition of the inner type of this field.
956    ///
957    /// The inner type is [`ty()`][Self::ty] after unwrapping non-null and list markers.
958    pub fn inner_type_def<'a>(&self, schema: &'a Schema) -> Option<&'a schema::ExtendedType> {
959        schema.types.get(self.ty().inner_named_type())
960    }
961
962    /// Returns the value of the argument named `name`, accounting for nullability
963    /// and for the default value in `schema`’s directive definition.
964    pub fn argument_by_name(&self, name: &str) -> Result<&Node<Value>, ArgumentByNameError> {
965        Argument::argument_by_name(&self.arguments, name, || {
966            self.definition
967                .argument_by_name(name)
968                .ok_or(ArgumentByNameError::NoSuchArgument)
969        })
970    }
971
972    /// Returns the value of the argument named `name`, as specified in the field selection.
973    ///
974    /// Returns `None` if the field selection does not specify this argument.
975    ///
976    /// If the field definition makes this argument nullable or defines a default value,
977    /// consider using [`argument_by_name`][Self::argument_by_name] instead.
978    pub fn specified_argument_by_name(&self, name: &str) -> Option<&Node<Value>> {
979        Argument::specified_argument_by_name(&self.arguments, name)
980    }
981
982    serialize_method!();
983}
984
985impl InlineFragment {
986    pub fn with_type_condition(type_condition: NamedType) -> Self {
987        let selection_set = SelectionSet::new(type_condition.clone());
988        Self {
989            type_condition: Some(type_condition),
990            directives: DirectiveList::new(),
991            selection_set,
992        }
993    }
994
995    pub fn without_type_condition(parent_selection_set_type: NamedType) -> Self {
996        Self {
997            type_condition: None,
998            directives: DirectiveList::new(),
999            selection_set: SelectionSet::new(parent_selection_set_type),
1000        }
1001    }
1002
1003    pub fn with_directive(mut self, directive: impl Into<Node<Directive>>) -> Self {
1004        self.directives.push(directive.into());
1005        self
1006    }
1007
1008    pub fn with_directives(
1009        mut self,
1010        directives: impl IntoIterator<Item = Node<Directive>>,
1011    ) -> Self {
1012        self.directives.extend(directives);
1013        self
1014    }
1015
1016    pub fn with_selection(mut self, selection: impl Into<Selection>) -> Self {
1017        self.selection_set.push(selection);
1018        self
1019    }
1020
1021    pub fn with_selections(
1022        mut self,
1023        selections: impl IntoIterator<Item = impl Into<Selection>>,
1024    ) -> Self {
1025        self.selection_set.extend(selections);
1026        self
1027    }
1028
1029    serialize_method!();
1030}
1031
1032impl FragmentSpread {
1033    pub fn new(fragment_name: Name) -> Self {
1034        Self {
1035            fragment_name,
1036            directives: DirectiveList::new(),
1037        }
1038    }
1039
1040    pub fn with_directive(mut self, directive: impl Into<Node<Directive>>) -> Self {
1041        self.directives.push(directive.into());
1042        self
1043    }
1044
1045    pub fn with_directives(
1046        mut self,
1047        directives: impl IntoIterator<Item = Node<Directive>>,
1048    ) -> Self {
1049        self.directives.extend(directives);
1050        self
1051    }
1052
1053    pub fn fragment_def<'a>(&self, document: &'a ExecutableDocument) -> Option<&'a Node<Fragment>> {
1054        document.fragments.get(&self.fragment_name)
1055    }
1056
1057    serialize_method!();
1058}
1059
1060impl FieldSet {
1061    /// Parse the given source a selection set with optional outer brackets.
1062    ///
1063    /// `path` is the filesystem path (or arbitrary string) used in diagnostics
1064    /// to identify this source file to users.
1065    ///
1066    /// Create a [`Parser`] to use different parser configuration.
1067    pub fn parse(
1068        schema: &Valid<Schema>,
1069        type_name: NamedType,
1070        source_text: impl Into<String>,
1071        path: impl AsRef<Path>,
1072    ) -> Result<FieldSet, WithErrors<FieldSet>> {
1073        Parser::new().parse_field_set(schema, type_name, source_text, path)
1074    }
1075
1076    /// [`parse`][Self::parse] then [`validate`][Self::validate],
1077    /// to get a `Valid<ExecutableDocument>` when mutating it isn’t needed.
1078    pub fn parse_and_validate(
1079        schema: &Valid<Schema>,
1080        type_name: NamedType,
1081        source_text: impl Into<String>,
1082        path: impl AsRef<Path>,
1083    ) -> Result<Valid<Self>, WithErrors<Self>> {
1084        let (field_set, mut errors) =
1085            Parser::new().parse_field_set_inner(schema, type_name, source_text, path);
1086        validation::validate_field_set(&mut errors, schema, &field_set);
1087        errors.into_valid_result(field_set)
1088    }
1089
1090    pub fn validate(&self, schema: &Valid<Schema>) -> Result<(), DiagnosticList> {
1091        let mut sources = IndexMap::clone(&schema.sources);
1092        sources.extend(self.sources.iter().map(|(k, v)| (*k, v.clone())));
1093        let mut errors = DiagnosticList::new(Arc::new(sources));
1094        validation::validate_field_set(&mut errors, schema, self);
1095        errors.into_result()
1096    }
1097
1098    serialize_method!();
1099}
1100
1101impl fmt::Display for SelectionPath {
1102    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1103        match &self.root {
1104            ExecutableDefinitionName::AnonymousOperation(operation_type) => {
1105                write!(f, "{operation_type}")?
1106            }
1107            ExecutableDefinitionName::NamedOperation(operation_type, name) => {
1108                write!(f, "{operation_type} {name}")?
1109            }
1110            ExecutableDefinitionName::Fragment(name) => write!(f, "fragment {name}")?,
1111        }
1112        for name in &self.nested_fields {
1113            write!(f, " → {name}")?
1114        }
1115        Ok(())
1116    }
1117}