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#[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 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 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 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 pub fn disable_introspection(mut self) -> Self {
156 self.introspection_disabled = true;
157 self
158 }
159
160 pub fn enable_introspection(mut self) -> Self {
166 self.introspection_disabled = false;
167 self
168 }
169
170 #[cfg(feature = "schema-language")]
171 #[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 #[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#[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 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 pub fn set_description(&mut self, description: impl Into<ArcStr>) {
304 self.description = Some(description.into());
305 }
306
307 pub fn add_directive(&mut self, directive: DirectiveType<S>) {
309 self.directives.insert(directive.name.clone(), directive);
310 }
311
312 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 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 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 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 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 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 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 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 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 pub fn concrete_type_list(&self) -> Vec<&MetaType<S>> {
394 self.types.values().collect()
395 }
396
397 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 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 pub fn directive_by_name(&self, name: &str) -> Option<&DirectiveType<S>> {
423 self.directives.get(name)
424 }
425
426 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 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 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 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 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
680fn 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
689fn sort_directives<S>(directives: &mut [&DirectiveType<S>]) {
691 directives.sort_by(|a, b| a.name.cmp(&b.name));
692}
693
694mod concrete_type_sort {
698 use crate::meta::MetaType;
699
700 use super::TypeType;
701
702 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 TypeType::Concrete(
713 MetaType::List(..) | MetaType::Nullable(..) | MetaType::Placeholder(..),
714 ) => 6,
715 TypeType::List(..) | TypeType::NonNull(..) => 7,
717 }
718 }
719
720 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 MetaType::List(..) | MetaType::Nullable(..) | MetaType::Placeholder(..),
732 )
733 | 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 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 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 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}