juniper/schema/
model.rs

1use std::ptr;
2
3use arcstr::ArcStr;
4use derive_more::with_trait::Display;
5use fnv::FnvHashMap;
6#[cfg(feature = "schema-language")]
7use graphql_parser::schema::Document;
8
9use crate::{
10    GraphQLEnum,
11    ast::{Type, TypeModifier},
12    executor::{Context, Registry},
13    schema::meta::{Argument, InterfaceMeta, MetaType, ObjectMeta, PlaceholderMeta, UnionMeta},
14    types::{base::GraphQLType, name::Name},
15    value::{DefaultScalarValue, ScalarValue},
16};
17
18/// Root query node of a schema
19///
20/// This brings the mutation, subscription and query types together,
21/// and provides the predefined metadata fields.
22#[derive(Debug)]
23pub struct RootNode<
24    QueryT: GraphQLType<S>,
25    MutationT: GraphQLType<S>,
26    SubscriptionT: GraphQLType<S>,
27    S = DefaultScalarValue,
28> where
29    S: ScalarValue,
30{
31    #[doc(hidden)]
32    pub query_type: QueryT,
33    #[doc(hidden)]
34    pub query_info: QueryT::TypeInfo,
35    #[doc(hidden)]
36    pub mutation_type: MutationT,
37    #[doc(hidden)]
38    pub mutation_info: MutationT::TypeInfo,
39    #[doc(hidden)]
40    pub subscription_type: SubscriptionT,
41    #[doc(hidden)]
42    pub subscription_info: SubscriptionT::TypeInfo,
43    #[doc(hidden)]
44    pub schema: SchemaType<S>,
45    #[doc(hidden)]
46    pub introspection_disabled: bool,
47}
48
49impl<QueryT, MutationT, SubscriptionT>
50    RootNode<QueryT, MutationT, SubscriptionT, DefaultScalarValue>
51where
52    QueryT: GraphQLType<DefaultScalarValue, TypeInfo = ()>,
53    MutationT: GraphQLType<DefaultScalarValue, TypeInfo = ()>,
54    SubscriptionT: GraphQLType<DefaultScalarValue, TypeInfo = ()>,
55{
56    /// Constructs a new [`RootNode`] from `query`, `mutation` and `subscription` nodes,
57    /// parametrizing it with a [`DefaultScalarValue`].
58    pub fn new(query: QueryT, mutation: MutationT, subscription: SubscriptionT) -> Self {
59        Self::new_with_info(query, mutation, subscription, (), (), ())
60    }
61}
62
63impl<QueryT, MutationT, SubscriptionT, S> RootNode<QueryT, MutationT, SubscriptionT, S>
64where
65    S: ScalarValue,
66    QueryT: GraphQLType<S, TypeInfo = ()>,
67    MutationT: GraphQLType<S, TypeInfo = ()>,
68    SubscriptionT: GraphQLType<S, TypeInfo = ()>,
69{
70    /// Constructs a new [`RootNode`] from `query`, `mutation` and `subscription` nodes,
71    /// parametrizing it with the provided [`ScalarValue`].
72    pub fn new_with_scalar_value(
73        query: QueryT,
74        mutation: MutationT,
75        subscription: SubscriptionT,
76    ) -> Self {
77        RootNode::new_with_info(query, mutation, subscription, (), (), ())
78    }
79}
80
81impl<S, QueryT, MutationT, SubscriptionT> RootNode<QueryT, MutationT, SubscriptionT, S>
82where
83    QueryT: GraphQLType<S>,
84    MutationT: GraphQLType<S>,
85    SubscriptionT: GraphQLType<S>,
86    S: ScalarValue,
87{
88    /// Construct a new root node from query and mutation nodes,
89    /// while also providing type info objects for the query and
90    /// mutation types.
91    pub fn new_with_info(
92        query_obj: QueryT,
93        mutation_obj: MutationT,
94        subscription_obj: SubscriptionT,
95        query_info: QueryT::TypeInfo,
96        mutation_info: MutationT::TypeInfo,
97        subscription_info: SubscriptionT::TypeInfo,
98    ) -> Self {
99        Self {
100            query_type: query_obj,
101            mutation_type: mutation_obj,
102            subscription_type: subscription_obj,
103            schema: SchemaType::new::<QueryT, MutationT, SubscriptionT>(
104                &query_info,
105                &mutation_info,
106                &subscription_info,
107            ),
108            query_info,
109            mutation_info,
110            subscription_info,
111            introspection_disabled: false,
112        }
113    }
114
115    /// Disables introspection for this [`RootNode`], making it to return a [`FieldError`] whenever
116    /// its `__schema` or `__type` field is resolved.
117    ///
118    /// By default, all introspection queries are allowed.
119    ///
120    /// # Example
121    ///
122    /// ```rust
123    /// # use juniper::{
124    /// #     graphql_object, graphql_vars, EmptyMutation, EmptySubscription, GraphQLError,
125    /// #     RootNode,
126    /// # };
127    /// #
128    /// pub struct Query;
129    ///
130    /// #[graphql_object]
131    /// impl Query {
132    ///     fn some() -> bool {
133    ///         true
134    ///     }
135    /// }
136    ///
137    /// type Schema = RootNode<Query, EmptyMutation, EmptySubscription>;
138    ///
139    /// let schema = Schema::new(Query, EmptyMutation::new(), EmptySubscription::new())
140    ///     .disable_introspection();
141    ///
142    /// # // language=GraphQL
143    /// let query = "query { __schema { queryType { name } } }";
144    ///
145    /// match juniper::execute_sync(query, None, &schema, &graphql_vars! {}, &()) {
146    ///     Err(GraphQLError::ValidationError(errs)) => {
147    ///         assert_eq!(
148    ///             errs.first().unwrap().message(),
149    ///             "GraphQL introspection is not allowed, but the operation contained `__schema`",
150    ///         );
151    ///     }
152    ///     res => panic!("expected `ValidationError`, returned: {res:#?}"),
153    /// }
154    /// ```
155    pub fn disable_introspection(mut self) -> Self {
156        self.introspection_disabled = true;
157        self
158    }
159
160    /// Enables introspection for this [`RootNode`], if it was previously [disabled][1].
161    ///
162    /// By default, all introspection queries are allowed.
163    ///
164    /// [1]: RootNode::disable_introspection
165    pub fn enable_introspection(mut self) -> Self {
166        self.introspection_disabled = false;
167        self
168    }
169
170    #[cfg(feature = "schema-language")]
171    /// Returns this [`RootNode`] as a [`String`] containing the schema in [SDL (schema definition language)].
172    ///
173    /// # Sorted
174    ///
175    /// The order of the generated definitions is stable and is sorted in the "type-then-name" manner.
176    ///
177    /// If another sorting order is required, then the [`as_document()`] method should be used, which allows to sort the
178    /// returned [`Document`] in the desired manner and then to convert it [`to_string()`].
179    ///
180    /// [`as_document()`]: RootNode::as_document
181    /// [`to_string()`]: ToString::to_string
182    /// [0]: https://graphql.org/learn/schema#type-language
183    #[must_use]
184    pub fn as_sdl(&self) -> String {
185        use crate::schema::translate::graphql_parser::sort_schema_document;
186
187        let mut doc = self.as_document();
188        sort_schema_document(&mut doc);
189        doc.to_string()
190    }
191
192    #[cfg(feature = "schema-language")]
193    /// Returns this [`RootNode`] as a [`graphql_parser`]'s [`Document`].
194    ///
195    /// # Unsorted
196    ///
197    /// The order of the generated definitions in the returned [`Document`] is NOT stable and may change without any
198    /// real schema changes.
199    #[must_use]
200    pub fn as_document(&self) -> Document<'_, &str> {
201        use crate::schema::translate::{
202            SchemaTranslator as _, graphql_parser::GraphQLParserTranslator,
203        };
204
205        GraphQLParserTranslator::translate_schema(&self.schema)
206    }
207}
208
209/// Metadata for a schema
210#[derive(Debug)]
211pub struct SchemaType<S> {
212    pub(crate) description: Option<ArcStr>,
213    pub(crate) types: FnvHashMap<Name, MetaType<S>>,
214    pub(crate) query_type_name: String,
215    pub(crate) mutation_type_name: Option<String>,
216    pub(crate) subscription_type_name: Option<String>,
217    directives: FnvHashMap<ArcStr, DirectiveType<S>>,
218}
219
220impl<S> Context for SchemaType<S> {}
221
222impl<S> SchemaType<S> {
223    /// Create a new schema.
224    pub fn new<QueryT, MutationT, SubscriptionT>(
225        query_info: &QueryT::TypeInfo,
226        mutation_info: &MutationT::TypeInfo,
227        subscription_info: &SubscriptionT::TypeInfo,
228    ) -> Self
229    where
230        S: ScalarValue,
231        QueryT: GraphQLType<S>,
232        MutationT: GraphQLType<S>,
233        SubscriptionT: GraphQLType<S>,
234    {
235        let mut directives = FnvHashMap::default();
236        let mut registry = Registry::new(FnvHashMap::default());
237
238        let query_type_name: Box<str> = registry
239            .get_type::<QueryT>(query_info)
240            .innermost_name()
241            .into();
242        let mutation_type_name: Box<str> = registry
243            .get_type::<MutationT>(mutation_info)
244            .innermost_name()
245            .into();
246        let subscription_type_name: Box<str> = registry
247            .get_type::<SubscriptionT>(subscription_info)
248            .innermost_name()
249            .into();
250
251        registry.get_type::<SchemaType<S>>(&());
252
253        let skip_directive = DirectiveType::new_skip(&mut registry);
254        let include_directive = DirectiveType::new_include(&mut registry);
255        let deprecated_directive = DirectiveType::new_deprecated(&mut registry);
256        let specified_by_directive = DirectiveType::new_specified_by(&mut registry);
257        directives.insert(skip_directive.name.clone(), skip_directive);
258        directives.insert(include_directive.name.clone(), include_directive);
259        directives.insert(deprecated_directive.name.clone(), deprecated_directive);
260        directives.insert(specified_by_directive.name.clone(), specified_by_directive);
261
262        let mut meta_fields = vec![
263            registry.field::<SchemaType<S>>(arcstr::literal!("__schema"), &()),
264            registry
265                .field::<TypeType<S>>(arcstr::literal!("__type"), &())
266                .argument(registry.arg::<String>(arcstr::literal!("name"), &())),
267        ];
268
269        if let Some(root_type) = registry.types.get_mut(query_type_name.as_ref()) {
270            if let MetaType::Object(ObjectMeta { ref mut fields, .. }) = *root_type {
271                fields.append(&mut meta_fields);
272            } else {
273                panic!("Root type is not an object");
274            }
275        } else {
276            panic!("Root type not found");
277        }
278
279        for meta_type in registry.types.values() {
280            if let MetaType::Placeholder(PlaceholderMeta { ref of_type }) = *meta_type {
281                panic!("Type {of_type:?} is still a placeholder type");
282            }
283        }
284        SchemaType {
285            description: None,
286            types: registry.types,
287            query_type_name: query_type_name.into(),
288            mutation_type_name: if mutation_type_name.as_ref() != "_EmptyMutation" {
289                Some(mutation_type_name.into())
290            } else {
291                None
292            },
293            subscription_type_name: if subscription_type_name.as_ref() != "_EmptySubscription" {
294                Some(subscription_type_name.into())
295            } else {
296                None
297            },
298            directives,
299        }
300    }
301
302    /// Add a description.
303    pub fn set_description(&mut self, description: impl Into<ArcStr>) {
304        self.description = Some(description.into());
305    }
306
307    /// Add a directive like `skip` or `include`.
308    pub fn add_directive(&mut self, directive: DirectiveType<S>) {
309        self.directives.insert(directive.name.clone(), directive);
310    }
311
312    /// Get a type by name.
313    pub fn type_by_name(&self, name: impl AsRef<str>) -> Option<TypeType<'_, S>> {
314        self.types.get(name.as_ref()).map(|t| TypeType::Concrete(t))
315    }
316
317    /// Get a concrete type by name.
318    pub fn concrete_type_by_name(&self, name: impl AsRef<str>) -> Option<&MetaType<S>> {
319        self.types.get(name.as_ref())
320    }
321
322    pub(crate) fn lookup_type(
323        &self,
324        ty: &Type<impl AsRef<str>, impl AsRef<[TypeModifier]>>,
325    ) -> Option<&MetaType<S>> {
326        if let Some(name) = ty.name() {
327            self.concrete_type_by_name(name)
328        } else {
329            self.lookup_type(&ty.borrow_inner())
330        }
331    }
332
333    /// Get the query type from the schema.
334    pub fn query_type(&self) -> TypeType<'_, S> {
335        TypeType::Concrete(
336            self.types
337                .get(self.query_type_name.as_str())
338                .expect("Query type does not exist in schema"),
339        )
340    }
341
342    /// Get the concrete query type from the schema.
343    pub fn concrete_query_type(&self) -> &MetaType<S> {
344        self.types
345            .get(self.query_type_name.as_str())
346            .expect("Query type does not exist in schema")
347    }
348
349    /// Get the mutation type from the schema.
350    pub fn mutation_type(&self) -> Option<TypeType<'_, S>> {
351        self.mutation_type_name.as_ref().map(|name| {
352            self.type_by_name(name)
353                .expect("Mutation type does not exist in schema")
354        })
355    }
356
357    /// Get the concrete mutation type from the schema.
358    pub fn concrete_mutation_type(&self) -> Option<&MetaType<S>> {
359        self.mutation_type_name.as_ref().map(|name| {
360            self.concrete_type_by_name(name)
361                .expect("Mutation type does not exist in schema")
362        })
363    }
364
365    /// Get the subscription type.
366    pub fn subscription_type(&self) -> Option<TypeType<'_, S>> {
367        self.subscription_type_name.as_ref().map(|name| {
368            self.type_by_name(name)
369                .expect("Subscription type does not exist in schema")
370        })
371    }
372
373    /// Get the concrete subscription type.
374    pub fn concrete_subscription_type(&self) -> Option<&MetaType<S>> {
375        self.subscription_type_name.as_ref().map(|name| {
376            self.concrete_type_by_name(name)
377                .expect("Subscription type does not exist in schema")
378        })
379    }
380
381    /// Get a list of types.
382    pub fn type_list(&self) -> Vec<TypeType<'_, S>> {
383        let mut types = self
384            .types
385            .values()
386            .map(|t| TypeType::Concrete(t))
387            .collect::<Vec<_>>();
388        sort_concrete_types(&mut types);
389        types
390    }
391
392    /// Get a list of concrete types.
393    pub fn concrete_type_list(&self) -> Vec<&MetaType<S>> {
394        self.types.values().collect()
395    }
396
397    /// Make a type.
398    pub fn make_type(
399        &self,
400        ty: &Type<impl AsRef<str>, impl AsRef<[TypeModifier]>>,
401    ) -> TypeType<'_, S> {
402        let mut out = self
403            .type_by_name(ty.innermost_name())
404            .expect("Type not found in schema");
405        for m in ty.modifiers() {
406            out = match m {
407                TypeModifier::NonNull => TypeType::NonNull(out.into()),
408                TypeModifier::List(expected_size) => TypeType::List(out.into(), *expected_size),
409            };
410        }
411        out
412    }
413
414    /// Get a list of directives.
415    pub fn directive_list(&self) -> Vec<&DirectiveType<S>> {
416        let mut directives = self.directives.values().collect::<Vec<_>>();
417        sort_directives(&mut directives);
418        directives
419    }
420
421    /// Get directive by name.
422    pub fn directive_by_name(&self, name: &str) -> Option<&DirectiveType<S>> {
423        self.directives.get(name)
424    }
425
426    /// Determine if there is an overlap between types.
427    pub fn type_overlap(&self, t1: &MetaType<S>, t2: &MetaType<S>) -> bool {
428        if std::ptr::eq(t1, t2) {
429            return true;
430        }
431
432        match (t1.is_abstract(), t2.is_abstract()) {
433            (true, true) => self
434                .possible_types(t1)
435                .iter()
436                .any(|t| self.is_possible_type(t2, t)),
437            (true, false) => self.is_possible_type(t1, t2),
438            (false, true) => self.is_possible_type(t2, t1),
439            (false, false) => false,
440        }
441    }
442
443    /// A list of possible typeees for a given type.
444    pub fn possible_types(&self, t: &MetaType<S>) -> Vec<&MetaType<S>> {
445        match *t {
446            MetaType::Union(UnionMeta {
447                ref of_type_names, ..
448            }) => of_type_names
449                .iter()
450                .flat_map(|t| self.concrete_type_by_name(t))
451                .collect(),
452            MetaType::Interface(InterfaceMeta { ref name, .. }) => self
453                .concrete_type_list()
454                .into_iter()
455                .filter(|t| match **t {
456                    MetaType::Object(ObjectMeta {
457                        ref interface_names,
458                        ..
459                    }) => interface_names.iter().any(|iname| iname == name),
460                    _ => false,
461                })
462                .collect(),
463            _ => panic!("Can't retrieve possible types from non-abstract meta type"),
464        }
465    }
466
467    /// If the abstract type is possible.
468    pub fn is_possible_type(
469        &self,
470        abstract_type: &MetaType<S>,
471        possible_type: &MetaType<S>,
472    ) -> bool {
473        self.possible_types(abstract_type)
474            .into_iter()
475            .any(|t| ptr::eq(t, possible_type))
476    }
477
478    /// If the type is a subtype of another type.
479    pub fn is_subtype(
480        &self,
481        sub_type: &Type<impl AsRef<str>, impl AsRef<[TypeModifier]>>,
482        super_type: &Type<impl AsRef<str>, impl AsRef<[TypeModifier]>>,
483    ) -> bool {
484        use TypeModifier::{List, NonNull};
485
486        if super_type == sub_type {
487            return true;
488        }
489
490        match (super_type.modifier(), sub_type.modifier()) {
491            (Some(NonNull), Some(NonNull)) => {
492                self.is_subtype(&sub_type.borrow_inner(), &super_type.borrow_inner())
493            }
494            (None | Some(List(..)), Some(NonNull)) => {
495                self.is_subtype(&sub_type.borrow_inner(), super_type)
496            }
497            (Some(List(..)), Some(List(..))) => {
498                self.is_subtype(&sub_type.borrow_inner(), &super_type.borrow_inner())
499            }
500            (None, None) => {
501                self.is_named_subtype(sub_type.innermost_name(), super_type.innermost_name())
502            }
503            _ => false,
504        }
505    }
506
507    /// If the type is a named subtype.
508    pub fn is_named_subtype(&self, sub_type_name: &str, super_type_name: &str) -> bool {
509        if sub_type_name == super_type_name {
510            true
511        } else if let (Some(sub_type), Some(super_type)) = (
512            self.concrete_type_by_name(sub_type_name),
513            self.concrete_type_by_name(super_type_name),
514        ) {
515            super_type.is_abstract() && self.is_possible_type(super_type, sub_type)
516        } else {
517            false
518        }
519    }
520}
521
522#[derive(Clone, Display)]
523pub enum TypeType<'a, S: 'a> {
524    #[display("{}", _0.name().unwrap())]
525    Concrete(&'a MetaType<S>),
526    #[display("{}!", **_0)]
527    NonNull(Box<TypeType<'a, S>>),
528    #[display("[{}]", **_0)]
529    List(Box<TypeType<'a, S>>, Option<usize>),
530}
531
532impl<'a, S> TypeType<'a, S> {
533    pub fn to_concrete(&self) -> Option<&'a MetaType<S>> {
534        match self {
535            Self::Concrete(t) => Some(t),
536            Self::List(..) | Self::NonNull(..) => None,
537        }
538    }
539
540    pub fn innermost_concrete(&self) -> &'a MetaType<S> {
541        match self {
542            Self::Concrete(t) => t,
543            Self::NonNull(n) | Self::List(n, ..) => n.innermost_concrete(),
544        }
545    }
546
547    pub fn list_contents(&self) -> Option<&Self> {
548        match self {
549            Self::List(n, ..) => Some(n),
550            Self::NonNull(n) => n.list_contents(),
551            Self::Concrete(..) => None,
552        }
553    }
554
555    pub fn is_non_null(&self) -> bool {
556        matches!(self, TypeType::NonNull(..))
557    }
558}
559
560#[derive(Debug)]
561pub struct DirectiveType<S> {
562    pub name: ArcStr,
563    pub description: Option<ArcStr>,
564    pub locations: Vec<DirectiveLocation>,
565    pub arguments: Vec<Argument<S>>,
566    pub is_repeatable: bool,
567}
568
569impl<S> DirectiveType<S> {
570    pub fn new(
571        name: impl Into<ArcStr>,
572        locations: &[DirectiveLocation],
573        arguments: &[Argument<S>],
574        is_repeatable: bool,
575    ) -> Self
576    where
577        S: Clone,
578    {
579        Self {
580            name: name.into(),
581            description: None,
582            locations: locations.to_vec(),
583            arguments: arguments.to_vec(),
584            is_repeatable,
585        }
586    }
587
588    fn new_skip(registry: &mut Registry<S>) -> Self
589    where
590        S: ScalarValue,
591    {
592        Self::new(
593            arcstr::literal!("skip"),
594            &[
595                DirectiveLocation::Field,
596                DirectiveLocation::FragmentSpread,
597                DirectiveLocation::InlineFragment,
598            ],
599            &[registry.arg::<bool>(arcstr::literal!("if"), &())],
600            false,
601        )
602    }
603
604    fn new_include(registry: &mut Registry<S>) -> Self
605    where
606        S: ScalarValue,
607    {
608        Self::new(
609            arcstr::literal!("include"),
610            &[
611                DirectiveLocation::Field,
612                DirectiveLocation::FragmentSpread,
613                DirectiveLocation::InlineFragment,
614            ],
615            &[registry.arg::<bool>(arcstr::literal!("if"), &())],
616            false,
617        )
618    }
619
620    fn new_deprecated(registry: &mut Registry<S>) -> Self
621    where
622        S: ScalarValue,
623    {
624        Self::new(
625            arcstr::literal!("deprecated"),
626            &[
627                DirectiveLocation::FieldDefinition,
628                DirectiveLocation::EnumValue,
629            ],
630            &[registry.arg::<String>(arcstr::literal!("reason"), &())],
631            false,
632        )
633    }
634
635    fn new_specified_by(registry: &mut Registry<S>) -> Self
636    where
637        S: ScalarValue,
638    {
639        Self::new(
640            arcstr::literal!("specifiedBy"),
641            &[DirectiveLocation::Scalar],
642            &[registry.arg::<String>(arcstr::literal!("url"), &())],
643            false,
644        )
645    }
646
647    pub fn description(mut self, description: impl Into<ArcStr>) -> Self {
648        self.description = Some(description.into());
649        self
650    }
651}
652
653#[derive(Clone, Debug, Display, Eq, GraphQLEnum, PartialEq)]
654#[graphql(name = "__DirectiveLocation", internal)]
655pub enum DirectiveLocation {
656    #[display("query")]
657    Query,
658    #[display("mutation")]
659    Mutation,
660    #[display("subscription")]
661    Subscription,
662    #[display("field")]
663    Field,
664    #[display("scalar")]
665    Scalar,
666    #[display("fragment definition")]
667    FragmentDefinition,
668    #[display("field definition")]
669    FieldDefinition,
670    #[display("variable definition")]
671    VariableDefinition,
672    #[display("fragment spread")]
673    FragmentSpread,
674    #[display("inline fragment")]
675    InlineFragment,
676    #[display("enum value")]
677    EnumValue,
678}
679
680/// Sorts the provided [`TypeType`]s in the "type-then-name" manner.
681fn sort_concrete_types<S>(types: &mut [TypeType<S>]) {
682    types.sort_by(|a, b| {
683        concrete_type_sort::by_type(a)
684            .cmp(&concrete_type_sort::by_type(b))
685            .then_with(|| concrete_type_sort::by_name(a).cmp(&concrete_type_sort::by_name(b)))
686    });
687}
688
689/// Sorts the provided [`DirectiveType`]s by name.
690fn sort_directives<S>(directives: &mut [&DirectiveType<S>]) {
691    directives.sort_by(|a, b| a.name.cmp(&b.name));
692}
693
694/// Evaluation of a [`TypeType`] weights for sorting (for concrete types only).
695///
696/// Used for deterministic introspection output.
697mod concrete_type_sort {
698    use crate::meta::MetaType;
699
700    use super::TypeType;
701
702    /// Returns a [`TypeType`] sorting weight by its type.
703    pub fn by_type<S>(t: &TypeType<S>) -> u8 {
704        match t {
705            TypeType::Concrete(MetaType::Enum(..)) => 0,
706            TypeType::Concrete(MetaType::InputObject(..)) => 1,
707            TypeType::Concrete(MetaType::Interface(..)) => 2,
708            TypeType::Concrete(MetaType::Scalar(..)) => 3,
709            TypeType::Concrete(MetaType::Object(..)) => 4,
710            TypeType::Concrete(MetaType::Union(..)) => 5,
711            // NOTE: The following types are not part of the introspected types.
712            TypeType::Concrete(
713                MetaType::List(..) | MetaType::Nullable(..) | MetaType::Placeholder(..),
714            ) => 6,
715            // NOTE: Other variants will not appear since we're only sorting concrete types.
716            TypeType::List(..) | TypeType::NonNull(..) => 7,
717        }
718    }
719
720    /// Returns a [`TypeType`] sorting weight by its name.
721    pub fn by_name<'a, S>(t: &'a TypeType<'a, S>) -> Option<&'a str> {
722        match t {
723            TypeType::Concrete(MetaType::Enum(meta)) => Some(&meta.name),
724            TypeType::Concrete(MetaType::InputObject(meta)) => Some(&meta.name),
725            TypeType::Concrete(MetaType::Interface(meta)) => Some(&meta.name),
726            TypeType::Concrete(MetaType::Scalar(meta)) => Some(&meta.name),
727            TypeType::Concrete(MetaType::Object(meta)) => Some(&meta.name),
728            TypeType::Concrete(MetaType::Union(meta)) => Some(&meta.name),
729            TypeType::Concrete(
730                // NOTE: The following types are not part of the introspected types.
731                MetaType::List(..) | MetaType::Nullable(..) | MetaType::Placeholder(..),
732            )
733            // NOTE: Other variants will not appear since we're only sorting concrete types.
734            | TypeType::List(..)
735            | TypeType::NonNull(..) => None,
736        }
737    }
738}
739
740#[cfg(test)]
741mod root_node_test {
742    #[cfg(feature = "schema-language")]
743    mod as_document {
744        use crate::{EmptyMutation, EmptySubscription, RootNode, graphql_object};
745
746        struct Query;
747
748        #[graphql_object]
749        impl Query {
750            fn blah() -> bool {
751                true
752            }
753        }
754
755        #[test]
756        fn generates_correct_document() {
757            let schema = RootNode::new(
758                Query,
759                EmptyMutation::<()>::new(),
760                EmptySubscription::<()>::new(),
761            );
762            let ast = graphql_parser::parse_schema::<&str>(
763                //language=GraphQL
764                r#"
765                type Query {
766                    blah: Boolean!
767                }
768
769                schema {
770                  query: Query
771                }
772                "#,
773            )
774            .unwrap();
775
776            assert_eq!(ast.to_string(), schema.as_document().to_string());
777        }
778    }
779
780    #[cfg(feature = "schema-language")]
781    mod as_sdl {
782        use crate::{
783            EmptyMutation, EmptySubscription, GraphQLEnum, GraphQLInputObject, GraphQLObject,
784            GraphQLUnion, RootNode, graphql_object,
785        };
786
787        #[derive(GraphQLObject, Default)]
788        struct Cake {
789            fresh: bool,
790        }
791
792        #[derive(GraphQLObject, Default)]
793        struct IceCream {
794            cold: bool,
795        }
796
797        #[derive(GraphQLUnion)]
798        enum GlutenFree {
799            Cake(Cake),
800            IceCream(IceCream),
801        }
802
803        #[derive(GraphQLEnum)]
804        enum Fruit {
805            Apple,
806            Orange,
807        }
808
809        #[derive(GraphQLInputObject)]
810        struct Coordinate {
811            latitude: f64,
812            longitude: f64,
813        }
814
815        struct Query;
816
817        #[graphql_object]
818        impl Query {
819            fn blah() -> bool {
820                true
821            }
822
823            /// This is whatever's description.
824            fn whatever() -> String {
825                "foo".into()
826            }
827
828            fn arr(stuff: Vec<Coordinate>) -> Option<&'static str> {
829                (!stuff.is_empty()).then_some("stuff")
830            }
831
832            fn fruit() -> Fruit {
833                Fruit::Apple
834            }
835
836            fn gluten_free(flavor: String) -> GlutenFree {
837                if flavor == "savory" {
838                    GlutenFree::Cake(Cake::default())
839                } else {
840                    GlutenFree::IceCream(IceCream::default())
841                }
842            }
843
844            #[deprecated]
845            fn old() -> i32 {
846                42
847            }
848
849            #[deprecated(note = "This field is deprecated, use another.")]
850            fn really_old() -> f64 {
851                42.0
852            }
853        }
854
855        #[test]
856        fn generates_correct_sdl() {
857            let actual = RootNode::new(
858                Query,
859                EmptyMutation::<()>::new(),
860                EmptySubscription::<()>::new(),
861            );
862            let expected = graphql_parser::parse_schema::<&str>(
863                //language=GraphQL
864                r#"
865                schema {
866                  query: Query
867                }
868                enum Fruit {
869                    APPLE
870                    ORANGE
871                }
872                input Coordinate {
873                    latitude: Float!
874                    longitude: Float!
875                }
876                type Cake {
877                    fresh: Boolean!
878                }
879                type IceCream {
880                    cold: Boolean!
881                }
882                type Query {
883                  blah: Boolean!
884                  "This is whatever's description."
885                  whatever: String!
886                  arr(stuff: [Coordinate!]!): String
887                  fruit: Fruit!
888                  glutenFree(flavor: String!): GlutenFree!
889                  old: Int! @deprecated
890                  reallyOld: Float! @deprecated(reason: "This field is deprecated, use another.")
891                }
892                union GlutenFree = Cake | IceCream
893                "#,
894            )
895            .unwrap();
896
897            assert_eq!(actual.as_sdl(), expected.to_string());
898        }
899    }
900}