1use std::collections::HashMap;
2
3use crate::parser::query::TypeCondition;
4
5use crate::static_graphql::{
6 query::{self, *},
7 schema::{self},
8};
9
10pub struct OperationVisitorContext<'a> {
12 pub schema: &'a schema::Document,
13 pub operation: &'a query::Document,
14 pub known_fragments: HashMap<&'a str, &'a FragmentDefinition>,
15 pub directives: HashMap<String, schema::DirectiveDefinition>,
16
17 type_stack: Vec<Option<&'a schema::TypeDefinition>>,
18 parent_type_stack: Vec<Option<&'a schema::TypeDefinition>>,
19 input_type_stack: Vec<Option<&'a schema::TypeDefinition>>,
20 type_literal_stack: Vec<Option<Type>>,
21 input_type_literal_stack: Vec<Option<&'a Type>>,
22 input_type_default_stack: Vec<bool>,
23 field_stack: Vec<Option<&'a schema::Field>>,
24}
25
26impl<'a> OperationVisitorContext<'a> {
27 pub fn new(operation: &'a Document, schema: &'a schema::Document) -> Self {
28 OperationVisitorContext {
29 schema,
30 operation,
31 type_stack: vec![],
32 parent_type_stack: vec![],
33 input_type_stack: vec![],
34 type_literal_stack: vec![],
35 input_type_literal_stack: vec![],
36 input_type_default_stack: vec![],
37 field_stack: vec![],
38 known_fragments: HashMap::from_iter(operation.definitions.iter().filter_map(|def| {
39 match def {
40 Definition::Fragment(fragment) => Some((fragment.name.as_str(), fragment)),
41 _ => None,
42 }
43 })),
44 directives: HashMap::<String, schema::DirectiveDefinition>::from_iter(
45 schema.definitions.iter().filter_map(|def| match def {
46 schema::Definition::DirectiveDefinition(directive_def) => {
47 Some((directive_def.name.clone(), directive_def.clone()))
48 }
49 _ => None,
50 }),
51 ),
52 }
53 }
54
55 pub fn with_type<Func>(&mut self, t: Option<&Type>, func: Func)
56 where
57 Func: FnOnce(&mut OperationVisitorContext<'a>),
58 {
59 if let Some(t) = t {
60 self.type_stack
61 .push(self.schema.type_by_name(t.inner_type()));
62 } else {
63 self.type_stack.push(None);
64 }
65
66 self.type_literal_stack.push(t.cloned());
67 func(self);
68 self.type_literal_stack.pop();
69 self.type_stack.pop();
70 }
71
72 pub fn with_parent_type<Func>(&mut self, func: Func)
73 where
74 Func: FnOnce(&mut OperationVisitorContext<'a>),
75 {
76 self.parent_type_stack
77 .push(*self.type_stack.last().unwrap_or(&None));
78 func(self);
79 self.parent_type_stack.pop();
80 }
81
82 pub fn with_field<'f, Func>(&mut self, f: Option<&'f schema::Field>, func: Func)
83 where
84 Func: FnOnce(&mut OperationVisitorContext<'a>),
85 'f: 'a,
86 {
87 if let Some(f) = f {
88 self.field_stack.push(Some(f));
89 } else {
90 self.field_stack.push(None);
91 }
92
93 func(self);
94 self.field_stack.pop();
95 }
96
97 pub fn with_input_type<Func>(&mut self, t: Option<&'a Type>, func: Func)
98 where
99 Func: FnOnce(&mut OperationVisitorContext<'a>),
100 {
101 self.with_input_type_and_default(t, false, func);
102 }
103
104 pub fn with_input_type_and_default<Func>(
105 &mut self,
106 t: Option<&'a Type>,
107 has_default: bool,
108 func: Func,
109 ) where
110 Func: FnOnce(&mut OperationVisitorContext<'a>),
111 {
112 if let Some(t) = t {
113 self.input_type_stack
114 .push(self.schema.type_by_name(t.inner_type()));
115 } else {
116 self.input_type_stack.push(None);
117 }
118
119 self.input_type_literal_stack.push(t);
120 self.input_type_default_stack.push(has_default);
121 func(self);
122 self.input_type_default_stack.pop();
123 self.input_type_literal_stack.pop();
124 self.input_type_stack.pop();
125 }
126
127 pub fn current_input_type_has_default(&self) -> bool {
128 *self.input_type_default_stack.last().unwrap_or(&false)
129 }
130
131 pub fn current_type(&self) -> Option<&schema::TypeDefinition> {
132 self.type_stack.last().unwrap_or(&None).as_deref()
133 }
134
135 pub fn current_input_type(&self) -> Option<&schema::TypeDefinition> {
136 self.input_type_stack.last().unwrap_or(&None).as_deref()
137 }
138
139 pub fn current_parent_type(&self) -> Option<&'a schema::TypeDefinition> {
140 *self.parent_type_stack.last().unwrap_or(&None)
141 }
142
143 pub fn current_type_literal(&self) -> Option<&Type> {
144 self.type_literal_stack.last().unwrap_or(&None).as_ref()
145 }
146
147 pub fn parent_input_type(&self) -> Option<&'a schema::TypeDefinition> {
150 self.input_type_stack
151 .len()
152 .checked_sub(2)
153 .and_then(|index| self.input_type_stack[index])
154 }
155
156 pub fn current_input_type_literal(&self) -> Option<&'a Type> {
157 *self.input_type_literal_stack.last().unwrap_or(&None)
158 }
159
160 pub fn current_field(&self) -> Option<&schema::Field> {
161 self.field_stack.last().unwrap_or(&None).as_deref()
162 }
163}
164
165pub fn visit_document<'a, Visitor, UserContext>(
166 visitor: &mut Visitor,
167 document: &'a Document,
168 context: &mut OperationVisitorContext<'a>,
169 user_context: &mut UserContext,
170) where
171 Visitor: OperationVisitor<'a, UserContext>,
172{
173 visitor.enter_document(context, user_context, document);
174 visit_definitions(visitor, &document.definitions, context, user_context);
175 visitor.leave_document(context, user_context, document);
176}
177
178fn visit_definitions<'a, Visitor, UserContext>(
179 visitor: &mut Visitor,
180 definitions: &'a Vec<Definition>,
181 context: &mut OperationVisitorContext<'a>,
182 user_context: &mut UserContext,
183) where
184 Visitor: OperationVisitor<'a, UserContext>,
185{
186 for definition in definitions {
187 let schema_type_name = match definition {
188 Definition::Fragment(fragment) => {
189 let TypeCondition::On(name) = &fragment.type_condition;
190 Some(name)
191 }
192 Definition::Operation(operation) => match operation {
193 OperationDefinition::Query(_) => Some(&context.schema.query_type().name),
194 OperationDefinition::SelectionSet(_) => Some(&context.schema.query_type().name),
195 OperationDefinition::Mutation(_) => context.schema.mutation_type().map(|t| &t.name),
196 OperationDefinition::Subscription(_) => {
197 context.schema.subscription_type().map(|t| &t.name)
198 }
199 },
200 };
201
202 let schema_type = schema_type_name.map(|v| Type::NamedType(v.clone()));
203 context.with_type(schema_type.as_ref(), |context| match definition {
204 Definition::Fragment(fragment) => {
205 visit_fragment_definition(visitor, fragment, context, user_context)
206 }
207 Definition::Operation(operation) => {
208 visit_operation_definition(visitor, operation, context, user_context)
209 }
210 });
211 }
212}
213
214fn visit_directives<'a, Visitor, UserContext>(
215 visitor: &mut Visitor,
216 directives: &'a [Directive],
217 context: &mut OperationVisitorContext<'a>,
218 user_context: &mut UserContext,
219) where
220 Visitor: OperationVisitor<'a, UserContext>,
221{
222 for directive in directives {
223 let directive_def_args = context
224 .schema
225 .directive_by_name(&directive.name)
226 .map(|def| &def.arguments);
227
228 visitor.enter_directive(context, user_context, directive);
229 visit_arguments(
230 visitor,
231 directive_def_args,
232 &directive.arguments,
233 context,
234 user_context,
235 );
236 visitor.leave_directive(context, user_context, directive);
237 }
238}
239
240fn visit_arguments<'a, Visitor, UserContext>(
241 visitor: &mut Visitor,
242 arguments_definition: Option<&'a Vec<schema::InputValue>>,
243 arguments: &'a Vec<(String, Value)>,
244 context: &mut OperationVisitorContext<'a>,
245 user_context: &mut UserContext,
246) where
247 Visitor: OperationVisitor<'a, UserContext>,
248{
249 for argument in arguments {
250 let arg_def = arguments_definition
251 .and_then(|argument_defs| argument_defs.iter().find(|a| a.name.eq(&argument.0)));
252
253 let arg_type = arg_def.map(|a| &a.value_type);
254 let has_default = arg_def.and_then(|a| a.default_value.as_ref()).is_some();
255
256 context.with_input_type_and_default(arg_type, has_default, |context| {
257 visitor.enter_argument(context, user_context, argument);
258 visit_input_value(visitor, &argument.1, context, user_context);
259 visitor.leave_argument(context, user_context, argument);
260 })
261 }
262}
263
264fn visit_input_value<'a, Visitor, UserContext>(
265 visitor: &mut Visitor,
266 input_value: &'a Value,
267 context: &mut OperationVisitorContext<'a>,
268 user_context: &mut UserContext,
269) where
270 Visitor: OperationVisitor<'a, UserContext>,
271{
272 match input_value {
273 Value::Boolean(_) | Value::Float(_) | Value::Int(_) | Value::String(_) => {
274 visitor.enter_scalar_value(context, user_context, input_value);
275 visitor.leave_scalar_value(context, user_context, input_value);
276 }
277 Value::Null => {
278 visitor.enter_null_value(context, user_context, ());
279 visitor.leave_null_value(context, user_context, ());
280 }
281 Value::Enum(v) => {
282 visitor.enter_enum_value(context, user_context, v);
283 visitor.leave_enum_value(context, user_context, v);
284 }
285 Value::List(v) => {
286 visitor.enter_list_value(context, user_context, v);
287
288 let input_type = context.current_input_type_literal().and_then(|t| {
289 let nullable_type = match t {
290 Type::NonNullType(inner_type) => inner_type.as_ref(),
291 t => t,
292 };
293 match nullable_type {
294 Type::ListType(inner_type) => Some(inner_type.as_ref()),
295 _ => None,
296 }
297 });
298
299 context.with_input_type(input_type, |context| {
300 for item in v {
301 visit_input_value(visitor, item, context, user_context)
302 }
303 });
304
305 visitor.leave_list_value(context, user_context, v);
306 }
307 Value::Object(v) => {
308 visitor.enter_object_value(context, user_context, v);
309
310 for pair in v {
311 let input_field = context
312 .current_input_type_literal()
313 .and_then(|v| context.schema.type_by_name(v.inner_type()))
314 .and_then(|v| v.input_field_by_name(&pair.0));
315 let input_type = input_field.map(|field| &field.value_type);
316 let has_default = input_field
317 .and_then(|field| field.default_value.as_ref())
318 .is_some();
319
320 context.with_input_type_and_default(input_type, has_default, |context| {
321 visitor.enter_object_field(context, user_context, pair);
322 visit_input_value(visitor, &pair.1, context, user_context);
323 visitor.leave_object_field(context, user_context, pair);
324 });
325 }
326
327 visitor.leave_object_value(context, user_context, v);
328 }
329 Value::Variable(v) => {
330 visitor.enter_variable_value(context, user_context, v);
331 visitor.leave_variable_value(context, user_context, v);
332 }
333 }
334}
335
336fn visit_variable_definitions<'a, Visitor, UserContext>(
337 visitor: &mut Visitor,
338 variables: &'a [VariableDefinition],
339 context: &mut OperationVisitorContext<'a>,
340 user_context: &mut UserContext,
341) where
342 Visitor: OperationVisitor<'a, UserContext>,
343{
344 for variable in variables {
345 context.with_input_type(Some(&variable.var_type), |context| {
346 visitor.enter_variable_definition(context, user_context, variable);
347
348 if let Some(default_value) = &variable.default_value {
349 visit_input_value(visitor, default_value, context, user_context);
350 }
351
352 visitor.leave_variable_definition(context, user_context, variable);
355 })
356 }
357}
358
359fn visit_selection<'a, Visitor, UserContext>(
360 visitor: &mut Visitor,
361 selection: &'a Selection,
362 context: &mut OperationVisitorContext<'a>,
363 user_context: &mut UserContext,
364) where
365 Visitor: OperationVisitor<'a, UserContext>,
366{
367 match selection {
368 Selection::Field(field) => {
369 let parent_type_def = context
370 .current_parent_type()
371 .and_then(|t| t.field_by_name(&field.name));
372
373 let field_type = parent_type_def.map(|f| &f.field_type);
374 let field_args = parent_type_def.map(|f| &f.arguments);
375
376 context.with_type(field_type, |context| {
377 visitor.enter_field(context, user_context, field);
378 context.with_field(
379 context
380 .current_parent_type()
381 .and_then(|t| t.field_by_name(&field.name)),
382 |context| {
383 visit_arguments(
384 visitor,
385 field_args,
386 &field.arguments,
387 context,
388 user_context,
389 );
390 visit_directives(visitor, &field.directives, context, user_context);
391 visit_selection_set(visitor, &field.selection_set, context, user_context);
392 },
393 );
394 visitor.leave_field(context, user_context, field);
395 });
396 }
397 Selection::FragmentSpread(fragment_spread) => {
398 visitor.enter_fragment_spread(context, user_context, fragment_spread);
399 visit_directives(visitor, &fragment_spread.directives, context, user_context);
400 visitor.leave_fragment_spread(context, user_context, fragment_spread);
401 }
402 Selection::InlineFragment(inline_fragment) => {
403 if let Some(TypeCondition::On(fragment_condition)) = &inline_fragment.type_condition {
404 context.with_type(
405 Some(&Type::NamedType(fragment_condition.clone())),
406 |context| {
407 visitor.enter_inline_fragment(context, user_context, inline_fragment);
408 visit_directives(
409 visitor,
410 &inline_fragment.directives,
411 context,
412 user_context,
413 );
414 visit_selection_set(
415 visitor,
416 &inline_fragment.selection_set,
417 context,
418 user_context,
419 );
420 visitor.leave_inline_fragment(context, user_context, inline_fragment);
421 },
422 );
423 } else {
424 visitor.enter_inline_fragment(context, user_context, inline_fragment);
425 visit_directives(visitor, &inline_fragment.directives, context, user_context);
426 visit_selection_set(
427 visitor,
428 &inline_fragment.selection_set,
429 context,
430 user_context,
431 );
432 visitor.leave_inline_fragment(context, user_context, inline_fragment);
433 }
434 }
435 }
436}
437
438fn visit_selection_set<'a, Visitor, UserContext>(
439 visitor: &mut Visitor,
440 selection_set: &'a SelectionSet,
441 context: &mut OperationVisitorContext<'a>,
442 user_context: &mut UserContext,
443) where
444 Visitor: OperationVisitor<'a, UserContext>,
445{
446 context.with_parent_type(|context| {
447 visitor.enter_selection_set(context, user_context, selection_set);
448
449 for selection in &selection_set.items {
450 visit_selection(visitor, selection, context, user_context);
451 }
452
453 visitor.leave_selection_set(context, user_context, selection_set);
454 });
455}
456
457fn visit_fragment_definition<'a, Visitor, UserContext>(
458 visitor: &mut Visitor,
459 fragment: &'a FragmentDefinition,
460 context: &mut OperationVisitorContext<'a>,
461 user_context: &mut UserContext,
462) where
463 Visitor: OperationVisitor<'a, UserContext>,
464{
465 visitor.enter_fragment_definition(context, user_context, fragment);
466 visit_directives(visitor, &fragment.directives, context, user_context);
467 visit_selection_set(visitor, &fragment.selection_set, context, user_context);
468 visitor.leave_fragment_definition(context, user_context, fragment);
469}
470
471fn visit_operation_definition<'a, Visitor, UserContext>(
472 visitor: &mut Visitor,
473 operation: &'a OperationDefinition,
474 context: &mut OperationVisitorContext<'a>,
475 user_context: &mut UserContext,
476) where
477 Visitor: OperationVisitor<'a, UserContext>,
478{
479 visitor.enter_operation_definition(context, user_context, operation);
480 visit_directives(visitor, operation.directives(), context, user_context);
481 visit_variable_definitions(
482 visitor,
483 operation.variable_definitions(),
484 context,
485 user_context,
486 );
487 visit_selection_set(visitor, operation.selection_set(), context, user_context);
488 visitor.leave_operation_definition(context, user_context, operation);
489}
490
491pub trait OperationVisitor<'a, UserContext = ()> {
493 fn enter_document(
494 &mut self,
495 _: &mut OperationVisitorContext<'a>,
496 _: &mut UserContext,
497 _: &'a Document,
498 ) {
499 }
500 fn leave_document(
501 &mut self,
502 _: &mut OperationVisitorContext<'a>,
503 _: &mut UserContext,
504 _: &Document,
505 ) {
506 }
507
508 fn enter_operation_definition(
509 &mut self,
510 _: &mut OperationVisitorContext<'a>,
511 _: &mut UserContext,
512 _: &'a OperationDefinition,
513 ) {
514 }
515 fn leave_operation_definition(
516 &mut self,
517 _: &mut OperationVisitorContext<'a>,
518 _: &mut UserContext,
519 _: &OperationDefinition,
520 ) {
521 }
522
523 fn enter_fragment_definition(
524 &mut self,
525 _: &mut OperationVisitorContext<'a>,
526 _: &mut UserContext,
527 _: &'a FragmentDefinition,
528 ) {
529 }
530 fn leave_fragment_definition(
531 &mut self,
532 _: &mut OperationVisitorContext<'a>,
533 _: &mut UserContext,
534 _: &FragmentDefinition,
535 ) {
536 }
537
538 fn enter_variable_definition(
539 &mut self,
540 _: &mut OperationVisitorContext<'a>,
541 _: &mut UserContext,
542 _: &'a VariableDefinition,
543 ) {
544 }
545 fn leave_variable_definition(
546 &mut self,
547 _: &mut OperationVisitorContext<'a>,
548 _: &mut UserContext,
549 _: &VariableDefinition,
550 ) {
551 }
552
553 fn enter_directive(
554 &mut self,
555 _: &mut OperationVisitorContext<'a>,
556 _: &mut UserContext,
557 _: &Directive,
558 ) {
559 }
560 fn leave_directive(
561 &mut self,
562 _: &mut OperationVisitorContext<'a>,
563 _: &mut UserContext,
564 _: &Directive,
565 ) {
566 }
567
568 fn enter_argument(
569 &mut self,
570 _: &mut OperationVisitorContext<'a>,
571 _: &mut UserContext,
572 _: &'a (String, Value),
573 ) {
574 }
575 fn leave_argument(
576 &mut self,
577 _: &mut OperationVisitorContext<'a>,
578 _: &mut UserContext,
579 _: &(String, Value),
580 ) {
581 }
582
583 fn enter_selection_set(
584 &mut self,
585 _: &mut OperationVisitorContext<'a>,
586 _: &mut UserContext,
587 _: &'a SelectionSet,
588 ) {
589 }
590 fn leave_selection_set(
591 &mut self,
592 _: &mut OperationVisitorContext<'a>,
593 _: &mut UserContext,
594 _: &SelectionSet,
595 ) {
596 }
597
598 fn enter_field(&mut self, _: &mut OperationVisitorContext<'a>, _: &mut UserContext, _: &Field) {
599 }
600 fn leave_field(&mut self, _: &mut OperationVisitorContext<'a>, _: &mut UserContext, _: &Field) {
601 }
602
603 fn enter_fragment_spread(
604 &mut self,
605 _: &mut OperationVisitorContext<'a>,
606 _: &mut UserContext,
607 _: &'a FragmentSpread,
608 ) {
609 }
610 fn leave_fragment_spread(
611 &mut self,
612 _: &mut OperationVisitorContext<'a>,
613 _: &mut UserContext,
614 _: &FragmentSpread,
615 ) {
616 }
617
618 fn enter_inline_fragment(
619 &mut self,
620 _: &mut OperationVisitorContext<'a>,
621 _: &mut UserContext,
622 _: &InlineFragment,
623 ) {
624 }
625 fn leave_inline_fragment(
626 &mut self,
627 _: &mut OperationVisitorContext<'a>,
628 _: &mut UserContext,
629 _: &InlineFragment,
630 ) {
631 }
632
633 fn enter_null_value(
634 &mut self,
635 _: &mut OperationVisitorContext<'a>,
636 _: &mut UserContext,
637 _: (),
638 ) {
639 }
640 fn leave_null_value(
641 &mut self,
642 _: &mut OperationVisitorContext<'a>,
643 _: &mut UserContext,
644 _: (),
645 ) {
646 }
647
648 fn enter_scalar_value(
649 &mut self,
650 _: &mut OperationVisitorContext<'a>,
651 _: &mut UserContext,
652 _: &Value,
653 ) {
654 }
655 fn leave_scalar_value(
656 &mut self,
657 _: &mut OperationVisitorContext<'a>,
658 _: &mut UserContext,
659 _: &Value,
660 ) {
661 }
662
663 fn enter_enum_value(
664 &mut self,
665 _: &mut OperationVisitorContext<'a>,
666 _: &mut UserContext,
667 _: &String,
668 ) {
669 }
670 fn leave_enum_value(
671 &mut self,
672 _: &mut OperationVisitorContext<'a>,
673 _: &mut UserContext,
674 _: &String,
675 ) {
676 }
677
678 fn enter_variable_value(
679 &mut self,
680 _: &mut OperationVisitorContext<'a>,
681 _: &mut UserContext,
682 _: &'a str,
683 ) {
684 }
685 fn leave_variable_value(
686 &mut self,
687 _: &mut OperationVisitorContext<'a>,
688 _: &mut UserContext,
689 _: &String,
690 ) {
691 }
692
693 fn enter_list_value(
694 &mut self,
695 _: &mut OperationVisitorContext<'a>,
696 _: &mut UserContext,
697 _: &Vec<Value>,
698 ) {
699 }
700 fn leave_list_value(
701 &mut self,
702 _: &mut OperationVisitorContext<'a>,
703 _: &mut UserContext,
704 _: &Vec<Value>,
705 ) {
706 }
707
708 fn enter_object_value(
709 &mut self,
710 _: &mut OperationVisitorContext<'a>,
711 _: &mut UserContext,
712 _: &[(String, Value)],
713 ) {
714 }
715 fn leave_object_value(
716 &mut self,
717 _: &mut OperationVisitorContext<'a>,
718 _: &mut UserContext,
719 _: &[(String, Value)],
720 ) {
721 }
722
723 fn enter_object_field(
724 &mut self,
725 _: &mut OperationVisitorContext<'a>,
726 _: &mut UserContext,
727 _: &(String, Value),
728 ) {
729 }
730 fn leave_object_field(
731 &mut self,
732 _: &mut OperationVisitorContext<'a>,
733 _: &mut UserContext,
734 _: &(String, Value),
735 ) {
736 }
737}
738
739#[cfg(test)]
740mod tests {
741 use super::*;
742
743 struct RecordingVisitor {
744 seen_types: Vec<Option<String>>,
745 }
746
747 impl<'a> OperationVisitor<'a, ()> for RecordingVisitor {
748 fn enter_operation_definition(
749 &mut self,
750 context: &mut OperationVisitorContext<'a>,
751 _user_context: &mut (),
752 _operation: &'a OperationDefinition,
753 ) {
754 self.seen_types.push(
755 context
756 .current_type()
757 .map(|type_def| type_def.name().to_string()),
758 );
759 }
760 }
761
762 fn assert_seen_type(schema_sdl: &str, operation: &str, expected: &str) {
763 let schema = crate::parser::parse_schema::<String>(schema_sdl)
764 .expect("valid schema")
765 .into_static();
766 let operation_doc = crate::parser::parse_query::<String>(operation)
767 .expect("valid operation")
768 .into_static();
769
770 let mut context = OperationVisitorContext::new(&operation_doc, &schema);
771 let mut visitor = RecordingVisitor {
772 seen_types: Vec::new(),
773 };
774
775 visit_document(&mut visitor, &operation_doc, &mut context, &mut ());
776
777 assert_eq!(visitor.seen_types.len(), 1);
778 assert_eq!(visitor.seen_types[0], Some(expected.to_string()));
779 }
780
781 #[test]
782 fn default_root_type_names() {
783 let schema = "
784 type Query { hello: String }
785 type Mutation { doThing: String }
786 type Subscription { onThing: String }
787 ";
788
789 assert_seen_type(schema, "mutation { doThing }", "Mutation");
790 assert_seen_type(schema, "subscription { onThing }", "Subscription");
791 }
792
793 #[test]
794 fn renamed_root_types() {
795 let schema = "
796 schema {
797 query: RootQuery
798 mutation: RootMutation
799 subscription: RootSubscription
800 }
801
802 type RootQuery { hello: String }
803 type RootMutation { doThing: String }
804 type RootSubscription { onThing: String }
805 ";
806
807 assert_seen_type(schema, "mutation { doThing }", "RootMutation");
808 assert_seen_type(schema, "subscription { onThing }", "RootSubscription");
809 }
810}