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