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 pub fn add_directive(
65 mut self,
66 annot: DirectiveAnnotation,
67 ) -> Result<Self> {
68 self.directives.push(annot);
70 Ok(self)
71 }
72
73 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 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 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 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 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 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 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 pub fn set_directives(
446 mut self,
447 directives: &[DirectiveAnnotation],
448 ) -> Result<Self> {
449 self.directives = directives.into();
450 Ok(self)
451 }
452
453 pub fn set_name(mut self, name: Option<String>) -> Result<Self> {
455 self.name = name;
456 Ok(self)
457 }
458
459 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}