Skip to main content

apollo_compiler/schema/
mod.rs

1//! High-level representation of a GraphQL type system document a.k.a. schema.
2//!
3//! Compared to an [`ast::Document`] which follows closely the structure of GraphQL syntax,
4//! a [`Schema`] is organized for semantics first:
5//!
6//! * Wherever something is meant to have a unique name (for example fields of a given object type),
7//!   a collection is stored as [`IndexMap<Name, _>`] instead of [`Vec<_>`]
8//!   in order to facilitate lookup by name while preserving source ordering.
9//!
10//! * Everything from [type system extensions] is stored
11//!   together with corresponding “main” definitions,
12//!   so that most consumers don’t need to care about extensions at all,
13//!   while still preserving extension origins in each [`Node<_>`]’s
14//!   [`origin`][Node::origin].
15//!   (For example, some directives can be applied to an object type extensions to affect
16//!   fields defined in the same extension but not other fields of the object type.)
17//!   See [`ExtensionId`].
18//!
19//! [type system extensions]: https://spec.graphql.org/September2025/#sec-Type-System-Extensions
20//!
21//! In some cases like [`SchemaDefinition`], this module and the [`ast`] module
22//! define different Rust types with the same names.
23//! In other cases like [`Directive`] there is no data structure difference needed,
24//! so this module reuses and publicly re-exports some Rust types from the [`ast`] module.
25//!
26//! ## “Build” errors
27//!
28//! As a result of how `Schema` is structured,
29//! not all AST documents (even if filtering out executable definitions) can be fully represented:
30//! creating a `Schema` can cause errors (on top of any potential syntax error)
31//! for cases like name collisions.
32//!
33//! When such errors (or in [`Schema::parse`], syntax errors) happen,
34//! a partial schema is returned together with a list of diagnostics.
35//!
36//! ## Structural sharing and mutation
37//!
38//! Many parts of a `Schema` are reference-counted with [`Node`] (like in AST).
39//! This allows sharing nodes between documents without cloning entire subtrees.
40//! To modify a node,
41//! the [`make_mut`][Node::make_mut] method provides copy-on-write semantics.
42//!
43//! ## Validation
44//!
45//! The [Type System] section of the GraphQL specification defines validation rules
46//! beyond syntax errors and errors detected while constructing a `Schema`.
47//! The [`validate`][Schema::validate] method returns either:
48//!
49//! * An immutable [`Valid<Schema>`] type wrapper, or
50//! * The schema together with a list of diagnostics
51//!
52//! If there is no mutation needed between parsing and validation,
53//! [`Schema::parse_and_validate`] does both in one step.
54//!
55//! [Type System]: https://spec.graphql.org/September2025/#sec-Type-System
56//!
57//! ## Serialization
58//!
59//! [`Schema`] and other types types implement [`Display`][std::fmt::Display]
60//! and [`ToString`] by serializing to GraphQL syntax with a default configuration.
61//! [`serialize`][Schema::serialize] methods return a builder
62//! that has chaining methods for setting serialization configuration,
63//! and also implements `Display` and `ToString`.
64
65use crate::ast;
66use crate::collections::HashMap;
67use crate::collections::IndexMap;
68use crate::collections::IndexSet;
69use crate::name;
70use crate::parser::FileId;
71use crate::parser::Parser;
72use crate::parser::SourceSpan;
73use crate::ty;
74use crate::validation::DiagnosticList;
75use crate::validation::Valid;
76use crate::validation::WithErrors;
77pub use crate::Name;
78use crate::Node;
79use std::path::Path;
80use std::sync::OnceLock;
81
82mod from_ast;
83mod serialize;
84pub(crate) mod validation;
85
86pub use self::from_ast::SchemaBuilder;
87pub use crate::ast::Directive;
88pub use crate::ast::DirectiveDefinition;
89pub use crate::ast::DirectiveList;
90pub use crate::ast::DirectiveLocation;
91pub use crate::ast::EnumValueDefinition;
92pub use crate::ast::FieldDefinition;
93pub use crate::ast::InputValueDefinition;
94pub use crate::ast::NamedType;
95pub use crate::ast::Type;
96pub use crate::ast::Value;
97pub use crate::node::ExtensionId;
98
99/// High-level representation of a GraphQL type system document a.k.a. schema.
100#[derive(Clone)]
101pub struct Schema {
102    /// Source files, if any, that were parsed to contribute to this schema.
103    ///
104    /// The schema (including parsed definitions) may have been modified since parsing.
105    pub sources: crate::parser::SourceMap,
106
107    /// The `schema` definition and its extensions, defining root operations
108    pub schema_definition: Node<SchemaDefinition>,
109
110    /// Built-in and explicit directive definitions
111    pub directive_definitions: IndexMap<Name, Node<DirectiveDefinition>>,
112
113    /// Definitions and extensions of all types relevant to a schema:
114    ///
115    /// * Explict types in parsed input files or added programatically.
116    ///
117    /// * [Schema-introspection](https://spec.graphql.org/September2025/#sec-Schema-Introspection)
118    ///   types such as `__Schema`, `__Field`, etc.
119    ///
120    /// * When a `Schema` is initially created or parsed,
121    ///   all [Built-in scalars](https://spec.graphql.org/September2025/#sec-Scalars.Built-in-Scalars).
122    ///   After validation, the Rust `types` map in a `Valid<Schema>` only contains
123    ///   built-in scalar definitions for scalars that are used in the schema.
124    ///   We reflect in this Rust API the behavior of `__Schema.types` in GraphQL introspection.
125    pub types: IndexMap<NamedType, ExtendedType>,
126
127    /// Whether to validate default values of input fields and arguments
128    /// against their types. Defaults to `true`.
129    ///
130    /// Set to `false` via [`SchemaBuilder::validate_default_values`]
131    /// to accept schemas with mistyped default values.
132    pub validate_default_values: bool,
133}
134
135/// The [`schema` definition](https://spec.graphql.org/September2025/#sec-Schema) and its extensions,
136/// defining root operations
137#[derive(Debug, Clone, PartialEq, Eq, Default)]
138pub struct SchemaDefinition {
139    pub description: Option<Node<str>>,
140    pub directives: DirectiveList,
141
142    /// Name of the object type for the `query` root operation
143    pub query: Option<Node<Name>>,
144
145    /// Name of the object type for the `mutation` root operation
146    pub mutation: Option<Node<Name>>,
147
148    /// Name of the object type for the `subscription` root operation
149    pub subscription: Option<Node<Name>>,
150}
151
152/// The definition of a named type, with all information from type extensions folded in.
153///
154/// The source location is that of the "main" definition.
155#[derive(Debug, Clone, PartialEq, Eq)]
156pub enum ExtendedType {
157    Scalar(Node<ScalarType>),
158    Object(Node<ObjectType>),
159    Interface(Node<InterfaceType>),
160    Union(Node<UnionType>),
161    Enum(Node<EnumType>),
162    InputObject(Node<InputObjectType>),
163}
164
165/// The definition of a [scalar type](https://spec.graphql.org/September2025/#sec-Scalars),
166/// with all information from type extensions folded in.
167#[derive(Debug, Clone, PartialEq, Eq, Hash)]
168pub struct ScalarType {
169    pub description: Option<Node<str>>,
170    pub name: Name,
171    pub directives: DirectiveList,
172}
173
174/// The definition of an [object type](https://spec.graphql.org/September2025/#sec-Objects),
175/// with all information from type extensions folded in.
176#[derive(Debug, Clone, PartialEq, Eq)]
177pub struct ObjectType {
178    pub description: Option<Node<str>>,
179    pub name: Name,
180    pub implements_interfaces: IndexSet<Node<Name>>,
181    pub directives: DirectiveList,
182
183    /// Explicit field definitions.
184    ///
185    /// When looking up a definition,
186    /// consider using [`Schema::type_field`] instead to include meta-fields.
187    pub fields: IndexMap<Name, Node<FieldDefinition>>,
188}
189
190#[derive(Debug, Clone, PartialEq, Eq)]
191pub struct InterfaceType {
192    pub description: Option<Node<str>>,
193    pub name: Name,
194    pub implements_interfaces: IndexSet<Node<Name>>,
195
196    pub directives: DirectiveList,
197
198    /// Explicit field definitions.
199    ///
200    /// When looking up a definition,
201    /// consider using [`Schema::type_field`] instead to include meta-fields.
202    pub fields: IndexMap<Name, Node<FieldDefinition>>,
203}
204
205/// The definition of an [union type](https://spec.graphql.org/September2025/#sec-Unions),
206/// with all information from type extensions folded in.
207#[derive(Debug, Clone, PartialEq, Eq)]
208pub struct UnionType {
209    pub description: Option<Node<str>>,
210    pub name: Name,
211    pub directives: DirectiveList,
212
213    /// * Key: name of a member object type
214    /// * Value: which union type extension defined this implementation,
215    ///   or `None` for the union type definition.
216    pub members: IndexSet<Node<Name>>,
217}
218
219/// The definition of an [enum type](https://spec.graphql.org/September2025/#sec-Enums),
220/// with all information from type extensions folded in.
221#[derive(Debug, Clone, PartialEq, Eq)]
222pub struct EnumType {
223    pub description: Option<Node<str>>,
224    pub name: Name,
225    pub directives: DirectiveList,
226    pub values: IndexMap<Name, Node<EnumValueDefinition>>,
227}
228
229/// The definition of an [input object type](https://spec.graphql.org/September2025/#sec-Input-Objects),
230/// with all information from type extensions folded in.
231#[derive(Debug, Clone, PartialEq, Eq)]
232pub struct InputObjectType {
233    pub description: Option<Node<str>>,
234    pub name: Name,
235    pub directives: DirectiveList,
236    pub fields: IndexMap<Name, Node<InputValueDefinition>>,
237}
238
239/// The names of all types that implement a given interface.
240/// Returned by [`Schema::implementers_map`].
241///
242/// Concrete object types and derived interfaces can be accessed separately.
243///
244/// # Examples
245///
246/// ```rust
247/// use apollo_compiler::schema::Implementers;
248/// # let implementers = Implementers::default();
249///
250/// // introspection must return only concrete implementers.
251/// let possible_types = implementers.objects;
252/// ```
253///
254/// ```rust
255/// use apollo_compiler::schema::Implementers;
256/// # let implementers = Implementers::default();
257///
258/// for name in implementers.iter() {
259///     // iterates both concrete objects and interfaces
260///     println!("{name}");
261/// }
262/// ```
263#[derive(Debug, Default, Clone, PartialEq, Eq)]
264pub struct Implementers {
265    /// Names of the concrete object types that implement an interface.
266    pub objects: IndexSet<Name>,
267    /// Names of the interface types that implement an interface.
268    pub interfaces: IndexSet<Name>,
269}
270
271/// AST node that has been skipped during conversion to `Schema`
272#[derive(thiserror::Error, Debug, Clone)]
273pub(crate) enum BuildError {
274    #[error("a schema document must not contain {describe}")]
275    ExecutableDefinition { describe: &'static str },
276
277    #[error("must not have multiple `schema` definitions")]
278    SchemaDefinitionCollision {
279        previous_location: Option<SourceSpan>,
280    },
281
282    #[error("the directive `@{name}` is defined multiple times in the schema")]
283    DirectiveDefinitionCollision {
284        previous_location: Option<SourceSpan>,
285        name: Name,
286    },
287
288    #[error("the type `{name}` is defined multiple times in the schema")]
289    TypeDefinitionCollision {
290        previous_location: Option<SourceSpan>,
291        name: Name,
292    },
293
294    #[error("built-in scalar definitions must be omitted")]
295    BuiltInScalarTypeRedefinition,
296
297    #[error("schema extension without a schema definition")]
298    OrphanSchemaExtension,
299
300    #[error("type extension for undefined type `{name}`")]
301    OrphanTypeExtension { name: Name },
302
303    #[error("adding {describe_ext}, but `{name}` is {describe_def}")]
304    TypeExtensionKindMismatch {
305        name: Name,
306        describe_ext: &'static str,
307        def_location: Option<SourceSpan>,
308        describe_def: &'static str,
309    },
310
311    #[error("duplicate definitions for the `{operation_type}` root operation type")]
312    DuplicateRootOperation {
313        previous_location: Option<SourceSpan>,
314        operation_type: &'static str,
315    },
316
317    #[error(
318        "object type `{type_name}` implements interface `{name_at_previous_location}` \
319         more than once"
320    )]
321    DuplicateImplementsInterfaceInObject {
322        name_at_previous_location: Name,
323        type_name: Name,
324    },
325
326    #[error(
327        "interface type `{type_name}` implements interface `{name_at_previous_location}` \
328         more than once"
329    )]
330    DuplicateImplementsInterfaceInInterface {
331        name_at_previous_location: Name,
332        type_name: Name,
333    },
334
335    #[error(
336        "duplicate definitions for the `{name_at_previous_location}` \
337         field of object type `{type_name}`"
338    )]
339    ObjectFieldNameCollision {
340        name_at_previous_location: Name,
341        type_name: Name,
342    },
343
344    #[error(
345        "duplicate definitions for the `{name_at_previous_location}` \
346         field of interface type `{type_name}`"
347    )]
348    InterfaceFieldNameCollision {
349        name_at_previous_location: Name,
350        type_name: Name,
351    },
352
353    #[error(
354        "duplicate definitions for the `{name_at_previous_location}` \
355         value of enum type `{type_name}`"
356    )]
357    EnumValueNameCollision {
358        name_at_previous_location: Name,
359        type_name: Name,
360    },
361
362    #[error(
363        "duplicate definitions for the `{name_at_previous_location}` \
364         member of union type `{type_name}`"
365    )]
366    UnionMemberNameCollision {
367        name_at_previous_location: Name,
368        type_name: Name,
369    },
370
371    #[error(
372        "duplicate definitions for the `{name_at_previous_location}` \
373         field of input object type `{type_name}`"
374    )]
375    InputFieldNameCollision {
376        name_at_previous_location: Name,
377        type_name: Name,
378    },
379}
380
381/// Error type of [`Schema::type_field`]: could not find the requested field definition
382#[derive(Debug, Clone, PartialEq, Eq)]
383pub enum FieldLookupError<'schema> {
384    NoSuchType,
385    NoSuchField(&'schema NamedType, &'schema ExtendedType),
386}
387
388impl Schema {
389    /// Returns an (almost) empty schema.
390    ///
391    /// It starts with built-in directives, built-in scalars, and introspection types.
392    /// It can then be filled programatically.
393    #[allow(clippy::new_without_default)] // not a great implicit default in generic contexts
394    pub fn new() -> Self {
395        SchemaBuilder::new().build().unwrap()
396    }
397
398    /// Parse a single source file into a schema, with the default parser configuration.
399    ///
400    /// Create a [`Parser`] to use different parser configuration.
401    /// Use [`builder()`][Self::builder] to build a schema from multiple parsed files.
402    #[allow(clippy::result_large_err)] // Typically not called very often
403    pub fn parse(
404        source_text: impl Into<String>,
405        path: impl AsRef<Path>,
406    ) -> Result<Self, WithErrors<Self>> {
407        Parser::default().parse_schema(source_text, path)
408    }
409
410    /// [`parse`][Self::parse] then [`validate`][Self::validate],
411    /// to get a `Valid<Schema>` when mutating it isn’t needed.
412    #[allow(clippy::result_large_err)] // Typically not called very often
413    pub fn parse_and_validate(
414        source_text: impl Into<String>,
415        path: impl AsRef<Path>,
416    ) -> Result<Valid<Self>, WithErrors<Self>> {
417        let mut builder = Schema::builder();
418        Parser::default().parse_into_schema_builder(source_text, path, &mut builder);
419        let (mut schema, mut errors) = builder.build_inner();
420        validation::validate_schema(&mut errors, &mut schema);
421        errors.into_valid_result(schema)
422    }
423
424    /// Returns a new builder for creating a Schema from AST documents,
425    /// initialized with built-in directives, built-in scalars, and introspection types
426    ///
427    /// ```rust
428    /// use apollo_compiler::Schema;
429    ///
430    /// let empty_schema = Schema::builder().build();
431    /// ```
432    pub fn builder() -> SchemaBuilder {
433        SchemaBuilder::new()
434    }
435
436    #[allow(clippy::result_large_err)] // Typically not called very often
437    pub fn validate(mut self) -> Result<Valid<Self>, WithErrors<Self>> {
438        let mut errors = DiagnosticList::new(self.sources.clone());
439        validation::validate_schema(&mut errors, &mut self);
440        errors.into_valid_result(self)
441    }
442
443    /// Returns the type with the given name, if it is a scalar type
444    pub fn get_scalar(&self, name: &str) -> Option<&Node<ScalarType>> {
445        if let Some(ExtendedType::Scalar(ty)) = self.types.get(name) {
446            Some(ty)
447        } else {
448            None
449        }
450    }
451
452    /// Returns the type with the given name, if it is a object type
453    pub fn get_object(&self, name: &str) -> Option<&Node<ObjectType>> {
454        if let Some(ExtendedType::Object(ty)) = self.types.get(name) {
455            Some(ty)
456        } else {
457            None
458        }
459    }
460
461    /// Returns the type with the given name, if it is a interface type
462    pub fn get_interface(&self, name: &str) -> Option<&Node<InterfaceType>> {
463        if let Some(ExtendedType::Interface(ty)) = self.types.get(name) {
464            Some(ty)
465        } else {
466            None
467        }
468    }
469
470    /// Returns the type with the given name, if it is a union type
471    pub fn get_union(&self, name: &str) -> Option<&Node<UnionType>> {
472        if let Some(ExtendedType::Union(ty)) = self.types.get(name) {
473            Some(ty)
474        } else {
475            None
476        }
477    }
478
479    /// Returns the type with the given name, if it is a enum type
480    pub fn get_enum(&self, name: &str) -> Option<&Node<EnumType>> {
481        if let Some(ExtendedType::Enum(ty)) = self.types.get(name) {
482            Some(ty)
483        } else {
484            None
485        }
486    }
487
488    /// Returns the type with the given name, if it is a input object type
489    pub fn get_input_object(&self, name: &str) -> Option<&Node<InputObjectType>> {
490        if let Some(ExtendedType::InputObject(ty)) = self.types.get(name) {
491            Some(ty)
492        } else {
493            None
494        }
495    }
496
497    /// Returns the name of the object type for the root operation with the given operation kind
498    pub fn root_operation(&self, operation_type: ast::OperationType) -> Option<&NamedType> {
499        match operation_type {
500            ast::OperationType::Query => &self.schema_definition.query,
501            ast::OperationType::Mutation => &self.schema_definition.mutation,
502            ast::OperationType::Subscription => &self.schema_definition.subscription,
503        }
504        .as_ref()
505        .map(|component| component.as_ref())
506    }
507
508    /// Returns the definition of a type’s explicit field or meta-field.
509    pub fn type_field(
510        &self,
511        type_name: &str,
512        field_name: &str,
513    ) -> Result<&Node<FieldDefinition>, FieldLookupError<'_>> {
514        use ExtendedType::*;
515        let (ty_def_name, ty_def) = self
516            .types
517            .get_key_value(type_name)
518            .ok_or(FieldLookupError::NoSuchType)?;
519        let explicit_field = match ty_def {
520            Object(ty) => ty.fields.get(field_name),
521            Interface(ty) => ty.fields.get(field_name),
522            Scalar(_) | Union(_) | Enum(_) | InputObject(_) => None,
523        };
524        if let Some(def) = explicit_field {
525            return Ok(def);
526        }
527        let meta = MetaFieldDefinitions::get();
528        if field_name == "__typename" && matches!(ty_def, Object(_) | Interface(_) | Union(_)) {
529            // .validate() errors for __typename at the root of a subscription operation
530            return Ok(&meta.__typename);
531        }
532        if self
533            .schema_definition
534            .query
535            .as_ref()
536            .is_some_and(|query_type| query_type == type_name)
537        {
538            match field_name {
539                "__schema" => return Ok(&meta.__schema),
540                "__type" => return Ok(&meta.__type),
541                _ => {}
542            }
543        }
544        Err(FieldLookupError::NoSuchField(ty_def_name, ty_def))
545    }
546
547    /// Returns a map of interface names to names of types that implement that interface
548    ///
549    /// `Schema` only stores the inverse relationship
550    /// (in [`ObjectType::implements_interfaces`] and [`InterfaceType::implements_interfaces`]),
551    /// so iterating the implementers of an interface requires a linear scan
552    /// of all types in the schema.
553    /// If that is repeated for multiple interfaces,
554    /// gathering them all at once amorticizes that cost.
555    pub fn implementers_map(&self) -> HashMap<Name, Implementers> {
556        let mut map = HashMap::<Name, Implementers>::default();
557        for (ty_name, ty) in &self.types {
558            match ty {
559                ExtendedType::Object(def) => {
560                    for interface in &def.implements_interfaces {
561                        map.entry(interface.as_ref().clone())
562                            .or_default()
563                            .objects
564                            .insert(ty_name.clone());
565                    }
566                }
567                ExtendedType::Interface(def) => {
568                    for interface in &def.implements_interfaces {
569                        map.entry(interface.as_ref().clone())
570                            .or_default()
571                            .interfaces
572                            .insert(ty_name.clone());
573                    }
574                }
575                ExtendedType::Scalar(_)
576                | ExtendedType::Union(_)
577                | ExtendedType::Enum(_)
578                | ExtendedType::InputObject(_) => (),
579            };
580        }
581        map
582    }
583
584    /// Returns whether `maybe_subtype` is a subtype of `abstract_type`, which means either:
585    ///
586    /// * `maybe_subtype` implements the interface `abstract_type`
587    /// * `maybe_subtype` is a member of the union type `abstract_type`
588    pub fn is_subtype(&self, abstract_type: &str, maybe_subtype: &str) -> bool {
589        self.types.get(abstract_type).is_some_and(|ty| match ty {
590            ExtendedType::Interface(_) => self.types.get(maybe_subtype).is_some_and(|ty2| {
591                match ty2 {
592                    ExtendedType::Object(def) => &def.implements_interfaces,
593                    ExtendedType::Interface(def) => &def.implements_interfaces,
594                    ExtendedType::Scalar(_)
595                    | ExtendedType::Union(_)
596                    | ExtendedType::Enum(_)
597                    | ExtendedType::InputObject(_) => return false,
598                }
599                .contains(abstract_type)
600            }),
601            ExtendedType::Union(def) => def.members.contains(maybe_subtype),
602            ExtendedType::Scalar(_)
603            | ExtendedType::Object(_)
604            | ExtendedType::Enum(_)
605            | ExtendedType::InputObject(_) => false,
606        })
607    }
608
609    /// Returns whether the type `ty` is defined as is an input type
610    ///
611    /// <https://spec.graphql.org/September2025/#sec-Input-and-Output-Types>
612    pub fn is_input_type(&self, ty: &Type) -> bool {
613        match self.types.get(ty.inner_named_type()) {
614            Some(ExtendedType::Scalar(_))
615            | Some(ExtendedType::Enum(_))
616            | Some(ExtendedType::InputObject(_)) => true,
617            Some(ExtendedType::Object(_))
618            | Some(ExtendedType::Interface(_))
619            | Some(ExtendedType::Union(_))
620            | None => false,
621        }
622    }
623
624    /// Returns whether the type `ty` is defined as is an output type
625    ///
626    /// <https://spec.graphql.org/September2025/#sec-Input-and-Output-Types>
627    pub fn is_output_type(&self, ty: &Type) -> bool {
628        match self.types.get(ty.inner_named_type()) {
629            Some(ExtendedType::Scalar(_))
630            | Some(ExtendedType::Object(_))
631            | Some(ExtendedType::Interface(_))
632            | Some(ExtendedType::Union(_))
633            | Some(ExtendedType::Enum(_)) => true,
634            Some(ExtendedType::InputObject(_)) | None => false,
635        }
636    }
637
638    serialize_method!();
639}
640
641impl SchemaDefinition {
642    pub fn iter_root_operations(&self) -> impl Iterator<Item = (ast::OperationType, &Node<Name>)> {
643        [
644            (ast::OperationType::Query, &self.query),
645            (ast::OperationType::Mutation, &self.mutation),
646            (ast::OperationType::Subscription, &self.subscription),
647        ]
648        .into_iter()
649        .filter_map(|(ty, maybe_op)| maybe_op.as_ref().map(|op| (ty, op)))
650    }
651
652    /// Iterate over the `origins` of all components
653    ///
654    /// The order of the returned set is unspecified but deterministic
655    /// for a given apollo-compiler version.
656    pub fn iter_extension_ids(&self) -> impl Iterator<Item = Option<&ExtensionId>> {
657        self.directives
658            .iter()
659            .map(|dir| dir.extension_id())
660            .chain(self.query.iter().map(|name| name.extension_id()))
661            .chain(self.mutation.iter().map(|name| name.extension_id()))
662            .chain(self.subscription.iter().map(|name| name.extension_id()))
663    }
664
665    /// Collect `schema` extensions that contribute any component
666    ///
667    /// The order of the returned set is unspecified but deterministic
668    /// for a given apollo-compiler version.
669    pub fn extensions(&self) -> IndexSet<&ExtensionId> {
670        self.iter_extension_ids().flatten().collect()
671    }
672}
673
674impl ExtendedType {
675    pub fn name(&self) -> &Name {
676        match self {
677            Self::Scalar(def) => &def.name,
678            Self::Object(def) => &def.name,
679            Self::Interface(def) => &def.name,
680            Self::Union(def) => &def.name,
681            Self::Enum(def) => &def.name,
682            Self::InputObject(def) => &def.name,
683        }
684    }
685
686    /// Return the source location of the type's base definition.
687    ///
688    /// If the type has extensions, those are not covered by this location.
689    pub fn location(&self) -> Option<SourceSpan> {
690        match self {
691            Self::Scalar(ty) => ty.location(),
692            Self::Object(ty) => ty.location(),
693            Self::Interface(ty) => ty.location(),
694            Self::Union(ty) => ty.location(),
695            Self::Enum(ty) => ty.location(),
696            Self::InputObject(ty) => ty.location(),
697        }
698    }
699
700    pub(crate) fn describe(&self) -> &'static str {
701        match self {
702            Self::Scalar(_) => "a scalar type",
703            Self::Object(_) => "an object type",
704            Self::Interface(_) => "an interface type",
705            Self::Union(_) => "a union type",
706            Self::Enum(_) => "an enum type",
707            Self::InputObject(_) => "an input object type",
708        }
709    }
710
711    pub fn is_scalar(&self) -> bool {
712        matches!(self, Self::Scalar(_))
713    }
714
715    pub fn is_object(&self) -> bool {
716        matches!(self, Self::Object(_))
717    }
718
719    pub fn is_interface(&self) -> bool {
720        matches!(self, Self::Interface(_))
721    }
722
723    pub fn is_union(&self) -> bool {
724        matches!(self, Self::Union(_))
725    }
726
727    pub fn is_enum(&self) -> bool {
728        matches!(self, Self::Enum(_))
729    }
730
731    pub fn is_input_object(&self) -> bool {
732        matches!(self, Self::InputObject(_))
733    }
734
735    pub fn as_scalar(&self) -> Option<&ScalarType> {
736        if let Self::Scalar(def) = self {
737            Some(def)
738        } else {
739            None
740        }
741    }
742
743    pub fn as_object(&self) -> Option<&ObjectType> {
744        if let Self::Object(def) = self {
745            Some(def)
746        } else {
747            None
748        }
749    }
750
751    pub fn as_interface(&self) -> Option<&InterfaceType> {
752        if let Self::Interface(def) = self {
753            Some(def)
754        } else {
755            None
756        }
757    }
758
759    pub fn as_union(&self) -> Option<&UnionType> {
760        if let Self::Union(def) = self {
761            Some(def)
762        } else {
763            None
764        }
765    }
766
767    pub fn as_enum(&self) -> Option<&EnumType> {
768        if let Self::Enum(def) = self {
769            Some(def)
770        } else {
771            None
772        }
773    }
774
775    pub fn as_input_object(&self) -> Option<&InputObjectType> {
776        if let Self::InputObject(def) = self {
777            Some(def)
778        } else {
779            None
780        }
781    }
782
783    /// Returns wether this type is a leaf type: scalar or enum.
784    ///
785    /// Field selections must have sub-selections if and only if
786    /// their inner named type is *not* a leaf field.
787    pub fn is_leaf(&self) -> bool {
788        matches!(self, Self::Scalar(_) | Self::Enum(_))
789    }
790
791    /// Returns true if a value of this type can be used as an input value.
792    ///
793    /// # Spec
794    /// This implements spec function
795    /// [`IsInputType(type)`](https://spec.graphql.org/September2025/#IsInputType())
796    pub fn is_input_type(&self) -> bool {
797        matches!(self, Self::Scalar(_) | Self::Enum(_) | Self::InputObject(_))
798    }
799
800    /// Returns true if a value of this type can be used as an output value.
801    ///
802    /// # Spec
803    /// This implements spec function
804    /// [`IsOutputType(type)`](https://spec.graphql.org/September2025/#IsOutputType())
805    pub fn is_output_type(&self) -> bool {
806        matches!(
807            self,
808            Self::Scalar(_) | Self::Enum(_) | Self::Object(_) | Self::Interface(_) | Self::Union(_)
809        )
810    }
811
812    /// Returns whether this is a built-in scalar or introspection type
813    pub fn is_built_in(&self) -> bool {
814        match self {
815            Self::Scalar(ty) => ty.is_built_in(),
816            Self::Object(ty) => ty.is_built_in(),
817            Self::Interface(ty) => ty.is_built_in(),
818            Self::Union(ty) => ty.is_built_in(),
819            Self::Enum(ty) => ty.is_built_in(),
820            Self::InputObject(ty) => ty.is_built_in(),
821        }
822    }
823
824    pub fn directives(&self) -> &DirectiveList {
825        match self {
826            Self::Scalar(ty) => &ty.directives,
827            Self::Object(ty) => &ty.directives,
828            Self::Interface(ty) => &ty.directives,
829            Self::Union(ty) => &ty.directives,
830            Self::Enum(ty) => &ty.directives,
831            Self::InputObject(ty) => &ty.directives,
832        }
833    }
834
835    pub fn description(&self) -> Option<&Node<str>> {
836        match self {
837            Self::Scalar(ty) => ty.description.as_ref(),
838            Self::Object(ty) => ty.description.as_ref(),
839            Self::Interface(ty) => ty.description.as_ref(),
840            Self::Union(ty) => ty.description.as_ref(),
841            Self::Enum(ty) => ty.description.as_ref(),
842            Self::InputObject(ty) => ty.description.as_ref(),
843        }
844    }
845
846    /// Iterate over the `origins` of all components
847    ///
848    /// The order of the returned set is unspecified but deterministic
849    /// for a given apollo-compiler version.
850    pub fn iter_extension_ids(&self) -> impl Iterator<Item = Option<&ExtensionId>> {
851        match self {
852            Self::Scalar(ty) => Box::new(ty.iter_extension_ids()) as Box<dyn Iterator<Item = _>>,
853            Self::Object(ty) => Box::new(ty.iter_extension_ids()),
854            Self::Interface(ty) => Box::new(ty.iter_extension_ids()),
855            Self::Union(ty) => Box::new(ty.iter_extension_ids()),
856            Self::Enum(ty) => Box::new(ty.iter_extension_ids()),
857            Self::InputObject(ty) => Box::new(ty.iter_extension_ids()),
858        }
859    }
860
861    /// Collect `schema` extensions that contribute any component
862    ///
863    /// The order of the returned set is unspecified but deterministic
864    /// for a given apollo-compiler version.
865    pub fn extensions(&self) -> IndexSet<&ExtensionId> {
866        self.iter_extension_ids().flatten().collect()
867    }
868
869    serialize_method!();
870}
871
872impl ScalarType {
873    /// Iterate over the `origins` of all components
874    ///
875    /// The order of the returned set is unspecified but deterministic
876    /// for a given apollo-compiler version.
877    pub fn iter_extension_ids(&self) -> impl Iterator<Item = Option<&ExtensionId>> {
878        self.directives.iter().map(|dir| dir.extension_id())
879    }
880
881    /// Collect scalar type extensions that contribute any component
882    ///
883    /// The order of the returned set is unspecified but deterministic
884    /// for a given apollo-compiler version.
885    pub fn extensions(&self) -> IndexSet<&ExtensionId> {
886        self.iter_extension_ids().flatten().collect()
887    }
888
889    serialize_method!();
890}
891
892impl ObjectType {
893    /// Iterate over the `origins` of all components
894    ///
895    /// The order of the returned set is unspecified but deterministic
896    /// for a given apollo-compiler version.
897    pub fn iter_extension_ids(&self) -> impl Iterator<Item = Option<&ExtensionId>> {
898        self.directives
899            .iter()
900            .map(|dir| dir.extension_id())
901            .chain(
902                self.implements_interfaces
903                    .iter()
904                    .map(|component| component.extension_id()),
905            )
906            .chain(self.fields.values().map(|field| field.extension_id()))
907    }
908
909    /// Collect object type extensions that contribute any component
910    ///
911    /// The order of the returned set is unspecified but deterministic
912    /// for a given apollo-compiler version.
913    pub fn extensions(&self) -> IndexSet<&ExtensionId> {
914        self.iter_extension_ids().flatten().collect()
915    }
916
917    serialize_method!();
918}
919
920impl InterfaceType {
921    /// Iterate over the `origins` of all components
922    ///
923    /// The order of the returned set is unspecified but deterministic
924    /// for a given apollo-compiler version.
925    pub fn iter_extension_ids(&self) -> impl Iterator<Item = Option<&ExtensionId>> {
926        self.directives
927            .iter()
928            .map(|dir| dir.extension_id())
929            .chain(
930                self.implements_interfaces
931                    .iter()
932                    .map(|component| component.extension_id()),
933            )
934            .chain(self.fields.values().map(|field| field.extension_id()))
935    }
936
937    /// Collect interface type extensions that contribute any component
938    ///
939    /// The order of the returned set is unspecified but deterministic
940    /// for a given apollo-compiler version.
941    pub fn extensions(&self) -> IndexSet<&ExtensionId> {
942        self.iter_extension_ids().flatten().collect()
943    }
944
945    serialize_method!();
946}
947
948impl UnionType {
949    /// Iterate over the `origins` of all components
950    ///
951    /// The order of the returned set is unspecified but deterministic
952    /// for a given apollo-compiler version.
953    pub fn iter_extension_ids(&self) -> impl Iterator<Item = Option<&ExtensionId>> {
954        self.directives.iter().map(|dir| dir.extension_id()).chain(
955            self.members
956                .iter()
957                .map(|component| component.extension_id()),
958        )
959    }
960
961    /// Collect union type extensions that contribute any component
962    ///
963    /// The order of the returned set is unspecified but deterministic
964    /// for a given apollo-compiler version.
965    pub fn extensions(&self) -> IndexSet<&ExtensionId> {
966        self.iter_extension_ids().flatten().collect()
967    }
968
969    serialize_method!();
970}
971
972impl EnumType {
973    /// Iterate over the `origins` of all components
974    ///
975    /// The order of the returned set is unspecified but deterministic
976    /// for a given apollo-compiler version.
977    pub fn iter_extension_ids(&self) -> impl Iterator<Item = Option<&ExtensionId>> {
978        self.directives
979            .iter()
980            .map(|dir| dir.extension_id())
981            .chain(self.values.values().map(|value| value.extension_id()))
982    }
983
984    /// Collect enum type extensions that contribute any component
985    ///
986    /// The order of the returned set is unspecified but deterministic
987    /// for a given apollo-compiler version.
988    pub fn extensions(&self) -> IndexSet<&ExtensionId> {
989        self.iter_extension_ids().flatten().collect()
990    }
991
992    serialize_method!();
993}
994
995impl InputObjectType {
996    /// Returns true if this is a OneOf Input Object (has the `@oneOf` directive).
997    pub fn is_one_of(&self) -> bool {
998        self.directives.get("oneOf").is_some()
999    }
1000
1001    /// Iterate over the `origins` of all components
1002    ///
1003    /// The order of the returned set is unspecified but deterministic
1004    /// for a given apollo-compiler version.
1005    pub fn iter_extension_ids(&self) -> impl Iterator<Item = Option<&ExtensionId>> {
1006        self.directives
1007            .iter()
1008            .map(|dir| dir.extension_id())
1009            .chain(self.fields.values().map(|field| field.extension_id()))
1010    }
1011
1012    /// Collect input object type extensions that contribute any component
1013    ///
1014    /// The order of the returned set is unspecified but deterministic
1015    /// for a given apollo-compiler version.
1016    pub fn extensions(&self) -> IndexSet<&ExtensionId> {
1017        self.iter_extension_ids().flatten().collect()
1018    }
1019
1020    serialize_method!();
1021}
1022
1023impl Eq for Schema {}
1024
1025impl PartialEq for Schema {
1026    fn eq(&self, other: &Self) -> bool {
1027        let Self {
1028            sources: _,                 // ignored
1029            validate_default_values: _, // ignored, config only
1030            schema_definition,
1031            directive_definitions,
1032            types,
1033        } = self;
1034        *schema_definition == other.schema_definition
1035            && *directive_definitions == other.directive_definitions
1036            && *types == other.types
1037    }
1038}
1039
1040impl Implementers {
1041    /// Iterate over all implementers, including objects and interfaces.
1042    ///
1043    /// The iteration order is unspecified.
1044    pub fn iter(&self) -> impl Iterator<Item = &'_ Name> {
1045        self.objects.iter().chain(&self.interfaces)
1046    }
1047}
1048
1049impl From<Node<ScalarType>> for ExtendedType {
1050    fn from(ty: Node<ScalarType>) -> Self {
1051        Self::Scalar(ty)
1052    }
1053}
1054
1055impl From<Node<ObjectType>> for ExtendedType {
1056    fn from(ty: Node<ObjectType>) -> Self {
1057        Self::Object(ty)
1058    }
1059}
1060
1061impl From<Node<InterfaceType>> for ExtendedType {
1062    fn from(ty: Node<InterfaceType>) -> Self {
1063        Self::Interface(ty)
1064    }
1065}
1066
1067impl From<Node<UnionType>> for ExtendedType {
1068    fn from(ty: Node<UnionType>) -> Self {
1069        Self::Union(ty)
1070    }
1071}
1072
1073impl From<Node<EnumType>> for ExtendedType {
1074    fn from(ty: Node<EnumType>) -> Self {
1075        Self::Enum(ty)
1076    }
1077}
1078
1079impl From<Node<InputObjectType>> for ExtendedType {
1080    fn from(ty: Node<InputObjectType>) -> Self {
1081        Self::InputObject(ty)
1082    }
1083}
1084
1085impl From<ScalarType> for ExtendedType {
1086    fn from(ty: ScalarType) -> Self {
1087        Self::Scalar(ty.into())
1088    }
1089}
1090
1091impl From<ObjectType> for ExtendedType {
1092    fn from(ty: ObjectType) -> Self {
1093        Self::Object(ty.into())
1094    }
1095}
1096
1097impl From<InterfaceType> for ExtendedType {
1098    fn from(ty: InterfaceType) -> Self {
1099        Self::Interface(ty.into())
1100    }
1101}
1102
1103impl From<UnionType> for ExtendedType {
1104    fn from(ty: UnionType) -> Self {
1105        Self::Union(ty.into())
1106    }
1107}
1108
1109impl From<EnumType> for ExtendedType {
1110    fn from(ty: EnumType) -> Self {
1111        Self::Enum(ty.into())
1112    }
1113}
1114
1115impl From<InputObjectType> for ExtendedType {
1116    fn from(ty: InputObjectType) -> Self {
1117        Self::InputObject(ty.into())
1118    }
1119}
1120
1121impl std::fmt::Debug for Schema {
1122    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1123        let Self {
1124            sources,
1125            schema_definition,
1126            directive_definitions,
1127            types,
1128            validate_default_values: _,
1129        } = self;
1130        f.debug_struct("Schema")
1131            .field("sources", sources)
1132            .field("schema_definition", schema_definition)
1133            .field(
1134                "directive_definitions",
1135                &DebugDirectiveDefinitions(directive_definitions),
1136            )
1137            .field("types", &DebugTypes(types))
1138            .finish()
1139    }
1140}
1141
1142struct DebugDirectiveDefinitions<'a>(&'a IndexMap<Name, Node<DirectiveDefinition>>);
1143
1144struct DebugTypes<'a>(&'a IndexMap<Name, ExtendedType>);
1145
1146impl std::fmt::Debug for DebugDirectiveDefinitions<'_> {
1147    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1148        let mut map = f.debug_map();
1149        for (name, def) in self.0 {
1150            if !def.is_built_in() {
1151                map.entry(name, def);
1152            } else {
1153                map.entry(name, &format_args!("built_in_directive!({name:?})"));
1154            }
1155        }
1156        map.finish()
1157    }
1158}
1159
1160impl std::fmt::Debug for DebugTypes<'_> {
1161    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1162        let mut map = f.debug_map();
1163        for (name, def) in self.0 {
1164            if !def.is_built_in() {
1165                map.entry(name, def);
1166            } else {
1167                map.entry(name, &format_args!("built_in_type!({name:?})"));
1168            }
1169        }
1170        map.finish()
1171    }
1172}
1173
1174struct MetaFieldDefinitions {
1175    __typename: Node<FieldDefinition>,
1176    __schema: Node<FieldDefinition>,
1177    __type: Node<FieldDefinition>,
1178}
1179
1180impl MetaFieldDefinitions {
1181    fn get() -> &'static Self {
1182        static DEFS: OnceLock<MetaFieldDefinitions> = OnceLock::new();
1183        DEFS.get_or_init(|| Self {
1184            // __typename: String!
1185            __typename: Node::new(FieldDefinition {
1186                description: None,
1187                name: name!("__typename"),
1188                arguments: Vec::new(),
1189                ty: ty!(String!),
1190                directives: ast::DirectiveList::new(),
1191            }),
1192            // __schema: __Schema!
1193            __schema: Node::new(FieldDefinition {
1194                description: None,
1195                name: name!("__schema"),
1196                arguments: Vec::new(),
1197                ty: ty!(__Schema!),
1198                directives: ast::DirectiveList::new(),
1199            }),
1200            // __type(name: String!): __Type
1201            __type: Node::new(FieldDefinition {
1202                description: None,
1203                name: name!("__type"),
1204                arguments: vec![InputValueDefinition {
1205                    description: None,
1206                    name: name!("name"),
1207                    ty: ty!(String!).into(),
1208                    default_value: None,
1209                    directives: ast::DirectiveList::new(),
1210                }
1211                .into()],
1212                ty: ty!(__Type),
1213                directives: ast::DirectiveList::new(),
1214            }),
1215        })
1216    }
1217}