bluejay_validator/executable/document/rules/
all_variables_used.rs

1use 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}