1use crate::executable::{
2 operation::{Analyzer, OperationDefinitionValueEvaluationExt, VariableValues, Visitor},
3 Cache,
4};
5use bluejay_core::{
6 definition::{ArgumentsDefinition, DirectiveDefinition, DirectiveLocation},
7 executable::{
8 ExecutableDocument, Field, FragmentDefinition, FragmentSpread, InlineFragment,
9 OperationDefinition, Selection, SelectionReference,
10 },
11};
12use bluejay_core::{
13 definition::{
14 FieldDefinition, FieldsDefinition, ObjectTypeDefinition, OutputType, SchemaDefinition,
15 TypeDefinitionReference,
16 },
17 ValueReference,
18};
19use bluejay_core::{Argument, AsIter, Directive, OperationType, Value};
20use std::borrow::Cow;
21use std::collections::HashSet;
22
23pub struct Orchestrator<
24 'a,
25 E: ExecutableDocument,
26 S: SchemaDefinition,
27 VV: VariableValues,
28 V: Visitor<'a, E, S, VV>,
29> {
30 schema_definition: &'a S,
31 operation_definition: &'a E::OperationDefinition,
32 variable_values: &'a VV,
33 visitor: V,
34 cache: &'a Cache<'a, E, S>,
35 currently_spread_fragments: HashSet<&'a str>,
36}
37
38impl<
39 'a,
40 E: ExecutableDocument,
41 S: SchemaDefinition,
42 VV: VariableValues,
43 V: Visitor<'a, E, S, VV>,
44 > Orchestrator<'a, E, S, VV, V>
45{
46 const SKIP_DIRECTIVE_NAME: &'static str = "skip";
47 const INCLUDE_DIRECTIVE_NAME: &'static str = "include";
48 const SKIP_INCLUDE_CONDITION_ARGUMENT: &'static str = "if";
49
50 fn new(
51 operation_definition: &'a E::OperationDefinition,
52 schema_definition: &'a S,
53 variable_values: &'a VV,
54 cache: &'a Cache<'a, E, S>,
55 extra_info: V::ExtraInfo,
56 ) -> Self {
57 Self {
58 schema_definition,
59 operation_definition,
60 variable_values,
61 visitor: Visitor::new(
62 operation_definition,
63 schema_definition,
64 variable_values,
65 cache,
66 extra_info,
67 ),
68 cache,
69 currently_spread_fragments: HashSet::new(),
70 }
71 }
72
73 fn visit(&mut self) {
74 self.visit_operation_definition(self.operation_definition);
75 }
76
77 fn visit_variable_directives(
78 &mut self,
79 directives: &'a E::Directives<false>,
80 location: DirectiveLocation,
81 ) {
82 directives
83 .iter()
84 .for_each(|directive| self.visit_variable_directive(directive, location));
85 }
86
87 fn visit_variable_directive(
88 &mut self,
89 directive: &'a E::Directive<false>,
90 _location: DirectiveLocation,
91 ) {
92 if let Some(arguments) = directive.arguments() {
93 if let Some(arguments_definition) = self
94 .schema_definition
95 .get_directive_definition(directive.name())
96 .and_then(DirectiveDefinition::arguments_definition)
97 {
98 self.visit_variable_arguments(arguments, arguments_definition);
99 }
100 }
101 }
102
103 fn visit_operation_definition(&mut self, operation_definition: &'a E::OperationDefinition) {
104 let core_operation_definition = operation_definition.as_ref();
105
106 let root_operation_type_definition_name = match core_operation_definition.operation_type() {
107 OperationType::Query => Some(self.schema_definition.query().name()),
108 OperationType::Mutation => self
109 .schema_definition
110 .mutation()
111 .map(ObjectTypeDefinition::name),
112 OperationType::Subscription => self
113 .schema_definition
114 .subscription()
115 .map(ObjectTypeDefinition::name),
116 };
117
118 if let Some(directives) = core_operation_definition.directives() {
119 self.visit_variable_directives(
120 directives,
121 core_operation_definition
122 .operation_type()
123 .associated_directive_location(),
124 )
125 }
126
127 if let Some(root_operation_type_definition_name) = root_operation_type_definition_name {
128 self.visit_selection_set(
129 core_operation_definition.selection_set(),
130 self.schema_definition
131 .get_type_definition(root_operation_type_definition_name)
132 .unwrap_or_else(|| {
133 panic!(
134 "Schema definition's `get_type` method returned `None` for {} root",
135 core_operation_definition.operation_type()
136 )
137 }),
138 true,
139 );
140 }
141
142 if let Some(variable_definitions) = operation_definition.as_ref().variable_definitions() {
143 variable_definitions.iter().for_each(|variable_definition| {
144 self.visitor.visit_variable_definition(variable_definition)
145 });
146 }
147 }
148
149 fn visit_selection_set(
150 &mut self,
151 selection_set: &'a E::SelectionSet,
152 scoped_type: TypeDefinitionReference<'a, S::TypeDefinition>,
153 included: bool,
154 ) {
155 selection_set
156 .iter()
157 .for_each(|selection| match selection.as_ref() {
158 SelectionReference::Field(f) => {
159 let field_definition = scoped_type
160 .fields_definition()
161 .and_then(|fields_definition| fields_definition.get(f.name()));
162
163 if let Some(field_definition) = field_definition {
164 self.visit_field(f, field_definition, scoped_type, included);
165 }
166 }
167 SelectionReference::InlineFragment(i) => {
168 self.visit_inline_fragment(i, scoped_type, included)
169 }
170 SelectionReference::FragmentSpread(fs) => self.visit_fragment_spread(fs, included),
171 })
172 }
173
174 fn visit_field(
175 &mut self,
176 field: &'a E::Field,
177 field_definition: &'a S::FieldDefinition,
178 owner_type: TypeDefinitionReference<'a, S::TypeDefinition>,
179 included: bool,
180 ) {
181 if let Some(directives) = field.directives() {
182 self.visit_variable_directives(directives, DirectiveLocation::Field);
183 }
184 let included = included
185 && field.directives().map_or(true, |directives| {
186 self.evaluate_selection_inclusion(directives)
187 });
188
189 self.visitor
190 .visit_field(field, field_definition, owner_type, included);
191
192 if let Some(arguments) = field.arguments() {
193 if let Some(arguments_definition) = field_definition.arguments_definition() {
194 arguments.iter().for_each(|argument| {
195 if let Some(ivd) = arguments_definition.get(argument.name()) {
196 self.visit_variable_argument(argument, ivd);
197 }
198 })
199 }
200 }
201
202 if let Some(selection_set) = field.selection_set() {
203 if let Some(nested_type) = self
204 .schema_definition
205 .get_type_definition(field_definition.r#type().base_name())
206 {
207 self.visit_selection_set(selection_set, nested_type, included);
208 }
209 }
210
211 self.visitor
212 .leave_field(field, field_definition, owner_type, included);
213 }
214
215 fn visit_variable_arguments(
216 &mut self,
217 arguments: &'a E::Arguments<false>,
218 arguments_definition: &'a S::ArgumentsDefinition,
219 ) {
220 arguments.iter().for_each(|argument| {
221 if let Some(ivd) = arguments_definition.get(argument.name()) {
222 self.visit_variable_argument(argument, ivd);
223 }
224 });
225 }
226
227 fn visit_variable_argument(
228 &mut self,
229 argument: &'a E::Argument<false>,
230 input_value_definition: &'a S::InputValueDefinition,
231 ) {
232 self.visitor
233 .visit_variable_argument(argument, input_value_definition);
234 }
235
236 fn visit_inline_fragment(
237 &mut self,
238 inline_fragment: &'a E::InlineFragment,
239 scoped_type: TypeDefinitionReference<'a, S::TypeDefinition>,
240 included: bool,
241 ) {
242 if let Some(directives) = inline_fragment.directives() {
243 self.visit_variable_directives(directives, DirectiveLocation::InlineFragment);
244 }
245
246 let included = included
247 && inline_fragment.directives().map_or(true, |directives| {
248 self.evaluate_selection_inclusion(directives)
249 });
250
251 let fragment_type = if let Some(type_condition) = inline_fragment.type_condition() {
252 self.schema_definition.get_type_definition(type_condition)
253 } else {
254 Some(scoped_type)
255 };
256
257 if let Some(fragment_type) = fragment_type {
258 self.visit_selection_set(inline_fragment.selection_set(), fragment_type, included);
259 }
260 }
261
262 fn visit_fragment_spread(&mut self, fragment_spread: &'a E::FragmentSpread, included: bool) {
263 if let Some(directives) = fragment_spread.directives() {
264 self.visit_variable_directives(directives, DirectiveLocation::FragmentSpread);
265 }
266
267 let included = included
268 && fragment_spread.directives().map_or(true, |directives| {
269 self.evaluate_selection_inclusion(directives)
270 });
271 if self
272 .currently_spread_fragments
273 .insert(fragment_spread.name())
274 {
275 if let Some(fragment_definition) =
276 self.cache.fragment_definition(fragment_spread.name())
277 {
278 if let Some(type_condition) = self
279 .schema_definition
280 .get_type_definition(fragment_definition.type_condition())
281 {
282 self.visit_selection_set(
283 fragment_definition.selection_set(),
284 type_condition,
285 included,
286 );
287 }
288 }
289 self.currently_spread_fragments
290 .remove(fragment_spread.name());
291 }
292 }
293
294 fn evaluate_selection_inclusion(&mut self, directives: &'a E::Directives<false>) -> bool {
295 let skip_directive_value = self.evaluate_boolean_directive_argument_value(
296 directives,
297 Self::SKIP_DIRECTIVE_NAME,
298 Self::SKIP_INCLUDE_CONDITION_ARGUMENT,
299 );
300
301 let include_directive_value = self.evaluate_boolean_directive_argument_value(
302 directives,
303 Self::INCLUDE_DIRECTIVE_NAME,
304 Self::SKIP_INCLUDE_CONDITION_ARGUMENT,
305 );
306
307 !matches!(
308 (skip_directive_value, include_directive_value),
309 (Some(true), _) | (_, Some(false))
310 )
311 }
312
313 fn evaluate_boolean_directive_argument_value(
314 &self,
315 directives: &'a E::Directives<false>,
316 directive_name: &str,
317 arg_name: &str,
318 ) -> Option<bool> {
319 directives
320 .iter()
321 .find(|directive| directive.name() == directive_name)
322 .and_then(|directive| {
323 directive
324 .arguments()
325 .and_then(|arguments| arguments.iter().find(|arg| arg.name() == arg_name))
326 .and_then(|argument| match argument.value().as_ref() {
327 ValueReference::Boolean(val) => Some(val),
328 ValueReference::Variable(v) => self
329 .operation_definition
330 .evaluate_bool(v, self.variable_values),
331 _ => None,
332 })
333 })
334 }
335
336 pub fn analyze<'b>(
337 executable_document: &'a E,
338 schema_definition: &'a S,
339 operation_name: Option<&'b str>,
340 variable_values: &'a VV,
341 cache: &'a Cache<'a, E, S>,
342 extra_info: V::ExtraInfo,
343 ) -> Result<<V as Analyzer<'a, E, S, VV>>::Output, OperationResolutionError<'b>>
344 where
345 V: Analyzer<'a, E, S, VV>,
346 {
347 let operation_definition = match operation_name {
348 Some(operation_name) => executable_document
349 .operation_definitions()
350 .find(|operation_definition| {
351 operation_definition
352 .as_ref()
353 .name()
354 .map_or(false, |name| name == operation_name)
355 })
356 .ok_or(OperationResolutionError::NoOperationWithName {
357 name: operation_name,
358 })?,
359 None => {
360 let [operation_definition]: [&'a E::OperationDefinition; 1] = executable_document
361 .operation_definitions()
362 .collect::<Vec<_>>()
363 .as_slice()
364 .try_into()
365 .map_err(|_| OperationResolutionError::AnonymousNotEligible)?;
366 operation_definition
367 }
368 };
369 let mut instance = Self::new(
370 operation_definition,
371 schema_definition,
372 variable_values,
373 cache,
374 extra_info,
375 );
376 instance.visit();
377 Ok(instance.visitor.into_output())
378 }
379}
380
381#[derive(Debug)]
382pub enum OperationResolutionError<'a> {
383 NoOperationWithName { name: &'a str },
384 AnonymousNotEligible,
385}
386
387impl<'a> OperationResolutionError<'a> {
388 pub fn message(&self) -> Cow<'static, str> {
389 match self {
390 Self::NoOperationWithName { name } => format!("No operation defined with name {}", name).into(),
391 Self::AnonymousNotEligible => "Anonymous operation can only be used when the document contains exactly one operation definition".into(),
392 }
393 }
394}