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                    .is_some_and(|variable_definitions| !variable_definitions.is_empty())
119            })
120            .flat_map(|operation_definition| {
121                let variable_usages: HashSet<&'a str> = self
122                    .fragment_usages(operation_definition)
123                    .map(PathRoot::Fragment)
124                    .chain(std::iter::once(PathRoot::Operation(operation_definition)))
125                    .flat_map(|executable_definition| {
126                        self.variable_usages
127                            .get(&executable_definition)
128                            .into_iter()
129                            .flatten()
130                            .copied()
131                    })
132                    .collect();
133
134                operation_definition
135                    .as_ref()
136                    .variable_definitions()
137                    .map(move |variable_definitions| {
138                        variable_definitions
139                            .iter()
140                            .filter_map(move |variable_definition| {
141                                variable_usages
142                                    .contains(variable_definition.variable())
143                                    .not()
144                                    .then_some(Error::VariableDefinitionUnused {
145                                        variable_definition,
146                                    })
147                            })
148                    })
149                    .into_iter()
150                    .flatten()
151            })
152            .collect::<Vec<Error<'a, E, S>>>()
153            .into_iter()
154    }
155}