bluejay_validator/executable/document/rules/
all_variables_used.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 std::collections::{HashMap, HashSet};
11use std::ops::Not;
12
13pub struct AllVariablesUsed<'a, E: ExecutableDocument, S: SchemaDefinition> {
14 fragment_references: HashMap<PathRoot<'a, E>, HashSet<Indexed<'a, E::FragmentDefinition>>>,
15 variable_usages: HashMap<PathRoot<'a, E>, HashSet<&'a str>>,
16 cache: &'a Cache<'a, E, S>,
17 executable_document: &'a E,
18}
19
20impl<'a, E: ExecutableDocument + 'a, S: SchemaDefinition + 'a> Visitor<'a, E, S>
21 for AllVariablesUsed<'a, E, S>
22{
23 fn new(executable_document: &'a E, _: &'a S, cache: &'a Cache<'a, E, S>) -> Self {
24 Self {
25 fragment_references: HashMap::new(),
26 variable_usages: HashMap::new(),
27 cache,
28 executable_document,
29 }
30 }
31
32 fn visit_variable_argument(
33 &mut self,
34 argument: &'a <E as ExecutableDocument>::Argument<false>,
35 _: &'a <S as SchemaDefinition>::InputValueDefinition,
36 path: &Path<'a, E>,
37 ) {
38 self.visit_value(argument.value(), *path.root());
39 }
40
41 fn visit_fragment_spread(
42 &mut self,
43 fragment_spread: &'a E::FragmentSpread,
44 _: TypeDefinitionReference<'a, S::TypeDefinition>,
45 path: &Path<'a, E>,
46 ) {
47 if let Some(fragment_definition) = self.cache.fragment_definition(fragment_spread.name()) {
48 self.fragment_references
49 .entry(*path.root())
50 .or_default()
51 .insert(Indexed(fragment_definition));
52 }
53 }
54}
55
56impl<'a, E: ExecutableDocument, S: SchemaDefinition> AllVariablesUsed<'a, E, S> {
57 fn visit_value(
58 &mut self,
59 value: &'a <E as ExecutableDocument>::Value<false>,
60 root: PathRoot<'a, E>,
61 ) {
62 match value.as_ref() {
63 ValueReference::Variable(v) => {
64 self.variable_usages
65 .entry(root)
66 .or_default()
67 .insert(v.name());
68 }
69 ValueReference::List(l) => l.iter().for_each(|value| self.visit_value(value, root)),
70 ValueReference::Object(o) => o
71 .iter()
72 .for_each(|(_, value)| self.visit_value(value, root)),
73 _ => {}
74 }
75 }
76
77 fn fragment_usages(
78 &self,
79 operation_definition: &'a E::OperationDefinition,
80 ) -> impl Iterator<Item = &'a E::FragmentDefinition> {
81 let mut references = HashSet::new();
82 self.visit_fragment_references(&PathRoot::Operation(operation_definition), &mut references);
83 references
84 .into_iter()
85 .map(|Indexed(fragment_definition)| fragment_definition)
86 }
87
88 fn visit_fragment_references(
89 &self,
90 executable_definition: &PathRoot<'a, E>,
91 visited: &mut HashSet<Indexed<'a, E::FragmentDefinition>>,
92 ) {
93 if let Some(references) = self.fragment_references.get(executable_definition) {
94 references.iter().for_each(
95 |Indexed(reference): &Indexed<'a, E::FragmentDefinition>| {
96 if visited.insert(Indexed(reference)) {
97 self.visit_fragment_references(&PathRoot::Fragment(*reference), visited);
98 }
99 },
100 );
101 }
102 }
103}
104
105impl<'a, E: ExecutableDocument + 'a, S: SchemaDefinition + 'a> Rule<'a, E, S>
106 for AllVariablesUsed<'a, E, S>
107{
108 type Error = Error<'a, E, S>;
109 type Errors = std::vec::IntoIter<Error<'a, E, S>>;
110
111 fn into_errors(self) -> Self::Errors {
112 self.executable_document
113 .operation_definitions()
114 .filter(|operation_definition| {
115 operation_definition
116 .as_ref()
117 .variable_definitions()
118 .map_or(false, |variable_definitions| {
119 !variable_definitions.is_empty()
120 })
121 })
122 .flat_map(|operation_definition| {
123 let variable_usages: HashSet<&'a str> = self
124 .fragment_usages(operation_definition)
125 .map(PathRoot::Fragment)
126 .chain(std::iter::once(PathRoot::Operation(operation_definition)))
127 .flat_map(|executable_definition| {
128 self.variable_usages
129 .get(&executable_definition)
130 .into_iter()
131 .flatten()
132 .copied()
133 })
134 .collect();
135
136 operation_definition
137 .as_ref()
138 .variable_definitions()
139 .map(move |variable_definitions| {
140 variable_definitions
141 .iter()
142 .filter_map(move |variable_definition| {
143 variable_usages
144 .contains(variable_definition.variable())
145 .not()
146 .then_some(Error::VariableDefinitionUnused {
147 variable_definition,
148 })
149 })
150 })
151 .into_iter()
152 .flatten()
153 })
154 .collect::<Vec<Error<'a, E, S>>>()
155 .into_iter()
156 }
157}