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