apollo_compiler/executable/
from_ast.rs1use super::*;
2use crate::ty;
3use std::sync::Arc;
4
5pub(crate) struct BuildErrors<'a> {
6 pub(crate) errors: &'a mut DiagnosticList,
7 pub(crate) path: SelectionPath,
8}
9
10pub struct ExecutableDocumentBuilder<'schema, 'errors> {
36 pub(crate) document: ExecutableDocument,
38 schema: Option<&'schema Schema>,
40 pub(crate) errors: &'errors mut DiagnosticList,
42 multiple_anonymous: bool,
44}
45
46impl<'schema, 'errors> ExecutableDocumentBuilder<'schema, 'errors> {
47 pub fn new(schema: Option<&'schema Schema>, errors: &'errors mut DiagnosticList) -> Self {
49 Self {
50 document: ExecutableDocument::new(),
51 schema,
52 errors,
53 multiple_anonymous: false,
54 }
55 }
56
57 pub fn parse(
63 mut self,
64 source_text: impl Into<String>,
65 path: impl AsRef<std::path::Path>,
66 ) -> Self {
67 Parser::new().parse_into_executable_builder(source_text, path, &mut self);
68 self
69 }
70
71 pub fn add_ast_document(
73 &mut self,
74 document: &ast::Document,
75 type_system_definitions_are_errors: bool,
76 ) {
77 Arc::make_mut(&mut self.errors.sources)
78 .extend(document.sources.iter().map(|(k, v)| (*k, v.clone())));
79 self.add_ast_document_not_adding_sources(document, type_system_definitions_are_errors);
80 }
81
82 pub(crate) fn add_ast_document_not_adding_sources(
83 &mut self,
84 document: &ast::Document,
85 type_system_definitions_are_errors: bool,
86 ) {
87 let mut errors = BuildErrors {
88 errors: self.errors,
89 path: SelectionPath {
90 nested_fields: Vec::new(),
91 root: ExecutableDefinitionName::AnonymousOperation(ast::OperationType::Query),
93 },
94 };
95
96 for definition in &document.definitions {
97 debug_assert!(errors.path.nested_fields.is_empty());
98 match definition {
99 ast::Definition::OperationDefinition(operation) => {
100 if let Some(name) = &operation.name {
101 if let Some(anonymous) = &self.document.operations.anonymous {
102 errors.errors.push(
103 anonymous.location(),
104 BuildError::AmbiguousAnonymousOperation,
105 )
106 }
107 if let Entry::Vacant(entry) =
108 self.document.operations.named.entry(name.clone())
109 {
110 errors.path.root = ExecutableDefinitionName::NamedOperation(
111 operation.operation_type,
112 name.clone(),
113 );
114 if let Some(op) =
115 Operation::from_ast(self.schema, &mut errors, operation)
116 {
117 entry.insert(operation.same_location(op));
118 } else {
119 errors.errors.push(
120 operation.location(),
121 BuildError::UndefinedRootOperation {
122 operation_type: operation.operation_type.name(),
123 },
124 )
125 }
126 } else {
127 let (key, _) =
128 self.document.operations.named.get_key_value(name).unwrap();
129 errors.errors.push(
130 name.location(),
131 BuildError::OperationNameCollision {
132 name_at_previous_location: key.clone(),
133 },
134 );
135 }
136 } else if let Some(previous) = &self.document.operations.anonymous {
137 if !self.multiple_anonymous {
138 self.multiple_anonymous = true;
139 errors
140 .errors
141 .push(previous.location(), BuildError::AmbiguousAnonymousOperation)
142 }
143 errors.errors.push(
144 operation.location(),
145 BuildError::AmbiguousAnonymousOperation,
146 )
147 } else if !self.document.operations.named.is_empty() {
148 errors.errors.push(
149 operation.location(),
150 BuildError::AmbiguousAnonymousOperation,
151 )
152 } else {
153 errors.path.root =
154 ExecutableDefinitionName::AnonymousOperation(operation.operation_type);
155 if let Some(op) = Operation::from_ast(self.schema, &mut errors, operation) {
156 self.document.operations.anonymous = Some(operation.same_location(op));
157 } else {
158 errors.errors.push(
159 operation.location(),
160 BuildError::UndefinedRootOperation {
161 operation_type: operation.operation_type.name(),
162 },
163 )
164 }
165 }
166 }
167 ast::Definition::FragmentDefinition(fragment) => {
168 if let Entry::Vacant(entry) =
169 self.document.fragments.entry(fragment.name.clone())
170 {
171 errors.path.root =
172 ExecutableDefinitionName::Fragment(fragment.name.clone());
173 if let Some(node) = Fragment::from_ast(self.schema, &mut errors, fragment) {
174 entry.insert(fragment.same_location(node));
175 }
176 } else {
177 let (key, _) = self
178 .document
179 .fragments
180 .get_key_value(&fragment.name)
181 .unwrap();
182 errors.errors.push(
183 fragment.name.location(),
184 BuildError::FragmentNameCollision {
185 name_at_previous_location: key.clone(),
186 },
187 )
188 }
189 }
190 _ => {
191 if type_system_definitions_are_errors {
192 errors.errors.push(
193 definition.location(),
194 BuildError::TypeSystemDefinition {
195 name: definition.name().cloned(),
196 describe: definition.describe(),
197 },
198 )
199 }
200 }
201 }
202 }
203
204 Arc::make_mut(&mut self.document.sources)
205 .extend(document.sources.iter().map(|(k, v)| (*k, v.clone())));
206 }
207
208 pub fn build(self) -> ExecutableDocument {
210 self.build_inner()
211 }
212
213 pub(crate) fn build_inner(mut self) -> ExecutableDocument {
214 self.document.sources = self.errors.sources.clone();
215 self.document
216 }
217}
218
219pub(crate) fn document_from_ast(
220 schema: Option<&Schema>,
221 document: &ast::Document,
222 errors: &mut DiagnosticList,
223 type_system_definitions_are_errors: bool,
224) -> ExecutableDocument {
225 let mut builder = ExecutableDocumentBuilder::new(schema, errors);
226
227 builder.add_ast_document_not_adding_sources(document, type_system_definitions_are_errors);
228
229 let doc = builder.build_inner();
230
231 ExecutableDocument {
232 sources: document.sources.clone(),
233 operations: doc.operations,
234 fragments: doc.fragments,
235 }
236}
237
238impl Operation {
239 fn from_ast(
240 schema: Option<&Schema>,
241 errors: &mut BuildErrors,
242 ast: &ast::OperationDefinition,
243 ) -> Option<Self> {
244 let ty = if let Some(s) = schema {
245 s.root_operation(ast.operation_type)?.clone()
246 } else {
247 ast.operation_type.default_type_name().clone()
249 };
250 let mut selection_set = SelectionSet::new(ty);
251 selection_set.extend_from_ast(schema, errors, &ast.selection_set);
252 Some(Self {
253 operation_type: ast.operation_type,
254 name: ast.name.clone(),
255 variables: ast.variables.clone(),
256 directives: ast.directives.clone(),
257 selection_set,
258 })
259 }
260}
261
262impl Fragment {
263 fn from_ast(
264 schema: Option<&Schema>,
265 errors: &mut BuildErrors,
266 ast: &ast::FragmentDefinition,
267 ) -> Option<Self> {
268 if let Some(schema) = schema {
269 if !schema.types.contains_key(&ast.type_condition) {
270 errors.errors.push(
271 ast.type_condition.location(),
272 BuildError::UndefinedTypeInNamedFragmentTypeCondition {
273 type_name: ast.type_condition.clone(),
274 fragment_name: ast.name.clone(),
275 },
276 );
277 return None;
278 }
279 }
280 let mut selection_set = SelectionSet::new(ast.type_condition.clone());
281 selection_set.extend_from_ast(schema, errors, &ast.selection_set);
282 Some(Self {
283 name: ast.name.clone(),
284 directives: ast.directives.clone(),
285 selection_set,
286 })
287 }
288}
289
290impl SelectionSet {
291 pub(crate) fn extend_from_ast(
292 &mut self,
293 schema: Option<&Schema>,
294 errors: &mut BuildErrors,
295 ast_selections: &[ast::Selection],
296 ) {
297 for selection in ast_selections {
298 match selection {
299 ast::Selection::Field(ast) => {
300 let field_def_result = if let Some(s) = schema {
301 s.type_field(&self.ty, &ast.name).map(|c| c.node.clone())
302 } else {
303 Ok(Node::new(ast::FieldDefinition {
304 description: None,
305 name: ast.name.clone(),
306 arguments: Vec::new(),
307 ty: ty!(UNKNOWN),
308 directives: Default::default(),
309 }))
310 };
311 errors
312 .path
313 .nested_fields
314 .push(ast.alias.clone().unwrap_or_else(|| ast.name.clone()));
315 match field_def_result {
316 Ok(field_def) => {
317 let leaf = ast.selection_set.is_empty();
318 let type_name = field_def.ty.inner_named_type();
319 match schema
320 .as_ref()
321 .and_then(|schema| schema.types.get(type_name))
322 {
323 Some(schema::ExtendedType::Scalar(_)) if !leaf => {
324 errors.errors.push(
325 ast.location(),
326 BuildError::SubselectionOnScalarType {
327 type_name: type_name.clone(),
328 path: errors.path.clone(),
329 },
330 )
331 }
332 Some(schema::ExtendedType::Enum(_)) if !leaf => errors.errors.push(
333 ast.location(),
334 BuildError::SubselectionOnEnumType {
335 type_name: type_name.clone(),
336 path: errors.path.clone(),
337 },
338 ),
339 _ => self.push(
340 ast.same_location(
341 Field::new(ast.name.clone(), field_def)
342 .with_opt_alias(ast.alias.clone())
343 .with_arguments(ast.arguments.iter().cloned())
344 .with_directives(ast.directives.iter().cloned())
345 .with_ast_selections(
346 schema,
347 errors,
348 &ast.selection_set,
349 ),
350 ),
351 ),
352 }
353 }
354 Err(schema::FieldLookupError::NoSuchField(type_name, _)) => {
355 errors.errors.push(
356 ast.name.location(),
357 BuildError::UndefinedField {
358 type_name: type_name.clone(),
359 field_name: ast.name.clone(),
360 path: errors.path.clone(),
361 },
362 )
363 }
364 Err(schema::FieldLookupError::NoSuchType) => {
365 }
374 }
375 errors.path.nested_fields.pop();
376 }
377 ast::Selection::FragmentSpread(ast) => self.push(
378 ast.same_location(
379 self.new_fragment_spread(ast.fragment_name.clone())
380 .with_directives(ast.directives.iter().cloned()),
381 ),
382 ),
383 ast::Selection::InlineFragment(ast) => {
384 let opt_type_condition = ast.type_condition.clone();
385 match (&opt_type_condition, schema) {
386 (Some(type_condition), Some(schema))
387 if !schema.types.contains_key(type_condition) =>
388 {
389 errors.errors.push(
390 type_condition.location(),
391 BuildError::UndefinedTypeInInlineFragmentTypeCondition {
392 type_name: type_condition.clone(),
393 path: errors.path.clone(),
394 },
395 )
396 }
397 _ => self.push(
398 ast.same_location(
399 self.new_inline_fragment(opt_type_condition)
400 .with_directives(ast.directives.iter().cloned())
401 .with_ast_selections(schema, errors, &ast.selection_set),
402 ),
403 ),
404 }
405 }
406 }
407 }
408 }
409}
410
411impl Field {
412 fn with_ast_selections(
413 mut self,
414 schema: Option<&Schema>,
415 errors: &mut BuildErrors,
416 ast_selections: &[ast::Selection],
417 ) -> Self {
418 self.selection_set
419 .extend_from_ast(schema, errors, ast_selections);
420 self
421 }
422}
423
424impl InlineFragment {
425 fn with_ast_selections(
426 mut self,
427 schema: Option<&Schema>,
428 errors: &mut BuildErrors,
429 ast_selections: &[ast::Selection],
430 ) -> Self {
431 self.selection_set
432 .extend_from_ast(schema, errors, ast_selections);
433 self
434 }
435}