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#[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#[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 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 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 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 pub fn disable_introspection(mut self) -> Self {
206 self.introspection_disabled = true;
207 self
208 }
209
210 pub fn enable_introspection(mut self) -> Self {
216 self.introspection_disabled = false;
217 self
218 }
219
220 #[cfg(feature = "schema-language")]
221 #[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 #[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 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 pub fn set_description(&mut self, description: impl Into<Cow<'a, str>>) {
343 self.description = Some(description.into());
344 }
345
346 pub fn add_directive(&mut self, directive: DirectiveType<'a, S>) {
348 self.directives.insert(directive.name.clone(), directive);
349 }
350
351 pub fn type_by_name(&self, name: &str) -> Option<TypeType<S>> {
353 self.types.get(name).map(|t| TypeType::Concrete(t))
354 }
355
356 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 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 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 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 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 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 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 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 pub fn concrete_type_list(&self) -> Vec<&MetaType<S>> {
431 self.types.values().collect()
432 }
433
434 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 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 pub fn directive_by_name(&self, name: &str) -> Option<&DirectiveType<S>> {
459 self.directives.get(name)
460 }
461
462 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 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 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 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 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
696fn 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
705fn sort_directives<S>(directives: &mut [&DirectiveType<S>]) {
707 directives.sort_by(|a, b| a.name.cmp(&b.name));
708}
709
710mod concrete_type_sort {
714 use crate::meta::MetaType;
715
716 use super::TypeType;
717
718 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 TypeType::Concrete(
729 MetaType::List(_) | MetaType::Nullable(_) | MetaType::Placeholder(_),
730 ) => 6,
731 TypeType::List(..) | TypeType::NonNull(_) => 7,
733 }
734 }
735
736 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 MetaType::List(_) | MetaType::Nullable(_) | MetaType::Placeholder(_),
748 )
749 | 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 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 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 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}