bluejay_validator/executable/document/rules/
all_variable_uses_defined.rs1use crate::executable::{
2 document::{Error, Path, PathRoot, Rule, Visitor},
3 Cache,
4};
5use bluejay_core::definition::{SchemaDefinition, TypeDefinitionReference};
6use bluejay_core::executable::{
7 ExecutableDocument, FragmentSpread, OperationDefinition, VariableDefinition,
8};
9use bluejay_core::{Argument, AsIter, Indexed, ObjectValue, Value, ValueReference, Variable};
10use itertools::Either;
11use std::collections::{BTreeMap, BTreeSet, HashMap};
12
13pub struct AllVariableUsesDefined<'a, E: ExecutableDocument, S: SchemaDefinition> {
14 fragment_references: HashMap<Indexed<'a, E::FragmentDefinition>, BTreeSet<PathRoot<'a, E>>>,
15 variable_usages:
16 BTreeMap<PathRoot<'a, E>, Vec<&'a <E::Value<false> as Value<false>>::Variable>>,
17 cache: &'a Cache<'a, E, S>,
18}
19
20impl<'a, E: ExecutableDocument + 'a, S: SchemaDefinition + 'a> Visitor<'a, E, S>
21 for AllVariableUsesDefined<'a, E, S>
22{
23 fn new(_: &'a E, _: &'a S, cache: &'a Cache<'a, E, S>) -> Self {
24 Self {
25 fragment_references: HashMap::new(),
26 variable_usages: BTreeMap::new(),
27 cache,
28 }
29 }
30
31 fn visit_variable_argument(
32 &mut self,
33 argument: &'a <E as ExecutableDocument>::Argument<false>,
34 _: &'a <S as SchemaDefinition>::InputValueDefinition,
35 path: &Path<'a, E>,
36 ) {
37 self.visit_value(argument.value(), *path.root());
38 }
39
40 fn visit_fragment_spread(
41 &mut self,
42 fragment_spread: &'a E::FragmentSpread,
43 _: TypeDefinitionReference<'a, S::TypeDefinition>,
44 path: &Path<'a, E>,
45 ) {
46 if let Some(fragment_definition) = self.cache.fragment_definition(fragment_spread.name()) {
47 self.fragment_references
48 .entry(Indexed(fragment_definition))
49 .or_default()
50 .insert(*path.root());
51 }
52 }
53}
54
55impl<'a, E: ExecutableDocument, S: SchemaDefinition> AllVariableUsesDefined<'a, E, S> {
56 fn visit_value(
57 &mut self,
58 value: &'a <E as ExecutableDocument>::Value<false>,
59 root: PathRoot<'a, E>,
60 ) {
61 match value.as_ref() {
62 ValueReference::Variable(v) => {
63 self.variable_usages.entry(root).or_default().push(v);
64 }
65 ValueReference::List(l) => l.iter().for_each(|value| self.visit_value(value, root)),
66 ValueReference::Object(o) => o
67 .iter()
68 .for_each(|(_, value)| self.visit_value(value, root)),
69 _ => {}
70 }
71 }
72
73 fn operation_definitions_where_fragment_used(
74 &self,
75 fragment_definition: &'a E::FragmentDefinition,
76 ) -> impl Iterator<Item = &'a E::OperationDefinition> {
77 let mut references = BTreeSet::new();
78 self.visit_fragment_references(fragment_definition, &mut references);
79 references
80 .into_iter()
81 .filter_map(|reference| match reference {
82 PathRoot::Operation(o) => Some(o),
83 PathRoot::Fragment(_) => None,
84 })
85 }
86
87 fn visit_fragment_references(
88 &self,
89 fragment_definition: &'a E::FragmentDefinition,
90 visited: &mut BTreeSet<PathRoot<'a, E>>,
91 ) {
92 if let Some(references) = self.fragment_references.get(&Indexed(fragment_definition)) {
93 references.iter().for_each(|reference| {
94 if visited.insert(*reference) {
95 if let PathRoot::Fragment(f) = reference {
96 self.visit_fragment_references(f, visited);
97 }
98 }
99 });
100 }
101 }
102}
103
104impl<'a, E: ExecutableDocument + 'a, S: SchemaDefinition + 'a> Rule<'a, E, S>
105 for AllVariableUsesDefined<'a, E, S>
106{
107 type Error = Error<'a, E, S>;
108 type Errors = std::vec::IntoIter<Error<'a, E, S>>;
109
110 fn into_errors(self) -> Self::Errors {
111 self.variable_usages
112 .iter()
113 .filter(|(_, variables)| !variables.is_empty())
114 .flat_map(|(root, variables)| {
115 let operation_definitions: Either<std::iter::Once<&'a E::OperationDefinition>, _> =
116 match root {
117 PathRoot::Operation(operation_definition) => {
118 Either::Left(std::iter::once(operation_definition))
119 }
120 PathRoot::Fragment(fragment_definition) => Either::Right(
121 self.operation_definitions_where_fragment_used(fragment_definition),
122 ),
123 };
124 operation_definitions.flat_map(|operation_definition| {
125 variables.iter().copied().filter_map(|variable| {
126 operation_definition
127 .as_ref()
128 .variable_definitions()
129 .map_or(true, |variable_definitions| {
130 variable_definitions.iter().all(|variable_definition| {
131 variable_definition.variable() != variable.name()
132 })
133 })
134 .then_some(Error::VariableNotDefined {
135 variable,
136 operation_definition,
137 })
138 })
139 })
140 })
141 .collect::<Vec<Error<'a, E, S>>>()
142 .into_iter()
143 }
144}