gurkle_codegen/
query.rs

1//! The responsibility of this module is to bind and validate a query
2//! against a given schema.
3
4mod fragments;
5mod operations;
6mod selection;
7mod validation;
8
9pub(crate) use fragments::{fragment_is_recursive, ResolvedFragment};
10pub(crate) use operations::{OperationType, ResolvedOperation};
11pub(crate) use selection::*;
12
13use crate::{
14    constants::TYPENAME_FIELD,
15    normalization::Normalization,
16    schema::{
17        resolve_field_type, EnumId, InputId, ScalarId, Schema, StoredEnum, StoredFieldType,
18        StoredInputType, StoredScalar, TypeId, UnionId,
19    },
20};
21use std::collections::{HashMap, HashSet};
22use thiserror::Error;
23
24#[derive(Debug, Error)]
25#[error("{}", message)]
26pub(crate) struct QueryValidationError {
27    message: String,
28}
29
30impl QueryValidationError {
31    pub(crate) fn new(message: String) -> Self {
32        QueryValidationError { message }
33    }
34}
35
36#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
37pub(crate) struct SelectionId(u32);
38#[derive(Debug, Clone, Copy, PartialEq)]
39pub(crate) struct OperationId(u32);
40
41impl OperationId {
42    pub(crate) fn new(idx: usize) -> Self {
43        OperationId(idx as u32)
44    }
45}
46
47#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
48pub(crate) struct ResolvedFragmentId(u32);
49
50#[derive(Debug, Clone, Copy)]
51pub(crate) struct VariableId(u32);
52
53pub(crate) fn resolve(
54    schema: &Schema,
55    query: &gurkle_parser::query::Document,
56) -> Result<Query, QueryValidationError> {
57    let mut resolved_query: Query = Default::default();
58
59    create_roots(&mut resolved_query, query, schema)?;
60
61    // Then resolve the selections.
62    for definition in &query.definitions {
63        match definition {
64            gurkle_parser::query::Definition::Fragment(fragment) => {
65                resolve_fragment(&mut resolved_query, schema, fragment)?
66            }
67            gurkle_parser::query::Definition::Operation(operation) => {
68                resolve_operation(&mut resolved_query, schema, operation)?
69            }
70        }
71    }
72
73    // Validation: to be expanded and factored out.
74    validation::validate_typename_presence(&BoundQuery {
75        query: &resolved_query,
76        schema,
77    })?;
78
79    for (selection_id, _) in resolved_query.selections() {
80        selection::validate_type_conditions(
81            selection_id,
82            &BoundQuery {
83                query: &resolved_query,
84                schema,
85            },
86        )?
87    }
88
89    Ok(resolved_query)
90}
91
92fn create_roots(
93    resolved_query: &mut Query,
94    query: &gurkle_parser::query::Document,
95    schema: &Schema,
96) -> Result<(), QueryValidationError> {
97    // First, give ids to all fragments and operations.
98    for definition in &query.definitions {
99        match definition {
100            gurkle_parser::query::Definition::Fragment(fragment) => {
101                let gurkle_parser::query::TypeCondition::On(on) = &fragment.type_condition;
102                resolved_query.fragments.push(ResolvedFragment {
103                    name: fragment.name.clone(),
104                    on: schema.find_type(on).ok_or_else(|| {
105                        QueryValidationError::new(format!(
106                            "Could not find type {} for fragment {} in schema.",
107                            on, fragment.name
108                        ))
109                    })?,
110                    selection_set: Vec::new(),
111                });
112            }
113            gurkle_parser::query::Definition::Operation(
114                gurkle_parser::query::OperationDefinition::Mutation(m),
115            ) => {
116                let on = schema.mutation_type().ok_or_else(|| {
117                    QueryValidationError::new(
118                        "Query contains a mutation operation, but the schema has no mutation type."
119                            .to_owned(),
120                    )
121                })?;
122                let resolved_operation: ResolvedOperation = ResolvedOperation {
123                    object_id: on,
124                    query_string: m.to_string(),
125                    name: m.name.as_ref().expect("mutation without name").to_owned(),
126                    operation_type: operations::OperationType::Mutation,
127                    selection_set: Vec::with_capacity(m.selection_set.items.len()),
128                };
129
130                resolved_query.operations.push(resolved_operation);
131            }
132            gurkle_parser::query::Definition::Operation(
133                gurkle_parser::query::OperationDefinition::Query(q),
134            ) => {
135                let on = schema.query_type();
136                let name = match q.name.as_ref() {
137                    Some(v) => v,
138                    None => {
139                        return Err(QueryValidationError::new(
140                            "Query was missing an operation name.".to_owned(),
141                        ));
142                    }
143                };
144
145                let resolved_operation: ResolvedOperation = ResolvedOperation {
146                    name: name.to_owned(),
147                    query_string: q.to_string(),
148                    operation_type: operations::OperationType::Query,
149                    object_id: on,
150                    selection_set: Vec::with_capacity(q.selection_set.items.len()),
151                };
152
153                resolved_query.operations.push(resolved_operation);
154            }
155            gurkle_parser::query::Definition::Operation(
156                gurkle_parser::query::OperationDefinition::Subscription(s),
157            ) => {
158                let on = schema.subscription_type().ok_or_else(|| {
159                    QueryValidationError::new(
160                        "Query contains a subscription operation, but the schema has no subscription type.".to_owned()
161                    )
162                })?;
163
164                if s.selection_set.items.len() != 1 {
165                    return Err(QueryValidationError::new(
166                        crate::constants::MULTIPLE_SUBSCRIPTION_FIELDS_ERROR.to_owned(),
167                    ));
168                }
169
170                let resolved_operation: ResolvedOperation = ResolvedOperation {
171                    name: s
172                        .name
173                        .as_ref()
174                        .expect("subscription without name")
175                        .to_owned(),
176                    query_string: s.to_string(),
177                    operation_type: operations::OperationType::Subscription,
178                    object_id: on,
179                    selection_set: Vec::with_capacity(s.selection_set.items.len()),
180                };
181
182                resolved_query.operations.push(resolved_operation);
183            }
184            gurkle_parser::query::Definition::Operation(
185                gurkle_parser::query::OperationDefinition::SelectionSet(_),
186            ) => {
187                return Err(QueryValidationError::new(
188                    crate::constants::SELECTION_SET_AT_ROOT.to_owned(),
189                ))
190            }
191        }
192    }
193
194    Ok(())
195}
196
197fn resolve_fragment(
198    query: &mut Query,
199    schema: &Schema,
200    fragment_definition: &gurkle_parser::query::FragmentDefinition,
201) -> Result<(), QueryValidationError> {
202    let gurkle_parser::query::TypeCondition::On(on) = &fragment_definition.type_condition;
203    let on = schema.find_type(&on).ok_or_else(|| {
204        QueryValidationError::new(format!(
205            "Could not find type `{}` referenced by fragment `{}`",
206            on, fragment_definition.name
207        ))
208    })?;
209
210    let (id, _) = query
211        .find_fragment(&fragment_definition.name)
212        .ok_or_else(|| {
213            QueryValidationError::new(format!(
214                "Could not find fragment `{}`.",
215                fragment_definition.name
216            ))
217        })?;
218
219    resolve_selection(
220        query,
221        on,
222        &fragment_definition.selection_set,
223        SelectionParent::Fragment(id),
224        schema,
225    )?;
226
227    Ok(())
228}
229
230fn resolve_union_selection(
231    query: &mut Query,
232    _union_id: UnionId,
233    selection_set: &gurkle_parser::query::SelectionSet,
234    parent: SelectionParent,
235    schema: &Schema,
236) -> Result<(), QueryValidationError> {
237    for item in selection_set.items.iter() {
238        match item {
239            gurkle_parser::query::Selection::Field(field) => {
240                if field.name == TYPENAME_FIELD {
241                    let id = query.push_selection(Selection::Typename, parent);
242                    parent.add_to_selection_set(query, id);
243                } else {
244                    return Err(QueryValidationError::new(format!(
245                        "Invalid field selection on union field ({:?})",
246                        parent
247                    )));
248                }
249            }
250            gurkle_parser::query::Selection::InlineFragment(inline_fragment) => {
251                let selection_id = resolve_inline_fragment(query, schema, inline_fragment, parent)?;
252                parent.add_to_selection_set(query, selection_id);
253            }
254            gurkle_parser::query::Selection::FragmentSpread(fragment_spread) => {
255                let (fragment_id, _fragment) = query
256                    .find_fragment(&fragment_spread.fragment_name)
257                    .ok_or_else(|| {
258                        QueryValidationError::new(format!(
259                            "Could not find fragment `{}` referenced by fragment spread.",
260                            fragment_spread.fragment_name
261                        ))
262                    })?;
263
264                let id = query.push_selection(Selection::FragmentSpread(fragment_id), parent);
265
266                parent.add_to_selection_set(query, id);
267            }
268        }
269    }
270
271    Ok(())
272}
273
274fn resolve_object_selection<'a>(
275    query: &mut Query,
276    object: &dyn crate::schema::ObjectLike,
277    selection_set: &gurkle_parser::query::SelectionSet,
278    parent: SelectionParent,
279    schema: &'a Schema,
280) -> Result<(), QueryValidationError> {
281    for item in selection_set.items.iter() {
282        match item {
283            gurkle_parser::query::Selection::Field(field) => {
284                if field.name == TYPENAME_FIELD {
285                    let id = query.push_selection(Selection::Typename, parent);
286                    parent.add_to_selection_set(query, id);
287                    continue;
288                }
289
290                let (field_id, schema_field) = object
291                    .get_field_by_name(&field.name, schema)
292                    .ok_or_else(|| {
293                        QueryValidationError::new(format!(
294                            "No field named {} on {}",
295                            &field.name,
296                            object.name()
297                        ))
298                    })?;
299
300                let id = query.push_selection(
301                    Selection::Field(SelectedField {
302                        alias: field.alias.clone(),
303                        field_id,
304                        selection_set: Vec::with_capacity(selection_set.items.len()),
305                    }),
306                    parent,
307                );
308
309                resolve_selection(
310                    query,
311                    schema_field.r#type.id,
312                    &field.selection_set,
313                    SelectionParent::Field(id),
314                    schema,
315                )?;
316
317                parent.add_to_selection_set(query, id);
318            }
319            gurkle_parser::query::Selection::InlineFragment(inline) => {
320                let selection_id = resolve_inline_fragment(query, schema, inline, parent)?;
321
322                parent.add_to_selection_set(query, selection_id);
323            }
324            gurkle_parser::query::Selection::FragmentSpread(fragment_spread) => {
325                let (fragment_id, _fragment) = query
326                    .find_fragment(&fragment_spread.fragment_name)
327                    .ok_or_else(|| {
328                        QueryValidationError::new(format!(
329                            "Could not find fragment `{}` referenced by fragment spread.",
330                            fragment_spread.fragment_name
331                        ))
332                    })?;
333
334                let id = query.push_selection(Selection::FragmentSpread(fragment_id), parent);
335
336                parent.add_to_selection_set(query, id);
337            }
338        }
339    }
340
341    Ok(())
342}
343
344fn resolve_selection(
345    ctx: &mut Query,
346    on: TypeId,
347    selection_set: &gurkle_parser::query::SelectionSet,
348    parent: SelectionParent,
349    schema: &Schema,
350) -> Result<(), QueryValidationError> {
351    match on {
352        TypeId::Object(oid) => {
353            let object = schema.get_object(oid);
354            resolve_object_selection(ctx, object, selection_set, parent, schema)?;
355        }
356        TypeId::Interface(interface_id) => {
357            let interface = schema.get_interface(interface_id);
358            resolve_object_selection(ctx, interface, selection_set, parent, schema)?;
359        }
360        TypeId::Union(union_id) => {
361            resolve_union_selection(ctx, union_id, selection_set, parent, schema)?;
362        }
363        other => {
364            if !selection_set.items.is_empty() {
365                return Err(QueryValidationError::new(format!(
366                    "Selection set on non-object, non-interface type. ({:?})",
367                    other
368                )));
369            }
370        }
371    };
372
373    Ok(())
374}
375
376fn resolve_inline_fragment(
377    query: &mut Query,
378    schema: &Schema,
379    inline_fragment: &gurkle_parser::query::InlineFragment,
380    parent: SelectionParent,
381) -> Result<SelectionId, QueryValidationError> {
382    let gurkle_parser::query::TypeCondition::On(on) = inline_fragment
383        .type_condition
384        .as_ref()
385        .expect("missing type condition on inline fragment");
386    let type_id = schema.find_type(on).ok_or_else(|| {
387        QueryValidationError::new(format!(
388            "Could not find type `{}` referenced by inline fragment.",
389            on
390        ))
391    })?;
392
393    let id = query.push_selection(
394        Selection::InlineFragment(InlineFragment {
395            type_id,
396            selection_set: Vec::with_capacity(inline_fragment.selection_set.items.len()),
397        }),
398        parent,
399    );
400
401    resolve_selection(
402        query,
403        type_id,
404        &inline_fragment.selection_set,
405        SelectionParent::InlineFragment(id),
406        schema,
407    )?;
408
409    Ok(id)
410}
411
412fn resolve_operation(
413    query: &mut Query,
414    schema: &Schema,
415    operation: &gurkle_parser::query::OperationDefinition,
416) -> Result<(), QueryValidationError> {
417    match operation {
418        gurkle_parser::query::OperationDefinition::Mutation(m) => {
419            let on = schema.mutation_type().ok_or_else(|| {
420                QueryValidationError::new(
421                    "Query contains a mutation operation, but the schema has no mutation type."
422                        .to_owned(),
423                )
424            })?;
425            let on = schema.get_object(on);
426
427            let (id, _) = query.find_operation(m.name.as_ref().unwrap()).unwrap();
428
429            resolve_variables(query, &m.variable_definitions, schema, id);
430            resolve_object_selection(
431                query,
432                on,
433                &m.selection_set,
434                SelectionParent::Operation(id),
435                schema,
436            )?;
437        }
438        gurkle_parser::query::OperationDefinition::Query(q) => {
439            let on = schema.get_object(schema.query_type());
440            let (id, _) = query.find_operation(q.name.as_ref().unwrap()).unwrap();
441
442            resolve_variables(query, &q.variable_definitions, schema, id);
443            resolve_object_selection(
444                query,
445                on,
446                &q.selection_set,
447                SelectionParent::Operation(id),
448                schema,
449            )?;
450        }
451        gurkle_parser::query::OperationDefinition::Subscription(s) => {
452            let on = schema.subscription_type().ok_or_else(|| QueryValidationError::new("Query contains a subscription operation, but the schema has no subscription type.".into()))?;
453            let on = schema.get_object(on);
454            let (id, _) = query.find_operation(s.name.as_ref().unwrap()).unwrap();
455
456            resolve_variables(query, &s.variable_definitions, schema, id);
457            resolve_object_selection(
458                query,
459                on,
460                &s.selection_set,
461                SelectionParent::Operation(id),
462                schema,
463            )?;
464        }
465        gurkle_parser::query::OperationDefinition::SelectionSet(_) => {
466            unreachable!("unnamed queries are not supported")
467        }
468    }
469
470    Ok(())
471}
472
473#[derive(Default)]
474pub(crate) struct Query {
475    fragments: Vec<ResolvedFragment>,
476    operations: Vec<ResolvedOperation>,
477    selection_parent_idx: HashMap<SelectionId, SelectionParent>,
478    selections: Vec<Selection>,
479    variables: Vec<ResolvedVariable>,
480}
481
482impl Query {
483    fn push_selection(&mut self, node: Selection, parent: SelectionParent) -> SelectionId {
484        let id = SelectionId(self.selections.len() as u32);
485        self.selections.push(node);
486
487        self.selection_parent_idx.insert(id, parent);
488
489        id
490    }
491
492    pub fn operations(&self) -> impl Iterator<Item = (OperationId, &ResolvedOperation)> {
493        walk_operations(self)
494    }
495
496    pub(crate) fn get_selection(&self, id: SelectionId) -> &Selection {
497        self.selections
498            .get(id.0 as usize)
499            .expect("Query.get_selection")
500    }
501
502    pub(crate) fn get_fragment(&self, id: ResolvedFragmentId) -> &ResolvedFragment {
503        self.fragments
504            .get(id.0 as usize)
505            .expect("Query.get_fragment")
506    }
507
508    pub(crate) fn get_operation(&self, id: OperationId) -> &ResolvedOperation {
509        self.operations
510            .get(id.0 as usize)
511            .expect("Query.get_operation")
512    }
513
514    /// Selects the first operation matching `struct_name`. Returns `None` when the query document defines no operation, or when the selected operation does not match any defined operation.
515    pub(crate) fn select_operation<'a>(
516        &'a self,
517        name: &str,
518        normalization: Normalization,
519    ) -> Option<(OperationId, &'a ResolvedOperation)> {
520        walk_operations(self).find(|(_id, op)| normalization.operation(&op.name) == name)
521    }
522
523    fn find_fragment(&mut self, name: &str) -> Option<(ResolvedFragmentId, &mut ResolvedFragment)> {
524        self.fragments
525            .iter_mut()
526            .enumerate()
527            .find(|(_, frag)| frag.name == name)
528            .map(|(id, f)| (ResolvedFragmentId(id as u32), f))
529    }
530
531    fn find_operation(&mut self, name: &str) -> Option<(OperationId, &mut ResolvedOperation)> {
532        self.operations
533            .iter_mut()
534            .enumerate()
535            .find(|(_, op)| op.name == name)
536            .map(|(id, op)| (OperationId::new(id), op))
537    }
538
539    fn selections(&self) -> impl Iterator<Item = (SelectionId, &Selection)> {
540        self.selections
541            .iter()
542            .enumerate()
543            .map(|(idx, selection)| (SelectionId(idx as u32), selection))
544    }
545
546    fn walk_selection_set<'a>(
547        &'a self,
548        selection_ids: &'a [SelectionId],
549    ) -> impl Iterator<Item = (SelectionId, &'a Selection)> + 'a {
550        selection_ids
551            .iter()
552            .map(move |id| (*id, self.get_selection(*id)))
553    }
554}
555
556#[derive(Debug)]
557pub(crate) struct ResolvedVariable {
558    pub(crate) operation_id: OperationId,
559    pub(crate) name: String,
560    pub(crate) default: Option<gurkle_parser::query::Value>,
561    pub(crate) r#type: StoredFieldType,
562}
563
564impl ResolvedVariable {
565    pub(crate) fn type_name<'schema>(&self, schema: &'schema Schema) -> &'schema str {
566        self.r#type.id.name(schema)
567    }
568
569    fn collect_used_types(&self, used_types: &mut UsedTypes, schema: &Schema) {
570        match self.r#type.id {
571            TypeId::Input(input_id) => {
572                used_types.types.insert(TypeId::Input(input_id));
573
574                let input = schema.get_input(input_id);
575
576                input.used_input_ids_recursive(used_types, schema)
577            }
578            type_id @ TypeId::Scalar(_) | type_id @ TypeId::Enum(_) => {
579                used_types.types.insert(type_id);
580            }
581            _ => (),
582        }
583    }
584}
585
586#[derive(Debug, Default)]
587pub(crate) struct UsedTypes {
588    pub(crate) types: HashSet<TypeId>,
589    fragments: HashSet<ResolvedFragmentId>,
590}
591
592impl UsedTypes {
593    pub(crate) fn inputs<'s, 'a: 's>(
594        &'s self,
595        schema: &'a Schema,
596    ) -> impl Iterator<Item = (InputId, &'a StoredInputType)> + 's {
597        schema
598            .inputs()
599            .filter(move |(id, _input)| self.types.contains(&TypeId::Input(*id)))
600    }
601
602    pub(crate) fn scalars<'s, 'a: 's>(
603        &'s self,
604        schema: &'a Schema,
605    ) -> impl Iterator<Item = (ScalarId, &'a StoredScalar)> + 's {
606        self.types
607            .iter()
608            .filter_map(TypeId::as_scalar_id)
609            .map(move |scalar_id| (scalar_id, schema.get_scalar(scalar_id)))
610            .filter(|(_id, scalar)| !crate::schema::DEFAULT_SCALARS.contains(&scalar.name.as_str()))
611    }
612
613    pub(crate) fn enums<'a, 'schema: 'a>(
614        &'a self,
615        schema: &'schema Schema,
616    ) -> impl Iterator<Item = (EnumId, &'schema StoredEnum)> + 'a {
617        self.types
618            .iter()
619            .filter_map(TypeId::as_enum_id)
620            .map(move |enum_id| (enum_id, schema.get_enum(enum_id)))
621    }
622
623    pub(crate) fn fragment_ids(&self) -> impl Iterator<Item = ResolvedFragmentId> + '_ {
624        self.fragments.iter().copied()
625    }
626}
627
628fn resolve_variables(
629    query: &mut Query,
630    variables: &[gurkle_parser::query::VariableDefinition],
631    schema: &Schema,
632    operation_id: OperationId,
633) {
634    for var in variables {
635        query.variables.push(ResolvedVariable {
636            operation_id,
637            name: var.name.clone(),
638            default: var.default_value.clone(),
639            r#type: resolve_field_type(schema, &var.var_type),
640        });
641    }
642}
643
644pub(crate) fn walk_operations(
645    query: &Query,
646) -> impl Iterator<Item = (OperationId, &ResolvedOperation)> {
647    query
648        .operations
649        .iter()
650        .enumerate()
651        .map(|(id, op)| (OperationId(id as u32), op))
652}
653
654pub(crate) fn operation_has_no_variables(operation_id: OperationId, query: &Query) -> bool {
655    walk_operation_variables(operation_id, query)
656        .next()
657        .is_none()
658}
659
660pub(crate) fn walk_operation_variables(
661    operation_id: OperationId,
662    query: &Query,
663) -> impl Iterator<Item = (VariableId, &ResolvedVariable)> {
664    query
665        .variables
666        .iter()
667        .enumerate()
668        .map(|(idx, var)| (VariableId(idx as u32), var))
669        .filter(move |(_id, var)| var.operation_id == operation_id)
670}
671
672pub(crate) fn all_used_types(operation_id: OperationId, query: &BoundQuery<'_>) -> UsedTypes {
673    let mut used_types = UsedTypes::default();
674
675    let operation = query.query.get_operation(operation_id);
676
677    for (_id, selection) in query.query.walk_selection_set(&operation.selection_set) {
678        selection.collect_used_types(&mut used_types, query);
679    }
680
681    for (_id, variable) in walk_operation_variables(operation_id, query.query) {
682        variable.collect_used_types(&mut used_types, query.schema);
683    }
684
685    used_types
686}
687
688pub(crate) fn full_path_prefix(selection_id: SelectionId, query: &BoundQuery<'_>) -> String {
689    let mut path = match query.query.get_selection(selection_id) {
690        Selection::FragmentSpread(_) | Selection::InlineFragment(_) => Vec::new(),
691        selection => vec![selection.to_path_segment(query)],
692    };
693
694    let mut item = selection_id;
695
696    while let Some(parent) = query.query.selection_parent_idx.get(&item) {
697        path.push(parent.to_path_segment(query));
698
699        match parent {
700            SelectionParent::Field(id) | SelectionParent::InlineFragment(id) => {
701                item = *id;
702            }
703            _ => break,
704        }
705    }
706
707    path.reverse();
708    path.join("")
709}
710
711#[derive(Clone, Copy)]
712pub(crate) struct BoundQuery<'a> {
713    pub(crate) query: &'a Query,
714    pub(crate) schema: &'a Schema,
715}