Skip to main content

bluejay_validator/executable/document/
orchestrator.rs

1use crate::executable::{
2    document::{Analyzer, BuiltinRules, Path, PathRoot, Rule, Visitor},
3    Cache,
4};
5use bluejay_core::definition::{
6    ArgumentsDefinition, DirectiveDefinition, DirectiveLocation, FieldDefinition, FieldsDefinition,
7    ObjectTypeDefinition, OutputType, SchemaDefinition, TypeDefinitionReference,
8};
9use bluejay_core::executable::{
10    ExecutableDocument, Field, FragmentDefinition, FragmentSpread, InlineFragment,
11    OperationDefinition, Selection, SelectionReference, VariableDefinition,
12};
13use bluejay_core::{Argument, AsIter, Directive, OperationType};
14
15pub struct Orchestrator<'a, E: ExecutableDocument, S: SchemaDefinition, V: Visitor<'a, E, S>> {
16    schema_definition: &'a S,
17    executable_document: &'a E,
18    visitor: V,
19}
20
21pub type BuiltinRulesValidator<'a, E, S> = Orchestrator<'a, E, S, BuiltinRules<'a, E, S>>;
22
23impl<'a, E: ExecutableDocument, S: SchemaDefinition, V: Visitor<'a, E, S>>
24    Orchestrator<'a, E, S, V>
25{
26    fn new(
27        executable_document: &'a E,
28        schema_definition: &'a S,
29        cache: &'a Cache<'a, E, S>,
30    ) -> Self {
31        Self {
32            schema_definition,
33            executable_document,
34            visitor: Visitor::new(executable_document, schema_definition, cache),
35        }
36    }
37
38    fn visit(&mut self) {
39        self.executable_document
40            .operation_definitions()
41            .for_each(|operation_definition| {
42                self.visit_operation_definition(operation_definition);
43            });
44        self.executable_document
45            .fragment_definitions()
46            .for_each(|fragment_definition| {
47                self.visit_fragment_definition(fragment_definition);
48            });
49    }
50
51    fn visit_operation_definition(&mut self, operation_definition: &'a E::OperationDefinition) {
52        let path = Path::new(PathRoot::Operation(operation_definition));
53        self.visitor
54            .visit_operation_definition(operation_definition);
55        let core_operation_definition = operation_definition.as_ref();
56        if let Some(directives) = core_operation_definition.directives() {
57            self.visit_variable_directives(
58                directives,
59                core_operation_definition
60                    .operation_type()
61                    .associated_directive_location(),
62                &path,
63            );
64        }
65
66        if let Some(variable_definitions) = core_operation_definition.variable_definitions() {
67            self.visit_variable_definitions(variable_definitions);
68        }
69
70        let root_operation_type_definition_name = match core_operation_definition.operation_type() {
71            OperationType::Query => Some(self.schema_definition.query().name()),
72            OperationType::Mutation => self
73                .schema_definition
74                .mutation()
75                .map(ObjectTypeDefinition::name),
76            OperationType::Subscription => self
77                .schema_definition
78                .subscription()
79                .map(ObjectTypeDefinition::name),
80        };
81
82        if let Some(root_operation_type_definition_name) = root_operation_type_definition_name {
83            self.visit_selection_set(
84                core_operation_definition.selection_set(),
85                self.schema_definition
86                    .get_type_definition(root_operation_type_definition_name)
87                    .unwrap_or_else(|| {
88                        panic!(
89                            "Schema definition's `get_type` method returned `None` for {} root",
90                            core_operation_definition.operation_type()
91                        )
92                    }),
93                &path,
94            );
95        }
96    }
97
98    fn visit_fragment_definition(&mut self, fragment_definition: &'a E::FragmentDefinition) {
99        let path = Path::new(PathRoot::Fragment(fragment_definition));
100        let type_condition = self
101            .schema_definition
102            .get_type_definition(fragment_definition.type_condition());
103        if let Some(type_condition) = type_condition {
104            self.visit_selection_set(fragment_definition.selection_set(), type_condition, &path);
105        }
106        if let Some(directives) = fragment_definition.directives() {
107            self.visit_variable_directives(
108                directives,
109                DirectiveLocation::FragmentDefinition,
110                &path,
111            );
112        }
113
114        self.visitor.visit_fragment_definition(fragment_definition);
115    }
116
117    fn visit_selection_set(
118        &mut self,
119        selection_set: &'a E::SelectionSet,
120        scoped_type: TypeDefinitionReference<'a, S::TypeDefinition>,
121        path: &Path<'a, E>,
122    ) {
123        self.visitor.visit_selection_set(selection_set, scoped_type);
124
125        selection_set
126            .iter()
127            .for_each(|selection| match selection.as_ref() {
128                SelectionReference::Field(f) => {
129                    let field_definition = scoped_type
130                        .fields_definition()
131                        .and_then(|fields_definition| fields_definition.get(f.name()));
132
133                    if let Some(field_definition) = field_definition {
134                        self.visit_field(f, field_definition, path);
135                    }
136                }
137                SelectionReference::InlineFragment(i) => {
138                    self.visit_inline_fragment(i, scoped_type, path)
139                }
140                SelectionReference::FragmentSpread(fs) => {
141                    self.visit_fragment_spread(fs, scoped_type, path)
142                }
143            })
144    }
145
146    fn visit_field(
147        &mut self,
148        field: &'a E::Field,
149        field_definition: &'a S::FieldDefinition,
150        path: &Path<'a, E>,
151    ) {
152        self.visitor.visit_field(field, field_definition, path);
153        if let Some(directives) = field.directives() {
154            self.visit_variable_directives(directives, DirectiveLocation::Field, path);
155        }
156
157        if let Some((arguments, arguments_definition)) = field
158            .arguments()
159            .zip(field_definition.arguments_definition())
160        {
161            self.visit_variable_arguments(arguments, arguments_definition, path);
162        }
163
164        if let Some(selection_set) = field.selection_set() {
165            if let Some(nested_type) = self
166                .schema_definition
167                .get_type_definition(field_definition.r#type().base_name())
168            {
169                self.visit_selection_set(selection_set, nested_type, path);
170            }
171        }
172
173        self.visitor.leave_field(field, field_definition);
174    }
175
176    fn visit_variable_directives(
177        &mut self,
178        directives: &'a E::Directives<false>,
179        location: DirectiveLocation,
180        path: &Path<'a, E>,
181    ) {
182        self.visitor.visit_variable_directives(directives, location);
183        directives
184            .iter()
185            .for_each(|directive| self.visit_variable_directive(directive, location, path));
186    }
187
188    fn visit_const_directives(
189        &mut self,
190        directives: &'a E::Directives<true>,
191        location: DirectiveLocation,
192    ) {
193        self.visitor.visit_const_directives(directives, location);
194        directives
195            .iter()
196            .for_each(|directive| self.visit_const_directive(directive, location));
197    }
198
199    fn visit_variable_directive(
200        &mut self,
201        directive: &'a E::Directive<false>,
202        location: DirectiveLocation,
203        path: &Path<'a, E>,
204    ) {
205        self.visitor.visit_variable_directive(directive, location);
206        if let Some(arguments) = directive.arguments() {
207            if let Some(arguments_definition) = self
208                .schema_definition
209                .get_directive_definition(directive.name())
210                .and_then(DirectiveDefinition::arguments_definition)
211            {
212                self.visit_variable_arguments(arguments, arguments_definition, path);
213            }
214        }
215    }
216
217    fn visit_const_directive(
218        &mut self,
219        directive: &'a E::Directive<true>,
220        location: DirectiveLocation,
221    ) {
222        self.visitor.visit_const_directive(directive, location);
223        if let Some(arguments) = directive.arguments() {
224            if let Some(arguments_definition) = self
225                .schema_definition
226                .get_directive_definition(directive.name())
227                .and_then(DirectiveDefinition::arguments_definition)
228            {
229                self.visit_const_arguments(arguments, arguments_definition);
230            }
231        }
232    }
233
234    fn visit_inline_fragment(
235        &mut self,
236        inline_fragment: &'a E::InlineFragment,
237        scoped_type: TypeDefinitionReference<'a, S::TypeDefinition>,
238        path: &Path<'a, E>,
239    ) {
240        if let Some(directives) = inline_fragment.directives() {
241            self.visit_variable_directives(directives, DirectiveLocation::InlineFragment, path);
242        }
243
244        let fragment_type = if let Some(type_condition) = inline_fragment.type_condition() {
245            self.schema_definition.get_type_definition(type_condition)
246        } else {
247            Some(scoped_type)
248        };
249
250        if let Some(fragment_type) = fragment_type {
251            self.visit_selection_set(inline_fragment.selection_set(), fragment_type, path);
252        }
253
254        self.visitor
255            .visit_inline_fragment(inline_fragment, scoped_type);
256    }
257
258    fn visit_fragment_spread(
259        &mut self,
260        fragment_spread: &'a E::FragmentSpread,
261        scoped_type: TypeDefinitionReference<'a, S::TypeDefinition>,
262        path: &Path<'a, E>,
263    ) {
264        if let Some(directives) = fragment_spread.directives() {
265            self.visit_variable_directives(directives, DirectiveLocation::FragmentSpread, path);
266        }
267
268        self.visitor
269            .visit_fragment_spread(fragment_spread, scoped_type, path);
270        // fragment will get checked when definition is visited
271    }
272
273    fn visit_variable_definitions(&mut self, variable_definitions: &'a E::VariableDefinitions) {
274        self.visitor
275            .visit_variable_definitions(variable_definitions);
276        variable_definitions.iter().for_each(|variable_definition| {
277            if let Some(directives) = variable_definition.directives() {
278                self.visit_const_directives(directives, DirectiveLocation::VariableDefinition);
279            }
280            self.visitor.visit_variable_definition(variable_definition);
281        });
282    }
283
284    fn visit_const_arguments(
285        &mut self,
286        arguments: &'a E::Arguments<true>,
287        arguments_definition: &'a S::ArgumentsDefinition,
288    ) {
289        arguments.iter().for_each(|argument| {
290            if let Some(ivd) = arguments_definition.get(argument.name()) {
291                self.visit_const_argument(argument, ivd);
292            }
293        });
294    }
295
296    fn visit_variable_arguments(
297        &mut self,
298        arguments: &'a E::Arguments<false>,
299        arguments_definition: &'a S::ArgumentsDefinition,
300        path: &Path<'a, E>,
301    ) {
302        arguments.iter().for_each(|argument| {
303            if let Some(ivd) = arguments_definition.get(argument.name()) {
304                self.visit_variable_argument(argument, ivd, path);
305            }
306        });
307    }
308
309    fn visit_const_argument(
310        &mut self,
311        argument: &'a E::Argument<true>,
312        input_value_definition: &'a S::InputValueDefinition,
313    ) {
314        self.visitor
315            .visit_const_argument(argument, input_value_definition);
316    }
317
318    fn visit_variable_argument(
319        &mut self,
320        argument: &'a E::Argument<false>,
321        input_value_definition: &'a S::InputValueDefinition,
322        path: &Path<'a, E>,
323    ) {
324        self.visitor
325            .visit_variable_argument(argument, input_value_definition, path);
326    }
327
328    pub fn validate(
329        executable_document: &'a E,
330        schema_definition: &'a S,
331        cache: &'a Cache<'a, E, S>,
332    ) -> <V as Rule<'a, E, S>>::Errors
333    where
334        V: Rule<'a, E, S>,
335    {
336        let mut instance = Self::new(executable_document, schema_definition, cache);
337        instance.visit();
338        instance.visitor.into_errors()
339    }
340
341    pub fn analyze(
342        executable_document: &'a E,
343        schema_definition: &'a S,
344        cache: &'a Cache<'a, E, S>,
345    ) -> <V as Analyzer<'a, E, S>>::Output
346    where
347        V: Analyzer<'a, E, S>,
348    {
349        let mut instance = Self::new(executable_document, schema_definition, cache);
350        instance.visit();
351        instance.visitor.into_output()
352    }
353}
354
355impl<'a, E: ExecutableDocument, S: SchemaDefinition, R: Rule<'a, E, S>, A: Analyzer<'a, E, S>>
356    Orchestrator<'a, E, S, (R, A)>
357{
358    pub fn validate_and_analyze(
359        executable_document: &'a E,
360        schema_definition: &'a S,
361        cache: &'a Cache<'a, E, S>,
362    ) -> (
363        <R as Rule<'a, E, S>>::Errors,
364        <A as Analyzer<'a, E, S>>::Output,
365    ) {
366        let mut instance = Self::new(executable_document, schema_definition, cache);
367        instance.visit();
368        (
369            instance.visitor.0.into_errors(),
370            instance.visitor.1.into_output(),
371        )
372    }
373}