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    /// The input type that encloses the current one: the input object type while visiting one
148    /// of its fields' values.
149    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            // DOTAN: We should visit the directives as well here, but it's extracted in graphql_parser.
353
354            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
491// Trait
492pub 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}