libgraphql_core/operation/
selection_set_builder.rs

1use crate::ast;
2use crate::ast::operation::TypeCondition;
3use crate::loc::SourceLocation;
4use crate::operation::SelectionSet;
5use crate::types::GraphQLTypeKind;
6use crate::DirectiveAnnotationBuilder;
7use crate::loc;
8use crate::named_ref::DerefByName;
9use crate::operation::FieldSelection;
10use crate::operation::Fragment;
11use crate::operation::FragmentRegistry;
12use crate::operation::FragmentSpread;
13use crate::operation::InlineFragment;
14use crate::operation::Selection;
15use crate::schema::Schema;
16use crate::types::GraphQLType;
17use crate::Value;
18use indexmap::IndexMap;
19use std::path::Path;
20use std::sync::Arc;
21use thiserror::Error;
22
23type Result<T> = std::result::Result<T, Vec<SelectionSetBuildError>>;
24
25#[derive(Clone, Debug, PartialEq)]
26pub struct SelectionSetBuilder<'schema: 'fragreg, 'fragreg> {
27    fragment_registry: &'fragreg FragmentRegistry<'schema>,
28    schema: &'schema Schema,
29    selections: Vec<Selection<'schema>>,
30}
31impl<'schema: 'fragreg, 'fragreg> SelectionSetBuilder<'schema, 'fragreg> {
32    pub fn add_selection(
33        mut self,
34        selection: Selection<'schema>,
35    ) -> Result<Self> {
36        self.selections.push(selection);
37        Ok(self)
38    }
39
40    pub fn build(self) -> Result<SelectionSet<'schema>> {
41        Ok(SelectionSet {
42            selections: self.selections,
43            schema: self.schema,
44        })
45    }
46
47    pub fn from_ast(
48        schema: &'schema Schema,
49        fragment_registry: &'fragreg FragmentRegistry<'schema>,
50        parent_type: &'schema GraphQLType,
51        ast: &ast::operation::SelectionSet,
52        file_path: Option<&Path>,
53    ) -> Result<Self> {
54        let parent_fields = match parent_type {
55            GraphQLType::Interface(iface_t) => iface_t.fields(),
56            GraphQLType::Object(obj_t) => obj_t.fields(),
57            _ => return Err(vec![
58                SelectionSetBuildError::UnselectableFieldType {
59                    location: loc::SourceLocation::from_execdoc_ast_position(
60                        file_path,
61                        &ast.span.0,
62                    ),
63                    parent_type_kind: parent_type.type_kind().to_owned(),
64                    parent_type_name: parent_type.name().to_string(),
65                }
66            ]),
67        };
68
69        let mut errors = vec![];
70        let mut selections = vec![];
71        for ast_selection in &ast.items {
72            // TODO: Need to assert that all field selections are unambiguously unique.
73            //
74            //       E.g. There cannot be 2 fields with the same selection-name same set of
75            //       argument names/argument types.
76            selections.push(match ast_selection {
77                ast::operation::Selection::Field(
78                    ast::operation::Field {
79                        alias,
80                        arguments: ast_arguments,
81                        directives: selected_field_ast_directives,
82                        name: field_name,
83                        position: selected_field_ast_position,
84                        selection_set: ast_sub_selection_set,
85                    }
86                ) => {
87                    let selected_field_srcloc = loc::SourceLocation::from_execdoc_ast_position(
88                        file_path,
89                        selected_field_ast_position,
90                    );
91
92                    let selected_field = match parent_fields.get(field_name) {
93                        Some(field) => field,
94                        None => {
95                            errors.push(
96                                SelectionSetBuildError::UndefinedFieldName {
97                                    location: selected_field_srcloc,
98                                    parent_type_name: parent_type.name().to_string(),
99                                    undefined_field_name: field_name.to_string(),
100                                }
101                            );
102                            continue
103                        }
104                    };
105
106                    let mut arguments = IndexMap::new();
107                    for (arg_name, ast_arg_value) in ast_arguments {
108                        if arguments.insert(
109                            arg_name.to_string(),
110                            Value::from_ast(
111                                ast_arg_value,
112                                &selected_field_srcloc,
113                            ),
114                        ).is_some() {
115                            errors.push(SelectionSetBuildError::DuplicateFieldArgument {
116                                argument_name: arg_name.to_string(),
117                                location1: selected_field_srcloc.to_owned(),
118                                location2: selected_field_srcloc.to_owned(),
119                            });
120                            continue
121                        }
122                    }
123
124                    let directives = DirectiveAnnotationBuilder::from_ast(
125                        &selected_field_srcloc,
126                        selected_field_ast_directives,
127                    );
128
129                    let selection_set =
130                        if ast_sub_selection_set.items.is_empty() {
131                            None
132                        } else {
133                            let maybe_selection_set = Self::from_ast(
134                                schema,
135                                fragment_registry,
136                                selected_field.type_annotation()
137                                    .innermost_named_type_annotation()
138                                    .graphql_type(schema),
139                                ast_sub_selection_set,
140                                file_path,
141                            ).and_then(|builder| builder.build());
142
143                            match maybe_selection_set {
144                                Ok(selection_set) => Some(selection_set),
145                                Err(mut ss_errors) => {
146                                    errors.append(&mut ss_errors);
147                                    continue;
148                                }
149                            }
150                        };
151
152                    Selection::Field(FieldSelection {
153                        alias: alias.clone(),
154                        arguments,
155                        def_location: selected_field_srcloc,
156                        directives,
157                        field: selected_field,
158                        schema,
159                        selection_set,
160                    })
161                },
162
163                ast::operation::Selection::FragmentSpread(
164                    ast::operation::FragmentSpread {
165                        directives: ast_directives,
166                        fragment_name,
167                        position: ast_fragspread_position,
168                    }
169                ) => {
170                    let fragspread_srcloc = loc::SourceLocation::from_execdoc_ast_position(
171                        file_path,
172                        ast_fragspread_position,
173                    );
174
175                    let directives = DirectiveAnnotationBuilder::from_ast(
176                        &fragspread_srcloc,
177                        ast_directives,
178                    );
179
180                    Selection::FragmentSpread(FragmentSpread {
181                        def_location: fragspread_srcloc.to_owned(),
182                        directives,
183                        fragment_ref: Fragment::named_ref(
184                            fragment_name.as_str(),
185                            fragspread_srcloc,
186                        ),
187                    })
188                },
189
190                ast::operation::Selection::InlineFragment(
191                    ast::operation::InlineFragment {
192                        directives: ast_inlinespread_directives,
193                        position: ast_inlinespread_position,
194                        selection_set: ast_sub_selection_set,
195                        type_condition: ast_type_condition,
196                    }
197                ) => {
198                    let inlinespread_srcloc = loc::SourceLocation::from_execdoc_ast_position(
199                        file_path,
200                        ast_inlinespread_position,
201                    );
202
203                    let directives = DirectiveAnnotationBuilder::from_ast(
204                        &inlinespread_srcloc,
205                        ast_inlinespread_directives,
206                    );
207
208                    let parent_type =
209                        if let Some(TypeCondition::On(
210                            typecond_type_name
211                        )) = ast_type_condition {
212                            let typecond_type =
213                                schema.all_types().get(typecond_type_name);
214
215                            let typecond_type =
216                                if let Some(typecond_type) = typecond_type {
217                                    typecond_type
218                                } else {
219                                    errors.push(
220                                        SelectionSetBuildError::UndefinedTypeQualifierType {
221                                            undefined_type_name: typecond_type_name.to_string(),
222                                            type_qualifier_location: inlinespread_srcloc,
223                                        }
224                                    );
225                                    continue;
226                                };
227
228                            let is_valid_qualifying_type = match parent_type {
229                                GraphQLType::Bool
230                                    | GraphQLType::Enum(_)
231                                    | GraphQLType::Float
232                                    | GraphQLType::ID
233                                    | GraphQLType::InputObject(_)
234                                    | GraphQLType::Int
235                                    | GraphQLType::Scalar(_)
236                                    | GraphQLType::String
237                                    => false,
238
239                                GraphQLType::Interface(parent_iface)
240                                    => typecond_type.implements_interface(schema, parent_iface),
241
242                                GraphQLType::Object(_)
243                                    => parent_type == typecond_type,
244
245                                GraphQLType::Union(parent_union) =>
246                                    if let GraphQLType::Interface(
247                                        typecond_iface
248                                    ) = typecond_type {
249                                        parent_union.implements_interface(schema, typecond_iface)
250                                    } else {
251                                        parent_union.contains_member(typecond_type)
252                                    },
253                            };
254
255                            if !is_valid_qualifying_type {
256                                errors.push(
257                                    SelectionSetBuildError::InvalidQualifyingType {
258                                        invalid_qualifying_type_name: typecond_type_name.to_string(),
259                                        parent_type_name: parent_type.name().to_string(),
260                                        qualifier_location: inlinespread_srcloc,
261                                    }
262                                );
263                                continue;
264                            }
265
266                            typecond_type
267                        } else {
268                            parent_type
269                        };
270
271                    let maybe_selection_set = SelectionSetBuilder::from_ast(
272                        schema,
273                        fragment_registry,
274                        parent_type,
275                        ast_sub_selection_set,
276                        file_path,
277                    ).and_then(|builder| builder.build());
278
279                    let selection_set = match maybe_selection_set {
280                        Ok(selection_set) => selection_set,
281                        Err(mut ss_errors) => {
282                            errors.append(&mut ss_errors);
283                            continue;
284                        }
285                    };
286
287                    Selection::InlineFragment(InlineFragment {
288                        directives,
289                        selection_set,
290                        type_condition: ast_type_condition.clone().map(
291                            |ast_type_cond| match ast_type_cond {
292                                ast::operation::TypeCondition::On(type_name) =>
293                                    GraphQLType::named_ref(
294                                        type_name.as_str(),
295                                        inlinespread_srcloc.with_ast_position(ast_inlinespread_position),
296                                    ),
297                            }
298                        ),
299                        def_location: inlinespread_srcloc,
300                    })
301                },
302            })
303        }
304
305        Ok(SelectionSetBuilder {
306            fragment_registry,
307            schema,
308            selections,
309        })
310    }
311
312    pub fn from_str(
313        schema: &'schema Schema,
314        fragment_registry: &'fragreg FragmentRegistry<'schema>,
315        parent_type: &'schema GraphQLType,
316        content: impl AsRef<str>,
317        file_path: Option<&Path>,
318    ) -> Result<Self> {
319        let doc_ast = ast::operation::parse(content.as_ref())
320            .map_err(|e| vec![e.into()])?;
321
322        let num_defs = doc_ast.definitions.len();
323        if num_defs != 1 {
324            return Err(vec![
325                SelectionSetBuildError::StringIsNotASelectionSet
326            ]);
327        }
328
329        let selection_set_ast = match doc_ast.definitions.first() {
330            Some(ast::operation::Definition::Operation(
331                ast::operation::OperationDefinition::SelectionSet(ast)
332            )) => ast,
333
334            _ => return Err(vec![
335                SelectionSetBuildError::StringIsNotASelectionSet,
336            ]),
337        };
338
339        Self::from_ast(
340            schema,
341            fragment_registry,
342            parent_type,
343            selection_set_ast,
344            file_path,
345        )
346    }
347
348    pub fn new(
349        schema: &'schema Schema,
350        fragment_registry: &'fragreg FragmentRegistry<'schema>,
351    ) -> Self {
352        Self {
353            fragment_registry,
354            schema,
355            selections: vec![],
356        }
357    }
358}
359
360#[derive(Clone, Debug, Error)]
361pub enum SelectionSetBuildError {
362    #[error("Multiple fields selected with the same name")]
363    DuplicateFieldArgument {
364        argument_name: String,
365        location1: loc::SourceLocation,
366        location2: loc::SourceLocation,
367    },
368
369    #[error(
370        "Attempted to selected a type-qualified set of fields using an \
371        invalid type. `{invalid_qualifying_type_name}` is not a subtype \
372        of `{parent_type_name}`."
373    )]
374    InvalidQualifyingType {
375        invalid_qualifying_type_name: String,
376        parent_type_name: String,
377        qualifier_location: SourceLocation,
378    },
379
380    #[error("Error parsing SelectionSet from string: $0")]
381    ParseError(Arc<ast::operation::ParseError>),
382
383    #[error("The string provided is not a selection set")]
384    StringIsNotASelectionSet,
385
386    #[error(
387        "Attempted to select a field named `{undefined_field_name}` on the \
388        `{parent_type_name}` type, but `{parent_type_name}` has no such field \
389        defined."
390    )]
391    UndefinedFieldName {
392        location: loc::SourceLocation,
393        parent_type_name: String,
394        undefined_field_name: String,
395    },
396
397    #[error(
398        "Attempted to select sub-fields on the `{parent_type_name}` type, but \
399        `{parent_type_name}` is neither an Object nor an Interface type."
400    )]
401    UnselectableFieldType {
402        location: loc::SourceLocation,
403        parent_type_kind: GraphQLTypeKind,
404        parent_type_name: String
405    },
406
407    #[error(
408        "Attempted to specify `... {undefined_type_name}`, but \
409        `{undefined_type_name}` is not a type defined in the schema."
410    )]
411    UndefinedTypeQualifierType {
412        undefined_type_name: String,
413        type_qualifier_location: SourceLocation,
414    },
415}
416impl std::convert::From<ast::operation::ParseError> for SelectionSetBuildError {
417    fn from(value: ast::operation::ParseError) -> Self {
418        Self::ParseError(Arc::new(value))
419    }
420}