graphql_tools/ast/
schema_visitor.rs

1use crate::static_graphql::schema::{
2    Definition, DirectiveDefinition, Document, EnumType, EnumValue, Field, InputObjectType,
3    InputValue, InterfaceType, ObjectType, ScalarType, SchemaDefinition, TypeDefinition, UnionType,
4};
5
6/// A trait for implenenting a visitor for GraphQL schema definition.
7pub trait SchemaVisitor<T = ()> {
8    fn visit_schema_document(&self, document: &Document, _visitor_context: &mut T) {
9        self.enter_document(document, _visitor_context);
10
11        for definition in &document.definitions {
12            match definition {
13                Definition::SchemaDefinition(schema_definition) => {
14                    self.enter_schema_definition(schema_definition, _visitor_context);
15                    self.leave_schema_definition(schema_definition, _visitor_context);
16                }
17                Definition::TypeDefinition(type_definition) => {
18                    self.enter_type_definition(type_definition, _visitor_context);
19
20                    match type_definition {
21                        TypeDefinition::Object(object) => {
22                            self.enter_object_type(object, _visitor_context);
23
24                            for field in &object.fields {
25                                self.enter_object_type_field(field, object, _visitor_context);
26                                // TODO: More advanced setup for fields: arguments, lists, null/non-null, directives
27                                self.leave_object_type_field(field, object, _visitor_context);
28                            }
29
30                            self.leave_object_type(object, _visitor_context);
31                        }
32                        TypeDefinition::Scalar(scalar) => {
33                            self.enter_scalar_type(scalar, _visitor_context);
34                            self.leave_scalar_type(scalar, _visitor_context);
35                        }
36                        TypeDefinition::Enum(enum_) => {
37                            self.enter_enum_type(enum_, _visitor_context);
38
39                            for value in &enum_.values {
40                                self.enter_enum_value(value, enum_, _visitor_context);
41                                self.leave_enum_value(value, enum_, _visitor_context);
42                            }
43
44                            self.leave_enum_type(enum_, _visitor_context);
45                        }
46                        TypeDefinition::Union(union) => {
47                            self.enter_union_type(union, _visitor_context);
48                            self.leave_union_type(union, _visitor_context);
49                        }
50                        TypeDefinition::InputObject(input_object) => {
51                            self.enter_input_object_type(input_object, _visitor_context);
52
53                            for field in &input_object.fields {
54                                self.enter_input_object_type_field(
55                                    field,
56                                    input_object,
57                                    _visitor_context,
58                                );
59                                self.leave_input_object_type_field(
60                                    field,
61                                    input_object,
62                                    _visitor_context,
63                                );
64                            }
65
66                            self.leave_input_object_type(input_object, _visitor_context);
67                        }
68                        TypeDefinition::Interface(interface) => {
69                            self.enter_interface_type(interface, _visitor_context);
70
71                            for field in &interface.fields {
72                                self.enter_interface_type_field(field, interface, _visitor_context);
73                                self.leave_interface_type_field(field, interface, _visitor_context);
74                            }
75
76                            self.leave_interface_type(interface, _visitor_context);
77                        }
78                    }
79
80                    self.leave_type_definition(type_definition, _visitor_context);
81                }
82                Definition::DirectiveDefinition(directive_definition) => {
83                    self.enter_directive_definition(directive_definition, _visitor_context);
84                    self.leave_directive_definition(directive_definition, _visitor_context);
85                }
86                Definition::TypeExtension(_type_extension) => {
87                    // TODO: implement this
88                    panic!("TypeExtension not supported at the moment");
89                }
90            }
91        }
92
93        self.leave_document(document, _visitor_context);
94    }
95
96    fn enter_document(&self, _node: &Document, _visitor_context: &mut T) {}
97    fn leave_document(&self, _node: &Document, _visitor_context: &mut T) {}
98
99    fn enter_schema_definition(&self, _node: &SchemaDefinition, _visitor_context: &mut T) {}
100    fn leave_schema_definition(&self, _node: &SchemaDefinition, _visitor_context: &mut T) {}
101
102    fn enter_directive_definition(&self, _node: &DirectiveDefinition, _visitor_context: &mut T) {}
103    fn leave_directive_definition(&self, _node: &DirectiveDefinition, _visitor_context: &mut T) {}
104
105    fn enter_type_definition(&self, _node: &TypeDefinition, _visitor_context: &mut T) {}
106    fn leave_type_definition(&self, _node: &TypeDefinition, _visitor_context: &mut T) {}
107
108    fn enter_interface_type(&self, _node: &InterfaceType, _visitor_context: &mut T) {}
109    fn leave_interface_type(&self, _node: &InterfaceType, _visitor_context: &mut T) {}
110
111    fn enter_interface_type_field(
112        &self,
113        _node: &Field,
114        _type_: &InterfaceType,
115        _visitor_context: &mut T,
116    ) {
117    }
118    fn leave_interface_type_field(
119        &self,
120        _node: &Field,
121        _type_: &InterfaceType,
122        _visitor_context: &mut T,
123    ) {
124    }
125
126    fn enter_object_type(&self, _node: &ObjectType, _visitor_context: &mut T) {}
127    fn leave_object_type(&self, _node: &ObjectType, _visitor_context: &mut T) {}
128
129    fn enter_object_type_field(
130        &self,
131        _node: &Field,
132        _type_: &ObjectType,
133        _visitor_context: &mut T,
134    ) {
135    }
136    fn leave_object_type_field(
137        &self,
138        _node: &Field,
139        _type_: &ObjectType,
140        _visitor_context: &mut T,
141    ) {
142    }
143
144    fn enter_input_object_type(&self, _node: &InputObjectType, _visitor_context: &mut T) {}
145    fn leave_input_object_type(&self, _node: &InputObjectType, _visitor_context: &mut T) {}
146
147    fn enter_input_object_type_field(
148        &self,
149        _node: &InputValue,
150        _input_type: &InputObjectType,
151        _visitor_context: &mut T,
152    ) {
153    }
154    fn leave_input_object_type_field(
155        &self,
156        _node: &InputValue,
157        _input_type: &InputObjectType,
158        _visitor_context: &mut T,
159    ) {
160    }
161
162    fn enter_union_type(&self, _node: &UnionType, _visitor_context: &mut T) {}
163    fn leave_union_type(&self, _node: &UnionType, _visitor_context: &mut T) {}
164
165    fn enter_scalar_type(&self, _node: &ScalarType, _visitor_context: &mut T) {}
166    fn leave_scalar_type(&self, _node: &ScalarType, _visitor_context: &mut T) {}
167
168    fn enter_enum_type(&self, _node: &EnumType, _visitor_context: &mut T) {}
169    fn leave_enum_type(&self, _node: &EnumType, _visitor_context: &mut T) {}
170
171    fn enter_enum_value(&self, _node: &EnumValue, _enum: &EnumType, _visitor_context: &mut T) {}
172    fn leave_enum_value(&self, _node: &EnumValue, _enum: &EnumType, _visitor_context: &mut T) {}
173}
174
175#[test]
176fn visit_schema() {
177    use crate::parser::schema::parse_schema;
178    let schema_ast = parse_schema(
179        r#"
180    scalar Date
181
182    type Query {
183      user(id: ID!): User!
184      users(filter: UsersFilter): [User!]!
185      now: Date
186    }
187
188    input UsersFilter {
189      name: String
190    }
191
192    type User implements Node {
193      id: ID!
194      name: String!
195      role: Role!
196    }
197
198    interface Node {
199      id: ID!
200    }
201
202    type Test {
203      foo: String!
204    }
205
206    enum Role {
207      USER
208      ADMIN
209    }
210
211    union TestUnion = Test | User
212
213    "#,
214    )
215    .expect("Failed to parse schema");
216
217    struct TestVisitorCollected {
218        collected_object_type: Vec<String>,
219        collected_scalar_type: Vec<String>,
220        collected_union_type: Vec<String>,
221        collected_input_type: Vec<String>,
222        collected_enum_type: Vec<String>,
223        collected_enum_value: Vec<String>,
224        collected_interface_type: Vec<String>,
225        collected_object_type_field: Vec<String>,
226        collected_interface_type_field: Vec<String>,
227        collected_input_type_fields: Vec<String>,
228    }
229
230    struct TestVisitor;
231
232    impl TestVisitor {
233        fn collect_visited_info(&self, document: &Document) -> TestVisitorCollected {
234            let mut collected = TestVisitorCollected {
235                collected_object_type: Vec::new(),
236                collected_interface_type: Vec::new(),
237                collected_object_type_field: Vec::new(),
238                collected_interface_type_field: Vec::new(),
239                collected_scalar_type: Vec::new(),
240                collected_union_type: Vec::new(),
241                collected_enum_type: Vec::new(),
242                collected_enum_value: Vec::new(),
243                collected_input_type: Vec::new(),
244                collected_input_type_fields: Vec::new(),
245            };
246            self.visit_schema_document(document, &mut collected);
247
248            collected
249        }
250    }
251
252    impl SchemaVisitor<TestVisitorCollected> for TestVisitor {
253        fn enter_object_type(
254            &self,
255            _node: &ObjectType,
256            _visitor_context: &mut TestVisitorCollected,
257        ) {
258            _visitor_context
259                .collected_object_type
260                .push(_node.name.clone());
261        }
262
263        fn enter_object_type_field(
264            &self,
265            _node: &Field,
266            _type_: &ObjectType,
267            _visitor_context: &mut TestVisitorCollected,
268        ) {
269            let field_id = format!("{}.{}", _type_.name.as_str(), _node.name.as_str());
270            _visitor_context.collected_object_type_field.push(field_id);
271        }
272
273        fn enter_interface_type(
274            &self,
275            _node: &InterfaceType,
276            _visitor_context: &mut TestVisitorCollected,
277        ) {
278            _visitor_context
279                .collected_interface_type
280                .push(_node.name.clone());
281        }
282
283        fn enter_interface_type_field(
284            &self,
285            _node: &Field,
286            _type_: &InterfaceType,
287            _visitor_context: &mut TestVisitorCollected,
288        ) {
289            _visitor_context
290                .collected_interface_type_field
291                .push(_node.name.clone());
292        }
293
294        fn enter_scalar_type(
295            &self,
296            _node: &ScalarType,
297            _visitor_context: &mut TestVisitorCollected,
298        ) {
299            _visitor_context
300                .collected_scalar_type
301                .push(_node.name.clone());
302        }
303
304        fn enter_union_type(&self, _node: &UnionType, _visitor_context: &mut TestVisitorCollected) {
305            _visitor_context
306                .collected_union_type
307                .push(_node.name.clone());
308        }
309
310        fn enter_enum_type(&self, _node: &EnumType, _visitor_context: &mut TestVisitorCollected) {
311            _visitor_context
312                .collected_enum_type
313                .push(_node.name.clone());
314        }
315
316        fn enter_enum_value(
317            &self,
318            _node: &EnumValue,
319            _enum: &EnumType,
320            _visitor_context: &mut TestVisitorCollected,
321        ) {
322            let enum_value_id = format!("{}.{}", _enum.name.as_str(), _node.name.as_str());
323            _visitor_context.collected_enum_value.push(enum_value_id);
324        }
325
326        fn enter_input_object_type(
327            &self,
328            _node: &InputObjectType,
329            _visitor_context: &mut TestVisitorCollected,
330        ) {
331            _visitor_context
332                .collected_input_type
333                .push(_node.name.clone());
334        }
335
336        fn enter_input_object_type_field(
337            &self,
338            _node: &InputValue,
339            _input_type: &InputObjectType,
340            _visitor_context: &mut TestVisitorCollected,
341        ) {
342            let field_id = format!("{}.{}", _input_type.name.as_str(), _node.name.as_str());
343            _visitor_context.collected_input_type_fields.push(field_id);
344        }
345    }
346
347    let visitor = TestVisitor {};
348    let collected = visitor.collect_visited_info(&schema_ast);
349
350    assert_eq!(
351        collected.collected_object_type,
352        vec!["Query", "User", "Test"]
353    );
354    assert_eq!(
355        collected.collected_object_type_field,
356        vec![
357            "Query.user",
358            "Query.users",
359            "Query.now",
360            "User.id",
361            "User.name",
362            "User.role",
363            "Test.foo"
364        ]
365    );
366    assert_eq!(collected.collected_interface_type, vec!["Node"]);
367    assert_eq!(collected.collected_union_type, vec!["TestUnion"]);
368    assert_eq!(collected.collected_scalar_type, vec!["Date"]);
369    assert_eq!(collected.collected_enum_type, vec!["Role"]);
370    assert_eq!(
371        collected.collected_enum_value,
372        vec!["Role.USER", "Role.ADMIN"]
373    );
374    assert_eq!(collected.collected_input_type, vec!["UsersFilter"]);
375    assert_eq!(
376        collected.collected_input_type_fields,
377        vec!["UsersFilter.name"]
378    );
379}