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