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.iter().for_each(|selection| {
126            let nested_path = path.with_selection(selection);
127            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, &nested_path);
135                    }
136                }
137                SelectionReference::InlineFragment(i) => {
138                    self.visit_inline_fragment(i, scoped_type, &nested_path)
139                }
140                SelectionReference::FragmentSpread(fs) => {
141                    self.visit_fragment_spread(fs, scoped_type, path)
142                }
143            }
144        })
145    }
146
147    fn visit_field(
148        &mut self,
149        field: &'a E::Field,
150        field_definition: &'a S::FieldDefinition,
151        path: &Path<'a, E>,
152    ) {
153        self.visitor.visit_field(field, field_definition, path);
154        if let Some(directives) = field.directives() {
155            self.visit_variable_directives(directives, DirectiveLocation::Field, path);
156        }
157
158        if let Some((arguments, arguments_definition)) = field
159            .arguments()
160            .zip(field_definition.arguments_definition())
161        {
162            self.visit_variable_arguments(arguments, arguments_definition, path);
163        }
164
165        if let Some(selection_set) = field.selection_set() {
166            if let Some(nested_type) = self
167                .schema_definition
168                .get_type_definition(field_definition.r#type().base_name())
169            {
170                self.visit_selection_set(selection_set, nested_type, path);
171            }
172        }
173    }
174
175    fn visit_variable_directives(
176        &mut self,
177        directives: &'a E::Directives<false>,
178        location: DirectiveLocation,
179        path: &Path<'a, E>,
180    ) {
181        self.visitor.visit_variable_directives(directives, location);
182        directives
183            .iter()
184            .for_each(|directive| self.visit_variable_directive(directive, location, path));
185    }
186
187    fn visit_const_directives(
188        &mut self,
189        directives: &'a E::Directives<true>,
190        location: DirectiveLocation,
191    ) {
192        self.visitor.visit_const_directives(directives, location);
193        directives
194            .iter()
195            .for_each(|directive| self.visit_const_directive(directive, location));
196    }
197
198    fn visit_variable_directive(
199        &mut self,
200        directive: &'a E::Directive<false>,
201        location: DirectiveLocation,
202        path: &Path<'a, E>,
203    ) {
204        self.visitor.visit_variable_directive(directive, location);
205        if let Some(arguments) = directive.arguments() {
206            if let Some(arguments_definition) = self
207                .schema_definition
208                .get_directive_definition(directive.name())
209                .and_then(DirectiveDefinition::arguments_definition)
210            {
211                self.visit_variable_arguments(arguments, arguments_definition, path);
212            }
213        }
214    }
215
216    fn visit_const_directive(
217        &mut self,
218        directive: &'a E::Directive<true>,
219        location: DirectiveLocation,
220    ) {
221        self.visitor.visit_const_directive(directive, location);
222        if let Some(arguments) = directive.arguments() {
223            if let Some(arguments_definition) = self
224                .schema_definition
225                .get_directive_definition(directive.name())
226                .and_then(DirectiveDefinition::arguments_definition)
227            {
228                self.visit_const_arguments(arguments, arguments_definition);
229            }
230        }
231    }
232
233    fn visit_inline_fragment(
234        &mut self,
235        inline_fragment: &'a E::InlineFragment,
236        scoped_type: TypeDefinitionReference<'a, S::TypeDefinition>,
237        path: &Path<'a, E>,
238    ) {
239        if let Some(directives) = inline_fragment.directives() {
240            self.visit_variable_directives(directives, DirectiveLocation::InlineFragment, path);
241        }
242
243        let fragment_type = if let Some(type_condition) = inline_fragment.type_condition() {
244            self.schema_definition.get_type_definition(type_condition)
245        } else {
246            Some(scoped_type)
247        };
248
249        if let Some(fragment_type) = fragment_type {
250            self.visit_selection_set(inline_fragment.selection_set(), fragment_type, path);
251        }
252
253        self.visitor
254            .visit_inline_fragment(inline_fragment, scoped_type);
255    }
256
257    fn visit_fragment_spread(
258        &mut self,
259        fragment_spread: &'a E::FragmentSpread,
260        scoped_type: TypeDefinitionReference<'a, S::TypeDefinition>,
261        path: &Path<'a, E>,
262    ) {
263        if let Some(directives) = fragment_spread.directives() {
264            self.visit_variable_directives(directives, DirectiveLocation::FragmentSpread, path);
265        }
266
267        self.visitor
268            .visit_fragment_spread(fragment_spread, scoped_type, path);
269        // fragment will get checked when definition is visited
270    }
271
272    fn visit_variable_definitions(&mut self, variable_definitions: &'a E::VariableDefinitions) {
273        self.visitor
274            .visit_variable_definitions(variable_definitions);
275        variable_definitions.iter().for_each(|variable_definition| {
276            if let Some(directives) = variable_definition.directives() {
277                self.visit_const_directives(directives, DirectiveLocation::VariableDefinition);
278            }
279            self.visitor.visit_variable_definition(variable_definition);
280        });
281    }
282
283    fn visit_const_arguments(
284        &mut self,
285        arguments: &'a E::Arguments<true>,
286        arguments_definition: &'a S::ArgumentsDefinition,
287    ) {
288        arguments.iter().for_each(|argument| {
289            if let Some(ivd) = arguments_definition.get(argument.name()) {
290                self.visit_const_argument(argument, ivd);
291            }
292        });
293    }
294
295    fn visit_variable_arguments(
296        &mut self,
297        arguments: &'a E::Arguments<false>,
298        arguments_definition: &'a S::ArgumentsDefinition,
299        path: &Path<'a, E>,
300    ) {
301        arguments.iter().for_each(|argument| {
302            if let Some(ivd) = arguments_definition.get(argument.name()) {
303                self.visit_variable_argument(argument, ivd, path);
304            }
305        });
306    }
307
308    fn visit_const_argument(
309        &mut self,
310        argument: &'a E::Argument<true>,
311        input_value_definition: &'a S::InputValueDefinition,
312    ) {
313        self.visitor
314            .visit_const_argument(argument, input_value_definition);
315    }
316
317    fn visit_variable_argument(
318        &mut self,
319        argument: &'a E::Argument<false>,
320        input_value_definition: &'a S::InputValueDefinition,
321        path: &Path<'a, E>,
322    ) {
323        self.visitor
324            .visit_variable_argument(argument, input_value_definition, path);
325    }
326
327    pub fn validate(
328        executable_document: &'a E,
329        schema_definition: &'a S,
330        cache: &'a Cache<'a, E, S>,
331    ) -> <V as Rule<'a, E, S>>::Errors
332    where
333        V: Rule<'a, E, S>,
334    {
335        let mut instance = Self::new(executable_document, schema_definition, cache);
336        instance.visit();
337        instance.visitor.into_errors()
338    }
339
340    pub fn analyze(
341        executable_document: &'a E,
342        schema_definition: &'a S,
343        cache: &'a Cache<'a, E, S>,
344    ) -> <V as Analyzer<'a, E, S>>::Output
345    where
346        V: Analyzer<'a, E, S>,
347    {
348        let mut instance = Self::new(executable_document, schema_definition, cache);
349        instance.visit();
350        instance.visitor.into_output()
351    }
352}
353
354impl<'a, E: ExecutableDocument, S: SchemaDefinition, R: Rule<'a, E, S>, A: Analyzer<'a, E, S>>
355    Orchestrator<'a, E, S, (R, A)>
356{
357    pub fn validate_and_analyze(
358        executable_document: &'a E,
359        schema_definition: &'a S,
360        cache: &'a Cache<'a, E, S>,
361    ) -> (
362        <R as Rule<'a, E, S>>::Errors,
363        <A as Analyzer<'a, E, S>>::Output,
364    ) {
365        let mut instance = Self::new(executable_document, schema_definition, cache);
366        instance.visit();
367        (
368            instance.visitor.0.into_errors(),
369            instance.visitor.1.into_output(),
370        )
371    }
372}