async_graphql/registry/
mod.rs

1mod cache_control;
2mod export_sdl;
3mod stringify_exec_doc;
4
5use std::{
6    collections::{BTreeMap, BTreeSet, HashMap, HashSet},
7    fmt::{self, Display, Formatter, Write},
8    sync::Arc,
9};
10
11pub use cache_control::CacheControl;
12pub use export_sdl::SDLExportOptions;
13use indexmap::{map::IndexMap, set::IndexSet};
14
15pub use crate::model::{__DirectiveLocation, location_traits};
16use crate::{
17    Any, Context, ID, InputType, OutputType, Positioned, ServerResult, SubscriptionType, Value,
18    VisitorContext,
19    model::__Schema,
20    parser::types::{BaseType as ParsedBaseType, Field, Type as ParsedType, VariableDefinition},
21    schema::IntrospectionMode,
22};
23
24fn strip_brackets(type_name: &str) -> Option<&str> {
25    type_name
26        .strip_prefix('[')
27        .map(|rest| &rest[..rest.len() - 1])
28}
29
30#[derive(Clone, Copy, Eq, PartialEq, Debug)]
31pub enum MetaTypeName<'a> {
32    List(&'a str),
33    NonNull(&'a str),
34    Named(&'a str),
35}
36
37impl Display for MetaTypeName<'_> {
38    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
39        match self {
40            MetaTypeName::Named(name) => write!(f, "{}", name),
41            MetaTypeName::NonNull(name) => write!(f, "{}!", name),
42            MetaTypeName::List(name) => write!(f, "[{}]", name),
43        }
44    }
45}
46
47impl MetaTypeName<'_> {
48    #[inline]
49    pub fn create(type_name: &str) -> MetaTypeName<'_> {
50        if let Some(type_name) = type_name.strip_suffix('!') {
51            MetaTypeName::NonNull(type_name)
52        } else if let Some(type_name) = strip_brackets(type_name) {
53            MetaTypeName::List(type_name)
54        } else {
55            MetaTypeName::Named(type_name)
56        }
57    }
58
59    #[inline]
60    pub fn concrete_typename(type_name: &str) -> &str {
61        match MetaTypeName::create(type_name) {
62            MetaTypeName::List(type_name) => Self::concrete_typename(type_name),
63            MetaTypeName::NonNull(type_name) => Self::concrete_typename(type_name),
64            MetaTypeName::Named(type_name) => type_name,
65        }
66    }
67
68    #[inline]
69    pub fn is_non_null(&self) -> bool {
70        matches!(self, MetaTypeName::NonNull(_))
71    }
72
73    #[inline]
74    #[must_use]
75    pub fn unwrap_non_null(&self) -> Self {
76        match self {
77            MetaTypeName::NonNull(ty) => MetaTypeName::create(ty),
78            _ => *self,
79        }
80    }
81
82    #[inline]
83    pub fn is_subtype(&self, sub: &MetaTypeName<'_>) -> bool {
84        match (self, sub) {
85            (MetaTypeName::NonNull(super_type), MetaTypeName::NonNull(sub_type))
86            | (MetaTypeName::Named(super_type), MetaTypeName::NonNull(sub_type)) => {
87                MetaTypeName::create(super_type).is_subtype(&MetaTypeName::create(sub_type))
88            }
89            (MetaTypeName::Named(super_type), MetaTypeName::Named(sub_type)) => {
90                super_type == sub_type
91            }
92            (MetaTypeName::List(super_type), MetaTypeName::List(sub_type)) => {
93                MetaTypeName::create(super_type).is_subtype(&MetaTypeName::create(sub_type))
94            }
95            _ => false,
96        }
97    }
98
99    #[inline]
100    pub fn is_list(&self) -> bool {
101        match self {
102            MetaTypeName::List(_) => true,
103            MetaTypeName::NonNull(ty) => MetaTypeName::create(ty).is_list(),
104            MetaTypeName::Named(name) => name.ends_with(']'),
105        }
106    }
107}
108
109/// actual directive invocation on SDL definitions
110#[derive(Debug, Clone)]
111pub struct MetaDirectiveInvocation {
112    /// name of directive to invoke
113    pub name: String,
114    /// actual arguments passed to directive
115    pub args: IndexMap<String, Value>,
116}
117
118impl MetaDirectiveInvocation {
119    pub fn sdl(&self) -> String {
120        let formatted_args = if self.args.is_empty() {
121            String::new()
122        } else {
123            format!(
124                "({})",
125                self.args
126                    .iter()
127                    .map(|(name, value)| format!("{}: {}", name, value))
128                    .collect::<Vec<_>>()
129                    .join(", ")
130            )
131        };
132        format!("@{}{}", self.name, formatted_args)
133    }
134}
135
136/// Input value metadata
137#[derive(Clone)]
138pub struct MetaInputValue {
139    /// The name of the input value
140    pub name: String,
141    /// The description of the input value
142    pub description: Option<String>,
143    /// The type of the input value
144    pub ty: String,
145    /// Field deprecation
146    pub deprecation: Deprecation,
147    /// The default value of the input value
148    pub default_value: Option<String>,
149    /// A function that uses to check if the input value should be exported to
150    /// schemas
151    pub visible: Option<MetaVisibleFn>,
152    /// Indicate that an input object is not accessible from a supergraph when
153    /// using Apollo Federation
154    pub inaccessible: bool,
155    /// Arbitrary string metadata that will be propagated to the supergraph when
156    /// using Apollo Federation. This attribute is repeatable
157    pub tags: Vec<String>,
158    /// Indicate that an input object is secret
159    pub is_secret: bool,
160    /// Custom directive invocations
161    pub directive_invocations: Vec<MetaDirectiveInvocation>,
162}
163
164type ComputeComplexityFn = fn(
165    &VisitorContext<'_>,
166    &[Positioned<VariableDefinition>],
167    &Field,
168    usize,
169) -> ServerResult<usize>;
170
171#[derive(Debug, Clone, Default)]
172pub enum Deprecation {
173    #[default]
174    NoDeprecated,
175    Deprecated {
176        reason: Option<String>,
177    },
178}
179
180impl Deprecation {
181    #[inline]
182    pub fn is_deprecated(&self) -> bool {
183        matches!(self, Deprecation::Deprecated { .. })
184    }
185
186    #[inline]
187    pub fn reason(&self) -> Option<&str> {
188        match self {
189            Deprecation::NoDeprecated => None,
190            Deprecation::Deprecated { reason } => reason.as_deref(),
191        }
192    }
193}
194
195/// Field metadata
196#[derive(Clone)]
197pub struct MetaField {
198    /// The name of the field
199    pub name: String,
200    /// The description of the field
201    pub description: Option<String>,
202    /// The arguments of the field
203    pub args: IndexMap<String, MetaInputValue>,
204    /// The type of the field
205    pub ty: String,
206    /// Field deprecation
207    pub deprecation: Deprecation,
208    /// Used to create HTTP `Cache-Control` header
209    pub cache_control: CacheControl,
210    /// Mark a field as owned by another service. This allows service A to use
211    /// fields from service B while also knowing at runtime the types of that
212    /// field.
213    pub external: bool,
214    /// Annotate the required input fieldset from a base type for a resolver. It
215    /// is used to develop a query plan where the required fields may not be
216    /// needed by the client, but the service may need additional information
217    /// from other services.
218    pub requires: Option<String>,
219    /// Annotate the expected returned fieldset from a field on a base type that
220    /// is guaranteed to be selectable by the gateway.
221    pub provides: Option<String>,
222    /// A function that uses to check if the field should be exported to
223    /// schemas
224    pub visible: Option<MetaVisibleFn>,
225    /// Indicate that an object type's field is allowed to be resolved by
226    /// multiple subgraphs
227    pub shareable: bool,
228    /// Indicate that an object is not accessible from a supergraph when using
229    /// Apollo Federation
230    pub inaccessible: bool,
231    /// Arbitrary string metadata that will be propagated to the supergraph when
232    /// using Apollo Federation. This attribute is repeatable
233    pub tags: Vec<String>,
234    /// Mark the field as overriding a field currently present on another
235    /// subgraph. It is used to migrate fields between subgraphs.
236    pub override_from: Option<String>,
237    /// A constant or function to get the complexity
238    pub compute_complexity: Option<ComputeComplexityFn>,
239    /// Custom directive invocations
240    pub directive_invocations: Vec<MetaDirectiveInvocation>,
241    /// Indicates to composition that the target element is accessible only to
242    /// the authenticated supergraph users with the appropriate JWT scopes
243    /// when using Apollo Federation.
244    pub requires_scopes: Vec<String>,
245}
246
247#[derive(Clone)]
248pub struct MetaEnumValue {
249    pub name: String,
250    pub description: Option<String>,
251    pub deprecation: Deprecation,
252    pub visible: Option<MetaVisibleFn>,
253    pub inaccessible: bool,
254    pub tags: Vec<String>,
255    pub directive_invocations: Vec<MetaDirectiveInvocation>,
256}
257
258type MetaVisibleFn = fn(&Context<'_>) -> bool;
259
260#[derive(Debug, Copy, Clone, Eq, PartialEq)]
261pub enum MetaTypeId {
262    Scalar,
263    Object,
264    Interface,
265    Union,
266    Enum,
267    InputObject,
268}
269
270impl MetaTypeId {
271    fn create_fake_type(&self, rust_typename: &'static str) -> MetaType {
272        match self {
273            MetaTypeId::Scalar => MetaType::Scalar {
274                name: "".to_string(),
275                description: None,
276                is_valid: None,
277                visible: None,
278                inaccessible: false,
279                tags: vec![],
280                specified_by_url: None,
281                directive_invocations: vec![],
282                requires_scopes: vec![],
283            },
284            MetaTypeId::Object => MetaType::Object {
285                name: "".to_string(),
286                description: None,
287                fields: Default::default(),
288                cache_control: Default::default(),
289                extends: false,
290                shareable: false,
291                resolvable: true,
292                inaccessible: false,
293                interface_object: false,
294                tags: vec![],
295                keys: None,
296                visible: None,
297                is_subscription: false,
298                rust_typename: Some(rust_typename),
299                directive_invocations: vec![],
300                requires_scopes: vec![],
301            },
302            MetaTypeId::Interface => MetaType::Interface {
303                name: "".to_string(),
304                description: None,
305                fields: Default::default(),
306                possible_types: Default::default(),
307                extends: false,
308                inaccessible: false,
309                tags: vec![],
310                keys: None,
311                visible: None,
312                rust_typename: Some(rust_typename),
313                directive_invocations: vec![],
314                requires_scopes: vec![],
315            },
316            MetaTypeId::Union => MetaType::Union {
317                name: "".to_string(),
318                description: None,
319                possible_types: Default::default(),
320                visible: None,
321                inaccessible: false,
322                tags: vec![],
323                rust_typename: Some(rust_typename),
324                directive_invocations: vec![],
325            },
326            MetaTypeId::Enum => MetaType::Enum {
327                name: "".to_string(),
328                description: None,
329                enum_values: Default::default(),
330                visible: None,
331                inaccessible: false,
332                tags: vec![],
333                rust_typename: Some(rust_typename),
334                directive_invocations: vec![],
335                requires_scopes: vec![],
336            },
337            MetaTypeId::InputObject => MetaType::InputObject {
338                name: "".to_string(),
339                description: None,
340                input_fields: Default::default(),
341                visible: None,
342                inaccessible: false,
343                tags: vec![],
344                rust_typename: Some(rust_typename),
345                oneof: false,
346                directive_invocations: vec![],
347            },
348        }
349    }
350}
351
352impl Display for MetaTypeId {
353    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
354        f.write_str(match self {
355            MetaTypeId::Scalar => "Scalar",
356            MetaTypeId::Object => "Object",
357            MetaTypeId::Interface => "Interface",
358            MetaTypeId::Union => "Union",
359            MetaTypeId::Enum => "Enum",
360            MetaTypeId::InputObject => "InputObject",
361        })
362    }
363}
364
365/// A validator for scalar
366pub type ScalarValidatorFn = Arc<dyn Fn(&Value) -> bool + Send + Sync>;
367
368/// Type metadata
369#[derive(Clone)]
370pub enum MetaType {
371    /// Scalar
372    ///
373    /// Reference: <https://spec.graphql.org/October2021/#sec-Scalars>
374    Scalar {
375        /// The name of the scalar
376        name: String,
377        /// the description of the scalar
378        description: Option<String>,
379        /// A function that uses to check if the scalar is valid
380        is_valid: Option<ScalarValidatorFn>,
381        /// A function that uses to check if the scalar should be exported to
382        /// schemas
383        visible: Option<MetaVisibleFn>,
384        /// Indicate that a scalar is not accessible from a supergraph when
385        /// using Apollo Federation
386        ///
387        /// Reference: <https://www.apollographql.com/docs/federation/federated-types/federated-directives/#inaccessible>
388        inaccessible: bool,
389        /// Arbitrary string metadata that will be propagated to the supergraph
390        /// when using Apollo Federation. This attribute is repeatable
391        ///
392        /// Reference: <https://www.apollographql.com/docs/federation/federated-types/federated-directives/#applying-metadata>
393        tags: Vec<String>,
394        /// Provide a specification URL for this scalar type, it must link to a
395        /// human-readable specification of the data format, serialization and
396        /// coercion rules for this scalar.
397        specified_by_url: Option<String>,
398        /// custom directive invocations
399        directive_invocations: Vec<MetaDirectiveInvocation>,
400        /// Indicates to composition that the target element is accessible only
401        /// to the authenticated supergraph users with the appropriate
402        /// JWT scopes when using Apollo Federation.
403        requires_scopes: Vec<String>,
404    },
405    /// Object
406    ///
407    /// Reference: <https://spec.graphql.org/October2021/#sec-Objects>
408    Object {
409        /// The name of the object
410        name: String,
411        /// The description of the object
412        description: Option<String>,
413        /// The fields of the object type
414        fields: IndexMap<String, MetaField>,
415        /// Used to create HTTP `Cache-Control` header
416        cache_control: CacheControl,
417        /// Indicates that an object definition is an extension of another
418        /// definition of that same type.
419        ///
420        /// Reference: <https://www.apollographql.com/docs/federation/federated-types/federated-directives/#extends>
421        extends: bool,
422        /// Indicates that an object type's field is allowed to be resolved by
423        /// multiple subgraphs (by default in Federation 2, object fields can be
424        /// resolved by only one subgraph).
425        ///
426        /// Reference: <https://www.apollographql.com/docs/federation/federated-types/federated-directives/#shareable>
427        shareable: bool,
428        /// Indicates that the subgraph does not define a reference resolver
429        /// for this object. Objects are assumed to be resolvable by default.
430        ///
431        /// Most commonly used to reference an entity defined in another
432        /// subgraph without contributing fields. Part of the `@key` directive.
433        ///
434        /// Reference: <https://www.apollographql.com/docs/federation/federated-types/federated-directives/#key>
435        resolvable: bool,
436        /// The keys of the object type
437        ///
438        /// Designates an object type as an [entity](https://www.apollographql.com/docs/federation/entities) and specifies
439        /// its key fields (a set of fields that the subgraph can use to
440        /// uniquely identify any instance of the entity).
441        ///
442        /// Reference: <https://www.apollographql.com/docs/federation/federated-types/federated-directives/#key>
443        keys: Option<Vec<String>>,
444        /// A function that uses to check if the object should be exported to
445        /// schemas
446        visible: Option<MetaVisibleFn>,
447        /// Indicate that an object is not accessible from a supergraph when
448        /// using Apollo Federation
449        ///
450        /// Reference: <https://www.apollographql.com/docs/federation/federated-types/federated-directives/#inaccessible>
451        inaccessible: bool,
452        /// During composition, the fields of every `@interfaceObject` are added
453        /// both to their corresponding interface definition and to all
454        /// entity types that implement that interface.
455        ///
456        /// Reference: <https://www.apollographql.com/docs/federation/federated-types/federated-directives/#interfaceobject>
457        interface_object: bool,
458        /// Arbitrary string metadata that will be propagated to the supergraph
459        /// when using Apollo Federation. This attribute is repeatable
460        ///
461        /// Reference: <https://www.apollographql.com/docs/federation/federated-types/federated-directives/#applying-metadata>
462        tags: Vec<String>,
463        /// Indicates whether it is a subscription object
464        is_subscription: bool,
465        /// The Rust typename corresponding to the object
466        rust_typename: Option<&'static str>,
467        /// custom directive invocations
468        directive_invocations: Vec<MetaDirectiveInvocation>,
469        /// Indicates to composition that the target element is accessible only
470        /// to the authenticated supergraph users with the appropriate
471        /// JWT scopes when using Apollo Federation.
472        requires_scopes: Vec<String>,
473    },
474    /// Interface
475    ///
476    /// Reference: <https://spec.graphql.org/October2021/#sec-Interfaces>
477    Interface {
478        /// The name of the interface
479        name: String,
480        /// The description of the interface
481        description: Option<String>,
482        /// The fields of the interface
483        fields: IndexMap<String, MetaField>,
484        /// The object types that implement this interface
485        /// Add fields to an entity that's defined in another service
486        possible_types: IndexSet<String>,
487        /// Indicates that an interface definition is an extension of another
488        /// definition of that same type.
489        ///
490        /// Reference: <https://www.apollographql.com/docs/federation/federated-types/federated-directives/#extends>
491        extends: bool,
492        /// The keys of the object type
493        ///
494        /// Designates an object type as an [entity](https://www.apollographql.com/docs/federation/entities) and specifies
495        /// its key fields (a set of fields that the subgraph can use to
496        /// uniquely identify any instance of the entity).
497        ///
498        /// Reference: <https://www.apollographql.com/docs/federation/federated-types/federated-directives/#key>
499        keys: Option<Vec<String>>,
500        /// A function that uses to check if the interface should be exported to
501        /// schemas
502        visible: Option<MetaVisibleFn>,
503        /// Indicate that an interface is not accessible from a supergraph when
504        /// using Apollo Federation
505        ///
506        /// Reference: <https://www.apollographql.com/docs/federation/federated-types/federated-directives/#inaccessible>
507        inaccessible: bool,
508        /// Arbitrary string metadata that will be propagated to the supergraph
509        /// when using Apollo Federation. This attribute is repeatable
510        ///
511        /// Reference: <https://www.apollographql.com/docs/federation/federated-types/federated-directives/#applying-metadata>
512        tags: Vec<String>,
513        /// The Rust typename corresponding to the interface
514        rust_typename: Option<&'static str>,
515        /// custom directive invocations
516        directive_invocations: Vec<MetaDirectiveInvocation>,
517        /// Indicates to composition that the target element is accessible only
518        /// to the authenticated supergraph users with the appropriate
519        /// JWT scopes when using Apollo Federation.
520        requires_scopes: Vec<String>,
521    },
522    /// Union
523    ///
524    /// Reference: <https://spec.graphql.org/October2021/#sec-Unions>
525    Union {
526        /// The name of the interface
527        name: String,
528        /// The description of the union
529        description: Option<String>,
530        /// The object types that could be the union
531        possible_types: IndexSet<String>,
532        /// A function that uses to check if the union should be exported to
533        /// schemas
534        visible: Option<MetaVisibleFn>,
535        /// Indicate that an union is not accessible from a supergraph when
536        /// using Apollo Federation
537        ///
538        /// Reference: <https://www.apollographql.com/docs/federation/federated-types/federated-directives/#inaccessible>
539        inaccessible: bool,
540        /// Arbitrary string metadata that will be propagated to the supergraph
541        /// when using Apollo Federation. This attribute is repeatable
542        ///
543        /// Reference: <https://www.apollographql.com/docs/federation/federated-types/federated-directives/#applying-metadata>
544        tags: Vec<String>,
545        /// The Rust typename corresponding to the union
546        rust_typename: Option<&'static str>,
547        /// custom directive invocations
548        directive_invocations: Vec<MetaDirectiveInvocation>,
549    },
550    /// Enum
551    ///
552    /// Reference: <https://spec.graphql.org/October2021/#sec-Enums>
553    Enum {
554        /// The name of the enum
555        name: String,
556        /// The description of the enum
557        description: Option<String>,
558        /// The values of the enum
559        enum_values: IndexMap<String, MetaEnumValue>,
560        /// A function that uses to check if the enum should be exported to
561        /// schemas
562        visible: Option<MetaVisibleFn>,
563        /// Indicate that an enum is not accessible from a supergraph when
564        /// using Apollo Federation
565        ///
566        /// Reference: <https://www.apollographql.com/docs/federation/federated-types/federated-directives/#inaccessible>
567        inaccessible: bool,
568        /// Arbitrary string metadata that will be propagated to the supergraph
569        /// when using Apollo Federation. This attribute is repeatable
570        ///
571        /// Reference: <https://www.apollographql.com/docs/federation/federated-types/federated-directives/#applying-metadata>
572        tags: Vec<String>,
573        /// The Rust typename corresponding to the enum
574        rust_typename: Option<&'static str>,
575        /// custom directive invocations
576        directive_invocations: Vec<MetaDirectiveInvocation>,
577        /// Indicates to composition that the target element is accessible only
578        /// to the authenticated supergraph users with the appropriate
579        /// JWT scopes when using Apollo Federation.
580        requires_scopes: Vec<String>,
581    },
582    /// Input object
583    ///
584    /// Reference: <https://spec.graphql.org/October2021/#sec-Input-Objects>
585    InputObject {
586        /// The name of the input object
587        name: String,
588        /// The description of the input object
589        description: Option<String>,
590        /// The fields of the input object
591        input_fields: IndexMap<String, MetaInputValue>,
592        /// A function that uses to check if the input object should be exported
593        /// to schemas
594        visible: Option<MetaVisibleFn>,
595        /// Indicate that a input object is not accessible from a supergraph
596        /// when using Apollo Federation
597        ///
598        /// Reference: <https://www.apollographql.com/docs/federation/federated-types/federated-directives/#inaccessible>
599        inaccessible: bool,
600        /// Arbitrary string metadata that will be propagated to the supergraph
601        /// when using Apollo Federation. This attribute is repeatable
602        ///
603        /// Reference: <https://www.apollographql.com/docs/federation/federated-types/federated-directives/#applying-metadata>
604        tags: Vec<String>,
605        /// The Rust typename corresponding to the enum
606        rust_typename: Option<&'static str>,
607        /// Is the oneof input objects
608        ///
609        /// Reference: <https://github.com/graphql/graphql-spec/pull/825>
610        oneof: bool,
611        /// custom directive invocations
612        directive_invocations: Vec<MetaDirectiveInvocation>,
613    },
614}
615
616impl MetaType {
617    #[inline]
618    pub fn type_id(&self) -> MetaTypeId {
619        match self {
620            MetaType::Scalar { .. } => MetaTypeId::Scalar,
621            MetaType::Object { .. } => MetaTypeId::Object,
622            MetaType::Interface { .. } => MetaTypeId::Interface,
623            MetaType::Union { .. } => MetaTypeId::Union,
624            MetaType::Enum { .. } => MetaTypeId::Enum,
625            MetaType::InputObject { .. } => MetaTypeId::InputObject,
626        }
627    }
628
629    #[inline]
630    pub fn field_by_name(&self, name: &str) -> Option<&MetaField> {
631        self.fields().and_then(|fields| fields.get(name))
632    }
633
634    #[inline]
635    pub fn fields(&self) -> Option<&IndexMap<String, MetaField>> {
636        match self {
637            MetaType::Object { fields, .. } => Some(&fields),
638            MetaType::Interface { fields, .. } => Some(&fields),
639            _ => None,
640        }
641    }
642
643    #[inline]
644    pub fn is_visible(&self, ctx: &Context<'_>) -> bool {
645        let visible = match self {
646            MetaType::Scalar { visible, .. } => visible,
647            MetaType::Object { visible, .. } => visible,
648            MetaType::Interface { visible, .. } => visible,
649            MetaType::Union { visible, .. } => visible,
650            MetaType::Enum { visible, .. } => visible,
651            MetaType::InputObject { visible, .. } => visible,
652        };
653        is_visible(ctx, visible)
654    }
655
656    #[inline]
657    pub fn name(&self) -> &str {
658        match self {
659            MetaType::Scalar { name, .. } => &name,
660            MetaType::Object { name, .. } => name,
661            MetaType::Interface { name, .. } => name,
662            MetaType::Union { name, .. } => name,
663            MetaType::Enum { name, .. } => name,
664            MetaType::InputObject { name, .. } => name,
665        }
666    }
667
668    #[inline]
669    pub fn is_composite(&self) -> bool {
670        matches!(
671            self,
672            MetaType::Object { .. } | MetaType::Interface { .. } | MetaType::Union { .. }
673        )
674    }
675
676    #[inline]
677    pub fn is_abstract(&self) -> bool {
678        matches!(self, MetaType::Interface { .. } | MetaType::Union { .. })
679    }
680
681    #[inline]
682    pub fn is_leaf(&self) -> bool {
683        matches!(self, MetaType::Enum { .. } | MetaType::Scalar { .. })
684    }
685
686    #[inline]
687    pub fn is_input(&self) -> bool {
688        matches!(
689            self,
690            MetaType::Enum { .. } | MetaType::Scalar { .. } | MetaType::InputObject { .. }
691        )
692    }
693
694    #[inline]
695    pub fn is_possible_type(&self, type_name: &str) -> bool {
696        match self {
697            MetaType::Interface { possible_types, .. } => possible_types.contains(type_name),
698            MetaType::Union { possible_types, .. } => possible_types.contains(type_name),
699            MetaType::Object { name, .. } => name == type_name,
700            _ => false,
701        }
702    }
703
704    #[inline]
705    pub fn possible_types(&self) -> Option<&IndexSet<String>> {
706        match self {
707            MetaType::Interface { possible_types, .. } => Some(possible_types),
708            MetaType::Union { possible_types, .. } => Some(possible_types),
709            _ => None,
710        }
711    }
712
713    pub fn type_overlap(&self, ty: &MetaType) -> bool {
714        if std::ptr::eq(self, ty) {
715            return true;
716        }
717
718        match (self.is_abstract(), ty.is_abstract()) {
719            (true, true) => self
720                .possible_types()
721                .iter()
722                .copied()
723                .flatten()
724                .any(|type_name| ty.is_possible_type(type_name)),
725            (true, false) => self.is_possible_type(ty.name()),
726            (false, true) => ty.is_possible_type(self.name()),
727            (false, false) => false,
728        }
729    }
730
731    pub fn rust_typename(&self) -> Option<&'static str> {
732        match self {
733            MetaType::Scalar { .. } => None,
734            MetaType::Object { rust_typename, .. } => *rust_typename,
735            MetaType::Interface { rust_typename, .. } => *rust_typename,
736            MetaType::Union { rust_typename, .. } => *rust_typename,
737            MetaType::Enum { rust_typename, .. } => *rust_typename,
738            MetaType::InputObject { rust_typename, .. } => *rust_typename,
739        }
740    }
741}
742
743pub struct MetaDirective {
744    pub name: String,
745    pub description: Option<String>,
746    pub locations: Vec<__DirectiveLocation>,
747    pub args: IndexMap<String, MetaInputValue>,
748    pub is_repeatable: bool,
749    pub visible: Option<MetaVisibleFn>,
750    pub composable: Option<String>,
751}
752
753impl MetaDirective {
754    pub(crate) fn sdl(&self, options: &SDLExportOptions) -> String {
755        let mut sdl = String::new();
756
757        if let Some(description) = &self.description {
758            self::export_sdl::write_description(&mut sdl, options, 0, description);
759        }
760
761        write!(sdl, "directive @{}", self.name).ok();
762
763        if !self.args.is_empty() {
764            let args = self
765                .args
766                .values()
767                .map(|value| self.argument_sdl(value))
768                .collect::<Vec<_>>()
769                .join(", ");
770            write!(sdl, "({})", args).ok();
771        }
772        let locations = self
773            .locations
774            .iter()
775            .map(|location| location.to_value().to_string())
776            .collect::<Vec<_>>()
777            .join(" | ");
778
779        if self.is_repeatable {
780            write!(sdl, " repeatable").ok();
781        }
782
783        write!(sdl, " on {}", locations).ok();
784        sdl
785    }
786
787    pub(crate) fn argument_sdl(&self, argument: &MetaInputValue) -> String {
788        let argument_default = match &argument.default_value {
789            Some(default) => format!(" = {default}"),
790            None => "".to_string(),
791        };
792
793        format!("{}: {}{}", argument.name, argument.ty, argument_default)
794    }
795}
796
797/// A type registry for build schemas
798#[derive(Default)]
799pub struct Registry {
800    pub types: BTreeMap<String, MetaType>,
801    pub directives: BTreeMap<String, MetaDirective>,
802    pub implements: HashMap<String, IndexSet<String>>,
803    pub query_type: String,
804    pub mutation_type: Option<String>,
805    pub subscription_type: Option<String>,
806    pub introspection_mode: IntrospectionMode,
807    pub enable_federation: bool,
808    pub federation_subscription: bool,
809    pub ignore_name_conflicts: HashSet<String>,
810    pub enable_suggestions: bool,
811}
812
813impl Registry {
814    pub(crate) fn add_system_types(&mut self) {
815        self.add_directive(MetaDirective {
816            name: "skip".into(),
817            description: Some("Directs the executor to skip this field or fragment when the `if` argument is true.".to_string()),
818            locations: vec![
819                __DirectiveLocation::FIELD,
820                __DirectiveLocation::FRAGMENT_SPREAD,
821                __DirectiveLocation::INLINE_FRAGMENT
822            ],
823            args: {
824                let mut args = IndexMap::new();
825                args.insert("if".to_string(), MetaInputValue {
826                    name: "if".to_string(),
827                    description: Some("Skipped when true.".to_string()),
828                    ty: "Boolean!".to_string(),
829                    deprecation: Deprecation::NoDeprecated,
830                    default_value: None,
831                    visible: None,
832                    inaccessible: false,
833                    tags: Default::default(),
834                    is_secret: false,
835                    directive_invocations: vec![]
836                });
837                args
838            },
839            is_repeatable: false,
840            visible: None,
841            composable: None,
842        });
843
844        self.add_directive(MetaDirective {
845            name: "include".into(),
846            description: Some("Directs the executor to include this field or fragment only when the `if` argument is true.".to_string()),
847            locations: vec![
848                __DirectiveLocation::FIELD,
849                __DirectiveLocation::FRAGMENT_SPREAD,
850                __DirectiveLocation::INLINE_FRAGMENT
851            ],
852            args: {
853                let mut args = IndexMap::new();
854                args.insert("if".to_string(), MetaInputValue {
855                    name: "if".to_string(),
856                    description: Some("Included when true.".to_string()),
857                    ty: "Boolean!".to_string(),
858                    deprecation: Deprecation::NoDeprecated,
859                    default_value: None,
860                    visible: None,
861                    inaccessible: false,
862                    tags: Default::default(),
863                    is_secret: false,
864                    directive_invocations: vec![]
865                });
866                args
867            },
868            is_repeatable: false,
869            visible: None,
870            composable: None,
871        });
872
873        self.add_directive(MetaDirective {
874            name: "deprecated".into(),
875            description: Some(
876                "Marks an element of a GraphQL schema as no longer supported.".into(),
877            ),
878            locations: vec![
879                __DirectiveLocation::FIELD_DEFINITION,
880                __DirectiveLocation::ARGUMENT_DEFINITION,
881                __DirectiveLocation::INPUT_FIELD_DEFINITION,
882                __DirectiveLocation::ENUM_VALUE,
883            ],
884            args: {
885                let mut args = IndexMap::new();
886                args.insert(
887                    "reason".into(),
888                    MetaInputValue {
889                        name: "reason".into(),
890                        description: Some(
891                            "A reason for why it is deprecated, formatted using Markdown syntax"
892                                .into(),
893                        ),
894                        ty: "String".into(),
895                        deprecation: Deprecation::NoDeprecated,
896                        default_value: Some(r#""No longer supported""#.into()),
897                        visible: None,
898                        inaccessible: false,
899                        tags: Default::default(),
900                        is_secret: false,
901                        directive_invocations: vec![],
902                    },
903                );
904                args
905            },
906            is_repeatable: false,
907            visible: None,
908            composable: None,
909        });
910
911        self.add_directive(MetaDirective {
912            name: "specifiedBy".into(),
913            description: Some("Provides a scalar specification URL for specifying the behavior of custom scalar types.".into()),
914            locations: vec![__DirectiveLocation::SCALAR],
915            args: {
916                let mut args = IndexMap::new();
917                args.insert(
918                    "url".into(),
919                    MetaInputValue {
920                        name: "url".into(),
921                        description: Some("URL that specifies the behavior of this scalar.".into()),
922                        ty: "String!".into(),
923                        deprecation: Deprecation::NoDeprecated,
924                        default_value: None,
925                        visible: None,
926                        inaccessible: false,
927                        tags: Default::default(),
928                        is_secret: false,
929                        directive_invocations: vec![],
930                    },
931                );
932                args
933            },
934            is_repeatable: false,
935            visible: None,
936            composable: None,
937        });
938
939        self.add_directive(MetaDirective {
940            name: "oneOf".into(),
941            description: Some(
942                "Indicates that an Input Object is a OneOf Input Object (and thus requires \
943                exactly one of its field be provided)"
944                    .to_string(),
945            ),
946            locations: vec![__DirectiveLocation::INPUT_OBJECT],
947            args: Default::default(),
948            is_repeatable: false,
949            visible: None,
950            composable: None,
951        });
952
953        // create system scalars
954        <bool as InputType>::create_type_info(self);
955        <i32 as InputType>::create_type_info(self);
956        <f32 as InputType>::create_type_info(self);
957        <String as InputType>::create_type_info(self);
958        <ID as InputType>::create_type_info(self);
959    }
960
961    pub fn create_input_type<T, F>(&mut self, type_id: MetaTypeId, mut f: F) -> String
962    where
963        T: InputType,
964        F: FnMut(&mut Registry) -> MetaType,
965    {
966        self.create_type(&mut f, &T::type_name(), std::any::type_name::<T>(), type_id);
967        T::qualified_type_name()
968    }
969
970    pub fn create_output_type<T, F>(&mut self, type_id: MetaTypeId, mut f: F) -> String
971    where
972        T: OutputType + ?Sized,
973        F: FnMut(&mut Registry) -> MetaType,
974    {
975        self.create_type(&mut f, &T::type_name(), std::any::type_name::<T>(), type_id);
976        T::qualified_type_name()
977    }
978
979    pub fn create_subscription_type<T, F>(&mut self, mut f: F) -> String
980    where
981        T: SubscriptionType + ?Sized,
982        F: FnMut(&mut Registry) -> MetaType,
983    {
984        self.create_type(
985            &mut f,
986            &T::type_name(),
987            std::any::type_name::<T>(),
988            MetaTypeId::Object,
989        );
990        T::qualified_type_name()
991    }
992
993    fn create_type<F: FnMut(&mut Registry) -> MetaType>(
994        &mut self,
995        f: &mut F,
996        name: &str,
997        rust_typename: &'static str,
998        type_id: MetaTypeId,
999    ) {
1000        match self.types.get(name) {
1001            Some(ty) => {
1002                if let Some(prev_typename) = ty.rust_typename() {
1003                    if prev_typename == "__fake_type__" {
1004                        return;
1005                    }
1006
1007                    if rust_typename != prev_typename && !self.ignore_name_conflicts.contains(name)
1008                    {
1009                        panic!(
1010                            "`{}` and `{}` have the same GraphQL name `{}`",
1011                            prev_typename, rust_typename, name,
1012                        );
1013                    }
1014
1015                    if ty.type_id() != type_id {
1016                        panic!(
1017                            "Register `{}` as `{}`, but it is already registered as `{}`",
1018                            name,
1019                            type_id,
1020                            ty.type_id()
1021                        );
1022                    }
1023                }
1024            }
1025            None => {
1026                // Inserting a fake type before calling the function allows recursive types to
1027                // exist.
1028                self.types
1029                    .insert(name.to_string(), type_id.create_fake_type(rust_typename));
1030                let ty = f(self);
1031                *self.types.get_mut(name).unwrap() = ty;
1032            }
1033        }
1034    }
1035
1036    pub fn create_fake_output_type<T: OutputType>(&mut self) -> MetaType {
1037        T::create_type_info(self);
1038        self.types
1039            .get(&*T::type_name())
1040            .cloned()
1041            .expect("You definitely encountered a bug!")
1042    }
1043
1044    pub fn create_fake_input_type<T: InputType>(&mut self) -> MetaType {
1045        T::create_type_info(self);
1046        self.types
1047            .get(&*T::type_name())
1048            .cloned()
1049            .expect("You definitely encountered a bug!")
1050    }
1051
1052    pub fn create_fake_subscription_type<T: SubscriptionType>(&mut self) -> MetaType {
1053        T::create_type_info(self);
1054        self.types
1055            .get(&*T::type_name())
1056            .cloned()
1057            .expect("You definitely encountered a bug!")
1058    }
1059
1060    pub fn add_directive(&mut self, directive: MetaDirective) {
1061        self.directives
1062            .insert(directive.name.to_string(), directive);
1063    }
1064
1065    pub fn add_implements(&mut self, ty: &str, interface: &str) {
1066        self.implements
1067            .entry(ty.to_string())
1068            .and_modify(|interfaces| {
1069                interfaces.insert(interface.to_string());
1070            })
1071            .or_insert({
1072                let mut interfaces = IndexSet::new();
1073                interfaces.insert(interface.to_string());
1074                interfaces
1075            });
1076    }
1077
1078    pub fn add_keys(&mut self, ty: &str, keys: impl Into<String>) {
1079        let all_keys = match self.types.get_mut(ty) {
1080            Some(MetaType::Object { keys: all_keys, .. }) => all_keys,
1081            Some(MetaType::Interface { keys: all_keys, .. }) => all_keys,
1082            _ => return,
1083        };
1084        if let Some(all_keys) = all_keys {
1085            all_keys.push(keys.into());
1086        } else {
1087            *all_keys = Some(vec![keys.into()]);
1088        }
1089    }
1090
1091    pub fn concrete_type_by_name(&self, type_name: &str) -> Option<&MetaType> {
1092        self.types.get(MetaTypeName::concrete_typename(type_name))
1093    }
1094
1095    pub fn concrete_type_by_parsed_type(&self, query_type: &ParsedType) -> Option<&MetaType> {
1096        match &query_type.base {
1097            ParsedBaseType::Named(name) => self.types.get(name.as_str()),
1098            ParsedBaseType::List(ty) => self.concrete_type_by_parsed_type(ty),
1099        }
1100    }
1101
1102    pub(crate) fn has_entities(&self) -> bool {
1103        self.types.values().any(|ty| match ty {
1104            MetaType::Object {
1105                keys: Some(keys),
1106                resolvable: true,
1107                ..
1108            }
1109            | MetaType::Interface {
1110                keys: Some(keys), ..
1111            } => !keys.is_empty(),
1112            _ => false,
1113        })
1114    }
1115
1116    /// Each type annotated with @key should be added to the _Entity union.
1117    /// If no types are annotated with the key directive, then the _Entity union
1118    /// and Query._entities field should be removed from the schema.
1119    ///
1120    /// [Reference](https://www.apollographql.com/docs/federation/federation-spec/#resolve-requests-for-entities).
1121    fn create_entity_type_and_root_field(&mut self) {
1122        let possible_types: IndexSet<String> = self
1123            .types
1124            .values()
1125            .filter_map(|ty| match ty {
1126                MetaType::Object {
1127                    name,
1128                    keys: Some(keys),
1129                    resolvable: true,
1130                    ..
1131                } if !keys.is_empty() => Some(name.clone()),
1132                MetaType::Interface {
1133                    name,
1134                    keys: Some(keys),
1135                    ..
1136                } if !keys.is_empty() => Some(name.clone()),
1137                _ => None,
1138            })
1139            .collect();
1140
1141        if let MetaType::Object { fields, .. } = self
1142            .types
1143            .get_mut(&self.query_type)
1144            .expect("missing query type")
1145        {
1146            fields.insert(
1147                "_service".to_string(),
1148                MetaField {
1149                    name: "_service".to_string(),
1150                    description: None,
1151                    args: Default::default(),
1152                    ty: "_Service!".to_string(),
1153                    deprecation: Default::default(),
1154                    cache_control: Default::default(),
1155                    external: false,
1156                    requires: None,
1157                    provides: None,
1158                    shareable: false,
1159                    inaccessible: false,
1160                    tags: Default::default(),
1161                    override_from: None,
1162                    visible: None,
1163                    compute_complexity: None,
1164                    directive_invocations: vec![],
1165                    requires_scopes: vec![],
1166                },
1167            );
1168        }
1169
1170        if !possible_types.is_empty() {
1171            self.types.insert(
1172                "_Entity".to_string(),
1173                MetaType::Union {
1174                    name: "_Entity".to_string(),
1175                    description: None,
1176                    possible_types,
1177                    visible: None,
1178                    inaccessible: false,
1179                    tags: Default::default(),
1180                    rust_typename: Some("async_graphql::federation::Entity"),
1181                    directive_invocations: vec![],
1182                },
1183            );
1184
1185            if let MetaType::Object { fields, .. } = self.types.get_mut(&self.query_type).unwrap() {
1186                fields.insert(
1187                    "_entities".to_string(),
1188                    MetaField {
1189                        name: "_entities".to_string(),
1190                        description: None,
1191                        args: {
1192                            let mut args = IndexMap::new();
1193                            args.insert(
1194                                "representations".to_string(),
1195                                MetaInputValue {
1196                                    name: "representations".to_string(),
1197                                    description: None,
1198                                    ty: "[_Any!]!".to_string(),
1199                                    deprecation: Deprecation::NoDeprecated,
1200                                    default_value: None,
1201                                    visible: None,
1202                                    inaccessible: false,
1203                                    tags: Default::default(),
1204                                    is_secret: false,
1205                                    directive_invocations: vec![],
1206                                },
1207                            );
1208                            args
1209                        },
1210                        ty: "[_Entity]!".to_string(),
1211                        deprecation: Default::default(),
1212                        cache_control: Default::default(),
1213                        external: false,
1214                        requires: None,
1215                        provides: None,
1216                        shareable: false,
1217                        visible: None,
1218                        inaccessible: false,
1219                        tags: Default::default(),
1220                        override_from: None,
1221                        compute_complexity: None,
1222                        directive_invocations: vec![],
1223                        requires_scopes: vec![],
1224                    },
1225                );
1226            }
1227        }
1228    }
1229
1230    pub(crate) fn create_introspection_types(&mut self) {
1231        __Schema::create_type_info(self);
1232
1233        if let Some(MetaType::Object { fields, .. }) = self.types.get_mut(&self.query_type) {
1234            fields.insert(
1235                "__schema".to_string(),
1236                MetaField {
1237                    name: "__schema".to_string(),
1238                    description: Some("Access the current type schema of this server.".to_string()),
1239                    args: Default::default(),
1240                    ty: "__Schema".to_string(),
1241                    deprecation: Default::default(),
1242                    cache_control: Default::default(),
1243                    external: false,
1244                    requires: None,
1245                    provides: None,
1246                    shareable: false,
1247                    inaccessible: false,
1248                    tags: Default::default(),
1249                    visible: None,
1250                    compute_complexity: None,
1251                    override_from: None,
1252                    directive_invocations: vec![],
1253                    requires_scopes: vec![],
1254                },
1255            );
1256
1257            fields.insert(
1258                "__type".to_string(),
1259                MetaField {
1260                    name: "__type".to_string(),
1261                    description: Some("Request the type information of a single type.".to_string()),
1262                    args: {
1263                        let mut args = IndexMap::new();
1264                        args.insert(
1265                            "name".to_string(),
1266                            MetaInputValue {
1267                                name: "name".to_string(),
1268                                description: None,
1269                                ty: "String!".to_string(),
1270                                deprecation: Deprecation::NoDeprecated,
1271                                default_value: None,
1272                                visible: None,
1273                                inaccessible: false,
1274                                tags: Default::default(),
1275                                is_secret: false,
1276                                directive_invocations: vec![],
1277                            },
1278                        );
1279                        args
1280                    },
1281                    ty: "__Type".to_string(),
1282                    deprecation: Default::default(),
1283                    cache_control: Default::default(),
1284                    external: false,
1285                    requires: None,
1286                    provides: None,
1287                    shareable: false,
1288                    inaccessible: false,
1289                    tags: Default::default(),
1290                    override_from: None,
1291                    visible: None,
1292                    compute_complexity: None,
1293                    directive_invocations: vec![],
1294                    requires_scopes: vec![],
1295                },
1296            );
1297        }
1298    }
1299
1300    pub(crate) fn create_federation_types(&mut self) {
1301        <Any as InputType>::create_type_info(self);
1302
1303        self.types.insert(
1304            "_Service".to_string(),
1305            MetaType::Object {
1306                name: "_Service".to_string(),
1307                description: None,
1308                fields: {
1309                    let mut fields = IndexMap::new();
1310                    fields.insert(
1311                        "sdl".to_string(),
1312                        MetaField {
1313                            name: "sdl".to_string(),
1314                            description: None,
1315                            args: Default::default(),
1316                            ty: "String".to_string(),
1317                            deprecation: Default::default(),
1318                            cache_control: Default::default(),
1319                            external: false,
1320                            requires: None,
1321                            provides: None,
1322                            shareable: false,
1323                            visible: None,
1324                            inaccessible: false,
1325                            tags: Default::default(),
1326                            override_from: None,
1327                            compute_complexity: None,
1328                            directive_invocations: vec![],
1329                            requires_scopes: vec![],
1330                        },
1331                    );
1332                    fields
1333                },
1334                cache_control: Default::default(),
1335                extends: false,
1336                shareable: false,
1337                resolvable: true,
1338                interface_object: false,
1339                keys: None,
1340                visible: None,
1341                inaccessible: false,
1342                tags: Default::default(),
1343                is_subscription: false,
1344                rust_typename: Some("async_graphql::federation::Service"),
1345                directive_invocations: vec![],
1346                requires_scopes: vec![],
1347            },
1348        );
1349
1350        self.create_entity_type_and_root_field();
1351    }
1352
1353    pub fn names(&self) -> Vec<String> {
1354        let mut names = HashSet::new();
1355
1356        for d in self.directives.values() {
1357            names.insert(d.name.to_string());
1358            names.extend(d.args.values().map(|arg| arg.name.to_string()));
1359        }
1360
1361        for ty in self.types.values() {
1362            match ty {
1363                MetaType::Scalar { name, .. } | MetaType::Union { name, .. } => {
1364                    names.insert(name.clone());
1365                }
1366                MetaType::Object { name, fields, .. }
1367                | MetaType::Interface { name, fields, .. } => {
1368                    names.insert(name.clone());
1369                    names.extend(
1370                        fields
1371                            .values()
1372                            .map(|field| {
1373                                std::iter::once(field.name.clone())
1374                                    .chain(field.args.values().map(|arg| arg.name.to_string()))
1375                            })
1376                            .flatten(),
1377                    );
1378                }
1379                MetaType::Enum {
1380                    name, enum_values, ..
1381                } => {
1382                    names.insert(name.clone());
1383                    names.extend(enum_values.values().map(|value| value.name.to_string()));
1384                }
1385                MetaType::InputObject {
1386                    name, input_fields, ..
1387                } => {
1388                    names.insert(name.clone());
1389                    names.extend(input_fields.values().map(|field| field.name.to_string()));
1390                }
1391            }
1392        }
1393
1394        names.into_iter().collect()
1395    }
1396
1397    pub fn set_description(&mut self, name: impl AsRef<str>, desc: impl Into<String>) {
1398        let desc = desc.into();
1399        match self.types.get_mut(name.as_ref()) {
1400            Some(MetaType::Scalar { description, .. }) => *description = Some(desc),
1401            Some(MetaType::Object { description, .. }) => *description = Some(desc),
1402            Some(MetaType::Interface { description, .. }) => *description = Some(desc),
1403            Some(MetaType::Union { description, .. }) => *description = Some(desc),
1404            Some(MetaType::Enum { description, .. }) => *description = Some(desc),
1405            Some(MetaType::InputObject { description, .. }) => *description = Some(desc),
1406            None => {}
1407        }
1408    }
1409
1410    pub fn remove_unused_types(&mut self) {
1411        let mut used_types = BTreeSet::new();
1412        let mut unused_types = BTreeSet::new();
1413
1414        fn traverse_field<'a>(
1415            types: &'a BTreeMap<String, MetaType>,
1416            used_types: &mut BTreeSet<&'a str>,
1417            field: &'a MetaField,
1418        ) {
1419            traverse_type(
1420                types,
1421                used_types,
1422                MetaTypeName::concrete_typename(&field.ty),
1423            );
1424            for arg in field.args.values() {
1425                traverse_input_value(types, used_types, arg);
1426            }
1427        }
1428
1429        fn traverse_input_value<'a>(
1430            types: &'a BTreeMap<String, MetaType>,
1431            used_types: &mut BTreeSet<&'a str>,
1432            input_value: &'a MetaInputValue,
1433        ) {
1434            traverse_type(
1435                types,
1436                used_types,
1437                MetaTypeName::concrete_typename(&input_value.ty),
1438            );
1439        }
1440
1441        fn traverse_type<'a>(
1442            types: &'a BTreeMap<String, MetaType>,
1443            used_types: &mut BTreeSet<&'a str>,
1444            type_name: &'a str,
1445        ) {
1446            if used_types.contains(type_name) {
1447                return;
1448            }
1449
1450            if let Some(ty) = types.get(type_name) {
1451                used_types.insert(type_name);
1452                match ty {
1453                    MetaType::Object { fields, .. } => {
1454                        for field in fields.values() {
1455                            traverse_field(types, used_types, field);
1456                        }
1457                    }
1458                    MetaType::Interface {
1459                        fields,
1460                        possible_types,
1461                        ..
1462                    } => {
1463                        for field in fields.values() {
1464                            traverse_field(types, used_types, field);
1465                        }
1466                        for type_name in possible_types.iter() {
1467                            traverse_type(types, used_types, type_name);
1468                        }
1469                    }
1470                    MetaType::Union { possible_types, .. } => {
1471                        for type_name in possible_types.iter() {
1472                            traverse_type(types, used_types, type_name);
1473                        }
1474                    }
1475                    MetaType::InputObject { input_fields, .. } => {
1476                        for field in input_fields.values() {
1477                            traverse_input_value(types, used_types, field);
1478                        }
1479                    }
1480                    _ => {}
1481                }
1482            }
1483        }
1484
1485        for directive in self.directives.values() {
1486            for arg in directive.args.values() {
1487                traverse_input_value(&self.types, &mut used_types, arg);
1488            }
1489        }
1490
1491        for type_name in Some(&self.query_type)
1492            .into_iter()
1493            .chain(self.mutation_type.iter())
1494            .chain(self.subscription_type.iter())
1495        {
1496            traverse_type(&self.types, &mut used_types, type_name);
1497        }
1498
1499        for ty in self.types.values().filter(|ty| match ty {
1500            MetaType::Object {
1501                keys: Some(keys), ..
1502            }
1503            | MetaType::Interface {
1504                keys: Some(keys), ..
1505            } => !keys.is_empty(),
1506            _ => false,
1507        }) {
1508            traverse_type(&self.types, &mut used_types, ty.name());
1509        }
1510
1511        for ty in self.types.values() {
1512            let name = ty.name();
1513            if !is_system_type(name) && !used_types.contains(name) {
1514                unused_types.insert(name.to_string());
1515            }
1516        }
1517
1518        for type_name in unused_types {
1519            self.types.remove(&type_name);
1520        }
1521    }
1522
1523    pub fn find_visible_types(&self, ctx: &Context<'_>) -> HashSet<&str> {
1524        let mut visible_types = HashSet::new();
1525
1526        fn traverse_field<'a>(
1527            ctx: &Context<'_>,
1528            types: &'a BTreeMap<String, MetaType>,
1529            visible_types: &mut HashSet<&'a str>,
1530            field: &'a MetaField,
1531        ) {
1532            if !is_visible(ctx, &field.visible) {
1533                return;
1534            }
1535
1536            traverse_type(
1537                ctx,
1538                types,
1539                visible_types,
1540                MetaTypeName::concrete_typename(&field.ty),
1541            );
1542            for arg in field.args.values() {
1543                traverse_input_value(ctx, types, visible_types, arg);
1544            }
1545        }
1546
1547        fn traverse_input_value<'a>(
1548            ctx: &Context<'_>,
1549            types: &'a BTreeMap<String, MetaType>,
1550            visible_types: &mut HashSet<&'a str>,
1551            input_value: &'a MetaInputValue,
1552        ) {
1553            if !is_visible(ctx, &input_value.visible) {
1554                return;
1555            }
1556
1557            traverse_type(
1558                ctx,
1559                types,
1560                visible_types,
1561                MetaTypeName::concrete_typename(&input_value.ty),
1562            );
1563        }
1564
1565        fn traverse_type<'a>(
1566            ctx: &Context<'_>,
1567            types: &'a BTreeMap<String, MetaType>,
1568            visible_types: &mut HashSet<&'a str>,
1569            type_name: &'a str,
1570        ) {
1571            if visible_types.contains(type_name) {
1572                return;
1573            }
1574
1575            if let Some(ty) = types.get(type_name) {
1576                if !ty.is_visible(ctx) {
1577                    return;
1578                }
1579
1580                visible_types.insert(type_name);
1581                match ty {
1582                    MetaType::Object { fields, .. } => {
1583                        for field in fields.values() {
1584                            traverse_field(ctx, types, visible_types, field);
1585                        }
1586                    }
1587                    MetaType::Interface {
1588                        fields,
1589                        possible_types,
1590                        ..
1591                    } => {
1592                        for field in fields.values() {
1593                            traverse_field(ctx, types, visible_types, field);
1594                        }
1595                        for type_name in possible_types.iter() {
1596                            traverse_type(ctx, types, visible_types, type_name);
1597                        }
1598                    }
1599                    MetaType::Union { possible_types, .. } => {
1600                        for type_name in possible_types.iter() {
1601                            traverse_type(ctx, types, visible_types, type_name);
1602                        }
1603                    }
1604                    MetaType::InputObject { input_fields, .. } => {
1605                        for field in input_fields.values() {
1606                            traverse_input_value(ctx, types, visible_types, field);
1607                        }
1608                    }
1609                    _ => {}
1610                }
1611            }
1612        }
1613
1614        for directive in self.directives.values() {
1615            if is_visible(ctx, &directive.visible) {
1616                for arg in directive.args.values() {
1617                    traverse_input_value(ctx, &self.types, &mut visible_types, arg);
1618                }
1619            }
1620        }
1621
1622        for type_name in Some(&self.query_type)
1623            .into_iter()
1624            .chain(self.mutation_type.iter())
1625            .chain(self.subscription_type.iter())
1626        {
1627            traverse_type(ctx, &self.types, &mut visible_types, type_name);
1628        }
1629
1630        for ty in self.types.values().filter(|ty| match ty {
1631            MetaType::Object {
1632                keys: Some(keys), ..
1633            }
1634            | MetaType::Interface {
1635                keys: Some(keys), ..
1636            } => !keys.is_empty(),
1637            _ => false,
1638        }) {
1639            traverse_type(ctx, &self.types, &mut visible_types, ty.name());
1640        }
1641
1642        for ty in self.types.values() {
1643            if let MetaType::Interface { possible_types, .. } = ty
1644                && ty.is_visible(ctx)
1645                && !visible_types.contains(ty.name())
1646            {
1647                for type_name in possible_types.iter() {
1648                    if visible_types.contains(type_name.as_str()) {
1649                        traverse_type(ctx, &self.types, &mut visible_types, ty.name());
1650                        break;
1651                    }
1652                }
1653            }
1654        }
1655
1656        self.types
1657            .values()
1658            .filter_map(|ty| {
1659                let name = ty.name();
1660                if is_system_type(name) || visible_types.contains(name) {
1661                    Some(name)
1662                } else {
1663                    None
1664                }
1665            })
1666            .collect()
1667    }
1668}
1669
1670pub(crate) fn is_visible(ctx: &Context<'_>, visible: &Option<MetaVisibleFn>) -> bool {
1671    match visible {
1672        Some(f) => f(ctx),
1673        None => true,
1674    }
1675}
1676
1677fn is_system_type(name: &str) -> bool {
1678    if name.starts_with("__") {
1679        return true;
1680    }
1681
1682    name == "Boolean" || name == "Int" || name == "Float" || name == "String" || name == "ID"
1683}
1684
1685#[cfg(test)]
1686mod test {
1687    use crate::{
1688        SDLExportOptions,
1689        registry::{__DirectiveLocation, MetaDirective, MetaDirectiveInvocation},
1690    };
1691
1692    #[test]
1693    fn test_directive_invocation_dsl() {
1694        let expected = r#"@testDirective(int_value: 1, str_value: "abc")"#;
1695        assert_eq!(
1696            expected.to_string(),
1697            MetaDirectiveInvocation {
1698                name: "testDirective".to_string(),
1699                args: [
1700                    ("int_value".to_string(), 1u32.into()),
1701                    ("str_value".to_string(), "abc".into())
1702                ]
1703                .into(),
1704            }
1705            .sdl()
1706        )
1707    }
1708
1709    #[test]
1710    fn test_repeatable_directive_dsl() {
1711        let expected = r#"directive @testDirective repeatable on OBJECT | INTERFACE"#;
1712        let export_options = SDLExportOptions::default();
1713
1714        assert_eq!(
1715            expected.to_string(),
1716            MetaDirective {
1717                name: "testDirective".to_string(),
1718                description: None,
1719                locations: vec![__DirectiveLocation::OBJECT, __DirectiveLocation::INTERFACE,],
1720                args: Default::default(),
1721                is_repeatable: true,
1722                visible: None,
1723                composable: None,
1724            }
1725            .sdl(&export_options)
1726        )
1727    }
1728
1729    #[test]
1730    fn test_non_repeatable_directive_dsl() {
1731        let expected = r#"directive @testDirective on OBJECT | INTERFACE"#;
1732        let export_options = SDLExportOptions::default();
1733
1734        assert_eq!(
1735            expected.to_string(),
1736            MetaDirective {
1737                name: "testDirective".to_string(),
1738                description: None,
1739                locations: vec![__DirectiveLocation::OBJECT, __DirectiveLocation::INTERFACE,],
1740                args: Default::default(),
1741                is_repeatable: false,
1742                visible: None,
1743                composable: None,
1744            }
1745            .sdl(&export_options)
1746        )
1747    }
1748}