juniper/schema/
model.rs

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