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