libgraphql_core/operation/
operation_builder.rs

1use crate::ast;
2use crate::ast::operation::OperationDefinition;
3use crate::DirectiveAnnotation;
4use crate::DirectiveAnnotationBuilder;
5use crate::file_reader;
6use crate::loc;
7use crate::named_ref::DerefByNameError;
8use crate::operation::FragmentRegistry;
9use crate::operation::Mutation;
10use crate::operation::Operation;
11use crate::operation::OperationBuilderTrait;
12use crate::operation::OperationData;
13use crate::operation::OperationKind;
14use crate::operation::Query;
15use crate::operation::Selection;
16use crate::operation::SelectionSetBuilder;
17use crate::operation::SelectionSetBuildError;
18use crate::operation::Subscription;
19use crate::operation::Variable;
20use crate::schema::Schema;
21use crate::types::GraphQLType;
22use crate::types::TypeAnnotation;
23use crate::Value;
24use indexmap::IndexMap;
25use inherent::inherent;
26use thiserror::Error;
27use std::path::Path;
28use std::sync::Arc;
29
30type Result<T> = std::result::Result<T, Vec<OperationBuildError>>;
31
32struct LoadFromAstDetails<'ast, 'schema> {
33    directives: &'ast Vec<ast::operation::Directive>,
34    name: Option<&'ast String>,
35    op_kind: OperationKind,
36    op_type: &'schema GraphQLType,
37    pos: &'ast ast::AstPos,
38    selection_set: &'ast ast::operation::SelectionSet,
39    variables: &'ast Vec<ast::operation::VariableDefinition>,
40}
41
42#[derive(Clone, Debug, PartialEq)]
43pub struct OperationBuilder<'schema: 'fragreg, 'fragreg> {
44    def_location: Option<loc::SourceLocation>,
45    directives: Vec<DirectiveAnnotation>,
46    fragment_registry: &'fragreg FragmentRegistry<'schema>,
47    name: Option<String>,
48    pub(super) operation_kind: Option<OperationKind>,
49    schema: &'schema Schema,
50    selection_set_builder: SelectionSetBuilder<'schema, 'fragreg>,
51    variables: IndexMap<String, Variable>,
52}
53
54#[inherent]
55impl<'schema: 'fragreg, 'fragreg> OperationBuilderTrait<
56    'schema,
57    'fragreg,
58    OperationDefinition,
59    Vec<OperationBuildError>,
60    Operation<'schema, 'fragreg>,
61> for OperationBuilder<'schema, 'fragreg> {
62    /// Add a [`DirectiveAnnotation`] after any previously added
63    /// `DirectiveAnnotation`s.
64    pub fn add_directive(
65        mut self,
66        annot: DirectiveAnnotation,
67    ) -> Result<Self> {
68        // TODO: Error if a non-repeatable directive is added twice
69        self.directives.push(annot);
70        Ok(self)
71    }
72
73    /// Add a [`Selection`] after any previously added `Selection`s.
74    pub fn add_selection(
75        mut self,
76        selection: Selection<'schema>,
77    ) -> Result<Self> {
78        self.selection_set_builder =
79            self.selection_set_builder
80                .add_selection(selection)
81                .map_err(|e| vec![OperationBuildError::SelectionSetBuildErrors(e)])?;
82
83        Ok(self)
84    }
85
86    /// Add a [`Variable`] after any previously added `Variable`s.
87    pub fn add_variable(
88        mut self,
89        variable: Variable,
90    ) -> Result<Self> {
91        if let Some(existing_variable) = self.variables.get(variable.name.as_str()) {
92            return Err(vec![
93                OperationBuildError::DuplicateVariableName {
94                    variable_definition1: existing_variable.def_location().to_owned(),
95                    variable_definition2: variable.def_location().to_owned(),
96                    variable_name: variable.name,
97                }
98            ]);
99        }
100        self.variables.insert(variable.name.to_owned(), variable);
101        Ok(self)
102    }
103
104    /// Consume ths [`OperationBuilder`] to produce an [`Operation`].
105    pub fn build(self) -> Result<Operation<'schema, 'fragreg>> {
106        let selection_set =
107            self.selection_set_builder
108                .build()
109                .map_err(|e| vec![OperationBuildError::SelectionSetBuildErrors(e)])?;
110
111        let operation_data = OperationData {
112            directives: self.directives,
113            def_location: self.def_location.unwrap_or(
114                loc::SourceLocation::ExecutableDocument
115            ),
116            fragment_registry: self.fragment_registry,
117            name: self.name,
118            schema: self.schema,
119            selection_set,
120            variables: self.variables,
121        };
122
123        Ok(match self.operation_kind {
124            Some(OperationKind::Mutation) => Operation::Mutation(Box::new(
125                Mutation(operation_data),
126            )),
127
128            Some(OperationKind::Query) => Operation::Query(Box::new(
129                Query(operation_data),
130            )),
131
132            Some(OperationKind::Subscription) => Operation::Subscription(Box::new(
133                Subscription(operation_data),
134            )),
135
136            None => return Err(vec![
137                OperationBuildError::AmbiguousOperationKind {
138                    operation_name: operation_data.name,
139                }
140            ]),
141        })
142    }
143
144    /// Produce a [`OperationBuilder`] from a
145    /// [`OperationDefinition`](ast::operation::OperationDefinition).
146    pub fn from_ast(
147        schema: &'schema Schema,
148        fragment_registry: &'fragreg FragmentRegistry<'schema>,
149        ast: &OperationDefinition,
150        file_path: Option<&Path>,
151    ) -> Result<Self> {
152        let ast_details = match ast {
153            OperationDefinition::SelectionSet(ss @ ast::operation::SelectionSet {
154                span: (pos, _),
155                ..
156            }) => LoadFromAstDetails {
157                directives: &vec![],
158                name: None,
159                op_kind: OperationKind::Query,
160                op_type: schema.query_type(),
161                pos,
162                selection_set: ss,
163                variables: &vec![],
164            },
165
166            OperationDefinition::Query(ast::operation::Query {
167                directives,
168                name,
169                position,
170                selection_set,
171                variable_definitions,
172                ..
173            }) => LoadFromAstDetails {
174                directives,
175                name: name.as_ref(),
176                op_kind: OperationKind::Query,
177                op_type: schema.query_type(),
178                pos: position,
179                selection_set,
180                variables: variable_definitions,
181            },
182
183            OperationDefinition::Mutation(ast::operation::Mutation {
184                directives,
185                name,
186                position,
187                selection_set,
188                variable_definitions,
189                ..
190            }) => {
191                let op_type = schema.mutation_type().ok_or_else(|| vec![
192                    OperationBuildError::NoMutationTypeDefinedInSchema
193                ])?;
194
195                LoadFromAstDetails {
196                    directives,
197                    name: name.as_ref(),
198                    op_kind: OperationKind::Mutation,
199                    op_type,
200                    pos: position,
201                    selection_set,
202                    variables: variable_definitions,
203                }
204            },
205
206            OperationDefinition::Subscription(ast::operation::Subscription {
207                directives,
208                name,
209                position,
210                selection_set,
211                variable_definitions,
212                ..
213            }) => {
214                let op_type = schema.subscription_type().ok_or_else(|| vec![
215                    OperationBuildError::NoSubscriptionTypeDefinedInSchema
216                ])?;
217
218                LoadFromAstDetails {
219                    directives,
220                    name: name.as_ref(),
221                    op_kind: OperationKind::Subscription,
222                    op_type,
223                    pos: position,
224                    selection_set,
225                    variables: variable_definitions,
226                }
227            },
228        };
229
230        let opdef_srcloc = loc::SourceLocation::from_execdoc_ast_position(
231            file_path,
232            ast_details.pos,
233        );
234
235        let mut errors = vec![];
236
237        let directives = DirectiveAnnotationBuilder::from_ast(
238            &opdef_srcloc,
239            ast_details.directives,
240        );
241
242        let mut variables = IndexMap::<String, Variable>::new();
243        for ast_var_def in ast_details.variables {
244            let var_name = ast_var_def.name.to_string();
245            let vardef_srcloc =
246                opdef_srcloc.with_ast_position(&ast_var_def.position);
247            let type_ref = TypeAnnotation::from_ast_type(
248                &vardef_srcloc.to_owned(),
249                &ast_var_def.var_type,
250            );
251
252            if let Some(var_def) = variables.get(var_name.as_str()) {
253                errors.push(OperationBuildError::DuplicateVariableName {
254                    variable_definition1: var_def.def_location().to_owned(),
255                    variable_definition2: vardef_srcloc,
256                    variable_name: var_name,
257                });
258                continue
259            }
260
261            // Ensure the inner named type reference is a valid type within the
262            // schema.
263            let inner_named_type_is_valid =
264                type_ref.inner_named_type_ref()
265                    .deref(schema)
266                    .map_err(|err| match err {
267                        DerefByNameError::DanglingReference(var_name)
268                            => OperationBuildError::UndefinedVariableType {
269                                variable_name: var_name,
270                                location: vardef_srcloc.to_owned(),
271                            },
272                    });
273            if let Err(e) = inner_named_type_is_valid {
274                errors.push(e);
275                continue
276            }
277
278            let default_value =
279                ast_var_def.default_value.as_ref().map(|val| {
280                    Value::from_ast(val, &loc::SourceLocation::from_execdoc_ast_position(
281                        file_path,
282                        &ast_var_def.position,
283                    ))
284                });
285
286            variables.insert(ast_var_def.name.to_string(), Variable {
287                default_value,
288                name: ast_var_def.name.to_string(),
289                type_: TypeAnnotation::from_ast_type(
290                    &vardef_srcloc,
291                    &ast_var_def.var_type,
292                ),
293                def_location: vardef_srcloc,
294            });
295        }
296
297        let maybe_selection_set_builder =
298            SelectionSetBuilder::from_ast(
299                schema,
300                fragment_registry,
301                ast_details.op_type,
302                ast_details.selection_set,
303                file_path,
304            );
305
306        let selection_set_builder = match maybe_selection_set_builder {
307            Ok(selection_set_builder) => selection_set_builder,
308            Err(selection_set_build_errors) => {
309                errors.push(selection_set_build_errors.into());
310                return Err(errors);
311            },
312        };
313
314        if !errors.is_empty() {
315            return Err(errors);
316        }
317
318        Ok(Self {
319            def_location: Some(opdef_srcloc),
320            directives,
321            fragment_registry,
322            name: ast_details.name.map(|s| s.to_string()),
323            operation_kind: Some(ast_details.op_kind),
324            schema,
325            selection_set_builder,
326            variables,
327        })
328    }
329
330    /// Produce a [`OperationBuilder`] from a file on disk that whose contents
331    /// contain an
332    /// [executable document](https://spec.graphql.org/October2021/#ExecutableDocument)
333    /// with only a single query defined in it.
334    ///
335    /// If multiple operations are defined in the document, an error will be
336    /// returned. For cases where multiple operations may be defined in a single
337    /// document, use
338    /// [`ExecutableDocumentBuilder`](crate::operation::ExecutableDocumentBuilder).
339    ///
340    /// If the document contents include any fragment definitions, an error will
341    /// be returned. For cases where operations and fragments may be defined
342    /// together in a single document, use
343    /// ['ExecutableDocumentBuilder`](crate::operation::ExecutableDocumentBuilder).
344    pub fn from_file(
345        schema: &'schema Schema,
346        fragment_registry: &'fragreg FragmentRegistry<'schema>,
347        file_path: impl AsRef<Path>,
348    ) -> Result<Self> {
349        let file_path = file_path.as_ref();
350        let file_content = file_reader::read_content(file_path)
351            .map_err(|e| vec![
352                OperationBuildError::OperationFileReadError(Box::new(e)),
353            ])?;
354        Self::from_str(schema, fragment_registry, file_content, Some(file_path))
355    }
356
357    /// Produce a [`OperationBuilder`] from a string whose contents contain a
358    /// [document](https://spec.graphql.org/October2021/#sec-Document) with only
359    /// a single query defined in it.
360    ///
361    /// If multiple operations are defined in the document, an error will be
362    /// returned. For cases where multiple operations may be defined in a single
363    /// document, use
364    /// [`ExecutableDocumentBuilder`](crate::operation::ExecutableDocumentBuilder).
365    ///
366    /// If the document contents include any fragment definitions, an error will
367    /// be returned. For cases where operations and fragments may be defined
368    /// together in a single document, use
369    /// ['ExecutableDocumentBuilder`](crate::operation::ExecutableDocumentBuilder).
370    pub fn from_str(
371        schema: &'schema Schema,
372        fragment_registry: &'fragreg FragmentRegistry<'schema>,
373        content: impl AsRef<str>,
374        file_path: Option<&Path>,
375    ) -> Result<Self> {
376        let ast_doc =
377            ast::operation::parse(content.as_ref())
378                .map_err(|e| vec![e.into()])?;
379        let op_def =
380            if ast_doc.definitions.len() > 1 {
381                let mut op_count = 0;
382                let mut frag_count = 0;
383                for def in ast_doc.definitions {
384                    match def {
385                        ast::operation::Definition::Operation(_) =>
386                            op_count += 1,
387                        ast::operation::Definition::Fragment(_) =>
388                            frag_count += 1,
389                    }
390                }
391
392                if frag_count > 0 {
393                    return Err(vec![
394                        OperationBuildError::SchemaDeclarationsFoundInExecutableDocument
395                    ]);
396                } else {
397                    return Err(vec![
398                        OperationBuildError::MultipleOperationsInExecutableDocument {
399                            num_operations_found: op_count,
400                        }
401                    ]);
402                }
403            } else if let Some(op_def) = ast_doc.definitions.first() {
404                match op_def {
405                    ast::operation::Definition::Operation(op_def)
406                        => op_def,
407                    ast::operation::Definition::Fragment(_)
408                        => return Err(vec![
409                            OperationBuildError::SchemaDeclarationsFoundInExecutableDocument,
410                        ]),
411                }
412            } else {
413                return Err(vec![
414                    OperationBuildError::NoOperationsFoundInExecutableDocument,
415                ]);
416            };
417
418        Self::from_ast(schema, fragment_registry, op_def, file_path)
419    }
420
421    pub fn new(
422        schema: &'schema Schema,
423        fragment_registry: &'fragreg FragmentRegistry<'schema>,
424    ) -> OperationBuilder<'schema, 'fragreg> {
425        Self {
426            def_location: None,
427            directives: vec![],
428            fragment_registry,
429            name: None,
430            operation_kind: None,
431            schema,
432            selection_set_builder: SelectionSetBuilder::new(
433                schema,
434                fragment_registry,
435            ),
436            variables: IndexMap::new(),
437        }
438    }
439
440    /// Set the list of [`DirectiveAnnotation`]s.
441    ///
442    /// NOTE: If any previous directives were added (either using this function
443    /// or [`OperationBuilder::add_directive()`]), they will be fully replaced by
444    /// the [`DirectiveAnnotation`]s passed here.
445    pub fn set_directives(
446        mut self,
447        directives: &[DirectiveAnnotation],
448    ) -> Result<Self> {
449        self.directives = directives.into();
450        Ok(self)
451    }
452
453    /// Set the name of the [`Operation`].
454    pub fn set_name(mut self, name: Option<String>) -> Result<Self> {
455        self.name = name;
456        Ok(self)
457    }
458
459    /// Set the list of [`Variable`]s.
460    ///
461    /// NOTE: If any previous variables were added (either using this function
462    /// or [`OperationBuilder::add_variable`]), they will be fully replaced by the
463    /// collection of variables passed here.
464    pub fn set_variables(mut self, variables: Vec<Variable>) -> Result<Self> {
465        self.variables =
466            variables.into_iter()
467                .map(|var| (var.name.to_owned(), var))
468                .collect();
469        Ok(self)
470    }
471}
472
473#[derive(Clone, Debug, Error)]
474pub enum OperationBuildError {
475    #[error("No operation type specified.")]
476    AmbiguousOperationKind {
477        operation_name: Option<String>,
478    },
479
480    #[error("Found multiple directive arguments with the same name.")]
481    DuplicateDirectiveArgument {
482        argument_name: String,
483        loc1: loc::FilePosition,
484        loc2: loc::FilePosition,
485    },
486
487    #[error("Found multiple variables defined with the same name on this operation")]
488    DuplicateVariableName {
489        variable_definition1: loc::SourceLocation,
490        variable_definition2: loc::SourceLocation,
491        variable_name: String,
492    },
493
494    #[error("Found multiple arguments for the same parameter on a field in this query")]
495    DuplicateFieldArgument {
496        argument_name: String,
497        location1: loc::FilePosition,
498        location2: loc::FilePosition,
499    },
500
501    #[error(
502        "Found multiple operations in document. If this was expected, consider \
503        using ExecutableDocumentBuilder instead.",
504    )]
505    MultipleOperationsInExecutableDocument {
506        num_operations_found: i16,
507    },
508
509    #[error("No operations found in document.")]
510    NoOperationsFoundInExecutableDocument,
511
512    #[error("No Mutation type defined on this schema")]
513    NoMutationTypeDefinedInSchema,
514
515    #[error("No Subscription type defined on this schema")]
516    NoSubscriptionTypeDefinedInSchema,
517
518    #[error("Failure while trying to read a schema file from disk")]
519    OperationFileReadError(Box<file_reader::ReadContentError>),
520
521    #[error("Error parsing operation document: $0")]
522    ParseError(Arc<ast::operation::ParseError>),
523
524    #[error("Non-operations found in document.")]
525    SchemaDeclarationsFoundInExecutableDocument,
526
527    #[error("Failure to build the selection set for this operation: $0")]
528    SelectionSetBuildErrors(Vec<SelectionSetBuildError>),
529
530    #[error("Named type is not defined in the schema for this query")]
531    UndefinedVariableType {
532        location: loc::SourceLocation,
533        variable_name: String,
534    },
535}
536impl std::convert::From<Vec<SelectionSetBuildError>> for OperationBuildError {
537    fn from(value: Vec<SelectionSetBuildError>) -> Self {
538        Self::SelectionSetBuildErrors(value)
539    }
540}
541impl std::convert::From<ast::operation::ParseError> for OperationBuildError {
542    fn from(value: ast::operation::ParseError) -> Self {
543        Self::ParseError(Arc::new(value))
544    }
545}