graphql_tools/validation/rules/
no_unused_fragments.rs1use std::collections::{HashMap, HashSet, VecDeque};
2
3use super::ValidationRule;
4use crate::ast::{OperationVisitor, OperationVisitorContext};
5use crate::static_graphql::query::*;
6use crate::validation::utils::{ValidationError, ValidationErrorContext};
7
8pub struct NoUnusedFragments<'doc> {
15 fragments_in_use: Vec<&'doc str>,
16 current_fragment_spreads: Vec<&'doc str>,
17 current_fragment: Option<&'doc str>,
18 fragment_spreads: HashMap<&'doc str, Vec<&'doc str>>,
19}
20
21impl<'doc> OperationVisitor<'doc, ValidationErrorContext> for NoUnusedFragments<'doc> {
22 fn enter_fragment_definition(
23 &mut self,
24 _: &mut OperationVisitorContext,
25 _: &mut ValidationErrorContext,
26 fragment: &'doc FragmentDefinition,
27 ) {
28 self.current_fragment = Some(fragment.name.as_str());
29 self.current_fragment_spreads = Vec::new();
30 }
31
32 fn leave_fragment_definition(
33 &mut self,
34 _: &mut OperationVisitorContext,
35 _: &mut ValidationErrorContext,
36 _: &FragmentDefinition,
37 ) {
38 if let Some(name) = self.current_fragment.take() {
39 self.fragment_spreads
40 .insert(name, std::mem::take(&mut self.current_fragment_spreads));
41 }
42 }
43
44 fn enter_fragment_spread(
45 &mut self,
46 _: &mut OperationVisitorContext,
47 _: &mut ValidationErrorContext,
48 fragment_spread: &'doc FragmentSpread,
49 ) {
50 let name = fragment_spread.fragment_name.as_str();
51 if self.current_fragment.is_some() {
52 self.current_fragment_spreads.push(name);
53 } else {
54 self.fragments_in_use.push(name);
55 }
56 }
57
58 fn leave_document(
59 &mut self,
60 visitor_context: &mut OperationVisitorContext,
61 user_context: &mut ValidationErrorContext,
62 _document: &Document,
63 ) {
64 let mut reachable: HashSet<&str> = HashSet::new();
65 let mut queue: VecDeque<&str> = self.fragments_in_use.iter().copied().collect();
66
67 while let Some(frag) = queue.pop_front() {
68 if !reachable.insert(frag) {
69 continue;
70 }
71
72 let Some(spreads) = self.fragment_spreads.get(frag) else {
73 continue;
74 };
75
76 for spread in spreads {
77 if !reachable.contains(spread) {
78 queue.push_back(spread);
79 }
80 }
81 }
82
83 visitor_context
84 .known_fragments
85 .keys()
86 .filter(|fragment_name| !reachable.contains(*fragment_name))
87 .for_each(|unused_fragment_name| {
88 user_context.report_error(ValidationError {
89 error_code: self.error_code(),
90 locations: vec![],
91 message: format!("Fragment \"{}\" is never used.", unused_fragment_name),
92 });
93 });
94 }
95}
96
97impl Default for NoUnusedFragments<'_> {
98 fn default() -> Self {
99 Self::new()
100 }
101}
102
103impl NoUnusedFragments<'_> {
104 pub fn new() -> Self {
105 NoUnusedFragments {
106 fragments_in_use: Vec::new(),
107 current_fragment_spreads: Vec::new(),
108 current_fragment: None,
109 fragment_spreads: HashMap::new(),
110 }
111 }
112}
113
114impl ValidationRule for NoUnusedFragments<'_> {
115 fn error_code(&self) -> &'static str {
116 "NoUnusedFragments"
117 }
118
119 fn visitor<'doc>(&self) -> super::ValidationVisitor<'doc> {
120 Box::new(NoUnusedFragments::new())
121 }
122}
123
124#[test]
125fn all_fragment_names_are_used() {
126 use crate::validation::test_utils::*;
127
128 let mut plan = create_plan_from_rule(Box::new(NoUnusedFragments::new()));
129 let errors = test_operation_with_schema(
130 "{
131 human(id: 4) {
132 ...HumanFields1
133 ... on Human {
134 ...HumanFields2
135 }
136 }
137 }
138 fragment HumanFields1 on Human {
139 name
140 ...HumanFields3
141 }
142 fragment HumanFields2 on Human {
143 name
144 }
145 fragment HumanFields3 on Human {
146 name
147 }",
148 TEST_SCHEMA,
149 &mut plan,
150 );
151
152 assert_eq!(get_messages(&errors).len(), 0);
153}
154
155#[test]
156fn all_fragment_names_are_used_by_multiple_operations() {
157 use crate::validation::test_utils::*;
158
159 let mut plan = create_plan_from_rule(Box::new(NoUnusedFragments::new()));
160 let errors = test_operation_with_schema(
161 "query Foo {
162 human(id: 4) {
163 ...HumanFields1
164 }
165 }
166 query Bar {
167 human(id: 4) {
168 ...HumanFields2
169 }
170 }
171 fragment HumanFields1 on Human {
172 name
173 ...HumanFields3
174 }
175 fragment HumanFields2 on Human {
176 name
177 }
178 fragment HumanFields3 on Human {
179 name
180 }
181 ",
182 TEST_SCHEMA,
183 &mut plan,
184 );
185
186 assert_eq!(get_messages(&errors).len(), 0);
187}
188
189#[test]
190fn contains_unknown_fragments() {
191 use crate::validation::test_utils::*;
192
193 let mut plan = create_plan_from_rule(Box::new(NoUnusedFragments::new()));
194 let errors = test_operation_with_schema(
195 "query Foo {
196 human(id: 4) {
197 ...HumanFields1
198 }
199 }
200 query Bar {
201 human(id: 4) {
202 ...HumanFields2
203 }
204 }
205 fragment HumanFields1 on Human {
206 name
207 ...HumanFields3
208 }
209 fragment HumanFields2 on Human {
210 name
211 }
212 fragment HumanFields3 on Human {
213 name
214 }
215 fragment Unused1 on Human {
216 name
217 }
218 fragment Unused2 on Human {
219 name
220 }
221 ",
222 TEST_SCHEMA,
223 &mut plan,
224 );
225
226 let messages = get_messages(&errors);
227 assert_eq!(messages.len(), 2);
228}
229
230#[test]
231fn contains_unknown_fragments_with_ref_cycle() {
232 use crate::validation::test_utils::*;
233
234 let mut plan = create_plan_from_rule(Box::new(NoUnusedFragments::new()));
235 let errors = test_operation_with_schema(
236 "query Foo {
237 human(id: 4) {
238 ...HumanFields1
239 }
240 }
241 query Bar {
242 human(id: 4) {
243 ...HumanFields2
244 }
245 }
246 fragment HumanFields1 on Human {
247 name
248 ...HumanFields3
249 }
250 fragment HumanFields2 on Human {
251 name
252 }
253 fragment HumanFields3 on Human {
254 name
255 }
256 fragment Unused1 on Human {
257 name
258 ...Unused2
259 }
260 fragment Unused2 on Human {
261 name
262 ...Unused1
263 }
264 ",
265 TEST_SCHEMA,
266 &mut plan,
267 );
268
269 let messages = get_messages(&errors);
270 assert_eq!(messages.len(), 2);
271 assert!(messages.contains(&&"Fragment \"Unused1\" is never used.".to_owned()));
272 assert!(messages.contains(&&"Fragment \"Unused2\" is never used.".to_owned()));
273}
274
275#[test]
276fn contains_unknown_and_undef_fragments() {
277 use crate::validation::test_utils::*;
278
279 let mut plan = create_plan_from_rule(Box::new(NoUnusedFragments::new()));
280 let errors = test_operation_with_schema(
281 "query Foo {
282 human(id: 4) {
283 ...bar
284 }
285 }
286 fragment foo on Human {
287 name
288 }
289 ",
290 TEST_SCHEMA,
291 &mut plan,
292 );
293
294 let messages = get_messages(&errors);
295 assert_eq!(messages.len(), 1);
296 assert_eq!(messages, vec!["Fragment \"foo\" is never used.",]);
297}