bluejay_validator/executable/document/rules/
all_variable_uses_defined.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 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}