Skip to main content

graphql_tools/ast/
operation_visitor.rs

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