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