libgraphql_core/operation/
selection_set_builder.rs

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