libgraphql_core/operation/
selection_set_builder.rs1use 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 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}