graphql_tools/validation/rules/
no_fragments_cycle.rs1use super::ValidationRule;
2use crate::ast::ext::AstNodeWithName;
3use crate::ast::{OperationVisitor, OperationVisitorContext};
4use crate::static_graphql::query::{FragmentDefinition, FragmentSpread};
5use crate::validation::utils::{ValidationError, ValidationErrorContext};
6use std::collections::{HashMap, HashSet};
7
8pub struct NoFragmentsCycle {
15 visited_fragments: HashSet<String>,
16}
17
18impl Default for NoFragmentsCycle {
19 fn default() -> Self {
20 Self::new()
21 }
22}
23
24impl NoFragmentsCycle {
25 pub fn new() -> Self {
26 Self {
27 visited_fragments: HashSet::new(),
28 }
29 }
30
31 fn detect_cycles<'doc>(
35 &mut self,
36 root: &'doc FragmentDefinition,
37 spread_paths: &mut Vec<&'doc FragmentSpread>,
38 spread_path_index_by_name: &mut HashMap<&'doc str, usize>,
39 known_fragments: &'doc HashMap<&'doc str, &'doc FragmentDefinition>,
40 error_context: &mut ValidationErrorContext,
41 ) {
42 enum Frame<'doc> {
46 EnterFragment(&'doc FragmentDefinition),
47 ExitFragment(&'doc str), EnterSpread(&'doc FragmentSpread), PopSpread, }
51
52 let mut stack: Vec<Frame<'doc>> = vec![Frame::EnterFragment(root)];
53
54 while let Some(frame) = stack.pop() {
55 match frame {
56 Frame::ExitFragment(name) => {
57 spread_path_index_by_name.remove(name);
58 }
59 Frame::PopSpread => {
60 spread_paths.pop();
61 }
62 Frame::EnterFragment(fragment) => {
63 if self.visited_fragments.contains(&fragment.name) {
64 continue;
65 }
66 self.visited_fragments.insert(fragment.name.clone());
67
68 let spread_nodes = fragment.selection_set.get_recursive_fragment_spreads();
69 if spread_nodes.is_empty() {
70 continue;
71 }
72
73 spread_path_index_by_name.insert(fragment.name.as_str(), spread_paths.len());
74 stack.push(Frame::ExitFragment(fragment.name.as_str()));
75
76 for spread_node in spread_nodes.into_iter().rev() {
78 stack.push(Frame::EnterSpread(spread_node));
79 }
80 }
81 Frame::EnterSpread(spread_node) => {
82 let spread_name = &spread_node.fragment_name;
83
84 match spread_path_index_by_name.get(spread_name.as_str()) {
85 None => {
86 if let Some(spread_def) = known_fragments.get(spread_name.as_str()) {
87 spread_paths.push(spread_node);
90 stack.push(Frame::PopSpread);
91 stack.push(Frame::EnterFragment(spread_def));
92 }
93 }
94 Some(cycle_index) => {
95 spread_paths.push(spread_node);
97 let cycle_path = &spread_paths[*cycle_index..];
98 let via_path = match cycle_path.len() {
99 0 => vec![],
100 _ => cycle_path[0..cycle_path.len() - 1]
101 .iter()
102 .map(|s| {
103 format!(
104 "\"{}\"",
105 s.node_name()
106 .expect("fragment spread must have a name")
107 )
108 })
109 .collect::<Vec<String>>(),
110 };
111
112 error_context.report_error(ValidationError {
113 error_code: self.error_code(),
114 locations: cycle_path.iter().map(|f| f.position).collect(),
115 message: match via_path.len() {
116 0 => format!(
117 "Cannot spread fragment \"{}\" within itself.",
118 spread_name
119 ),
120 _ => format!(
121 "Cannot spread fragment \"{}\" within itself via {}.",
122 spread_name,
123 via_path.join(", ")
124 ),
125 },
126 });
127 spread_paths.pop();
128 }
129 }
130 }
131 }
132 }
133 }
134}
135
136impl<'doc> OperationVisitor<'doc, ValidationErrorContext> for NoFragmentsCycle {
137 fn enter_fragment_definition(
138 &mut self,
139 visitor_context: &mut OperationVisitorContext,
140 user_context: &mut ValidationErrorContext,
141 fragment: &FragmentDefinition,
142 ) {
143 let mut spread_paths: Vec<&FragmentSpread> = vec![];
144 let mut spread_path_index_by_name: HashMap<&str, usize> = HashMap::new();
145
146 self.detect_cycles(
147 fragment,
148 &mut spread_paths,
149 &mut spread_path_index_by_name,
150 &visitor_context.known_fragments,
151 user_context,
152 );
153 }
154}
155
156impl ValidationRule for NoFragmentsCycle {
157 fn error_code(&self) -> &'static str {
158 "NoFragmentsCycle"
159 }
160
161 fn visitor<'doc>(&self) -> super::ValidationVisitor<'doc> {
162 Box::new(NoFragmentsCycle::new())
163 }
164}
165
166#[test]
167fn single_reference_is_valid() {
168 use crate::validation::test_utils::*;
169
170 let mut plan = create_plan_from_rule(Box::new(NoFragmentsCycle::new()));
171 let errors = test_operation_with_schema(
172 "fragment fragA on Dog { ...fragB }
173 fragment fragB on Dog { name }",
174 TEST_SCHEMA,
175 &mut plan,
176 );
177
178 let mes = get_messages(&errors).len();
179 assert_eq!(mes, 0);
180}
181
182#[test]
183fn spreading_twice_is_not_circular() {
184 use crate::validation::test_utils::*;
185
186 let mut plan = create_plan_from_rule(Box::new(NoFragmentsCycle::new()));
187 let errors = test_operation_with_schema(
188 "fragment fragA on Dog { ...fragB, ...fragB }
189 fragment fragB on Dog { name }",
190 TEST_SCHEMA,
191 &mut plan,
192 );
193
194 let mes = get_messages(&errors).len();
195 assert_eq!(mes, 0);
196}
197
198#[test]
199fn spreading_twice_indirectly_is_not_circular() {
200 use crate::validation::test_utils::*;
201
202 let mut plan = create_plan_from_rule(Box::new(NoFragmentsCycle::new()));
203 let errors = test_operation_with_schema(
204 "fragment fragA on Dog { ...fragB, ...fragC }
205 fragment fragB on Dog { ...fragC }
206 fragment fragC on Dog { name }",
207 TEST_SCHEMA,
208 &mut plan,
209 );
210
211 let mes = get_messages(&errors).len();
212 assert_eq!(mes, 0);
213}
214
215#[test]
216fn double_spread_within_abstract_types() {
217 use crate::validation::test_utils::*;
218
219 let mut plan = create_plan_from_rule(Box::new(NoFragmentsCycle::new()));
220 let errors = test_operation_with_schema(
221 "fragment nameFragment on Pet {
222 ... on Dog { name }
223 ... on Cat { name }
224 }
225
226 fragment spreadsInAnon on Pet {
227 ... on Dog { ...nameFragment }
228 ... on Cat { ...nameFragment }
229 }",
230 TEST_SCHEMA,
231 &mut plan,
232 );
233
234 let mes = get_messages(&errors).len();
235 assert_eq!(mes, 0);
236}
237
238#[test]
239fn does_not_false_positive_on_unknown_fragment() {
240 use crate::validation::test_utils::*;
241
242 let mut plan = create_plan_from_rule(Box::new(NoFragmentsCycle::new()));
243 let errors = test_operation_with_schema(
244 "fragment nameFragment on Pet {
245 ...UnknownFragment
246 }",
247 TEST_SCHEMA,
248 &mut plan,
249 );
250
251 let mes = get_messages(&errors).len();
252 assert_eq!(mes, 0);
253}
254
255#[test]
256fn spreading_recursively_within_field_fails() {
257 use crate::validation::test_utils::*;
258
259 let mut plan = create_plan_from_rule(Box::new(NoFragmentsCycle::new()));
260 let errors = test_operation_with_schema(
261 "fragment fragA on Human { relatives { ...fragA } }",
262 TEST_SCHEMA,
263 &mut plan,
264 );
265
266 let mes = get_messages(&errors);
267 assert_eq!(mes.len(), 1);
268 assert_eq!(mes, vec!["Cannot spread fragment \"fragA\" within itself."]);
269}
270
271#[test]
272fn no_spreading_itself_directly() {
273 use crate::validation::test_utils::*;
274
275 let mut plan = create_plan_from_rule(Box::new(NoFragmentsCycle::new()));
276 let errors = test_operation_with_schema(
277 "
278 fragment fragA on Dog { ...fragA }",
279 TEST_SCHEMA,
280 &mut plan,
281 );
282
283 let mes = get_messages(&errors);
284 assert_eq!(mes.len(), 1);
285 assert_eq!(mes, vec!["Cannot spread fragment \"fragA\" within itself."]);
286}
287
288#[test]
289fn no_spreading_itself_directly_within_inline_fragment() {
290 use crate::validation::test_utils::*;
291
292 let mut plan = create_plan_from_rule(Box::new(NoFragmentsCycle::new()));
293 let errors = test_operation_with_schema(
294 "fragment fragA on Pet {
295 ... on Dog {
296 ...fragA
297 }
298 }",
299 TEST_SCHEMA,
300 &mut plan,
301 );
302
303 let mes = get_messages(&errors);
304 assert_eq!(mes.len(), 1);
305 assert_eq!(mes, vec!["Cannot spread fragment \"fragA\" within itself."]);
306}
307
308#[test]
309fn no_spreading_itself_indirectly() {
310 use crate::validation::test_utils::*;
311
312 let mut plan = create_plan_from_rule(Box::new(NoFragmentsCycle::new()));
313 let errors = test_operation_with_schema(
314 "fragment fragA on Dog { ...fragB }
315 fragment fragB on Dog { ...fragA }",
316 TEST_SCHEMA,
317 &mut plan,
318 );
319
320 let mes = get_messages(&errors);
321 assert_eq!(mes.len(), 1);
322 assert_eq!(
323 mes,
324 vec!["Cannot spread fragment \"fragA\" within itself via \"fragB\"."]
325 );
326}
327
328#[test]
329fn no_spreading_itself_indirectly_reports_opposite_order() {
330 use crate::validation::test_utils::*;
331
332 let mut plan = create_plan_from_rule(Box::new(NoFragmentsCycle::new()));
333 let errors = test_operation_with_schema(
334 "fragment fragB on Dog { ...fragA }
335 fragment fragA on Dog { ...fragB }",
336 TEST_SCHEMA,
337 &mut plan,
338 );
339
340 let mes = get_messages(&errors);
341 assert_eq!(mes.len(), 1);
342 assert_eq!(
343 mes,
344 vec!["Cannot spread fragment \"fragB\" within itself via \"fragA\"."]
345 );
346}
347
348#[test]
349fn no_spreading_itself_indirectly_within_inline_fragment() {
350 use crate::validation::test_utils::*;
351
352 let mut plan = create_plan_from_rule(Box::new(NoFragmentsCycle::new()));
353 let errors = test_operation_with_schema(
354 "fragment fragA on Pet {
355 ... on Dog {
356 ...fragB
357 }
358 }
359 fragment fragB on Pet {
360 ... on Dog {
361 ...fragA
362 }
363 }",
364 TEST_SCHEMA,
365 &mut plan,
366 );
367
368 let mes = get_messages(&errors);
369 assert_eq!(mes.len(), 1);
370 assert_eq!(
371 mes,
372 vec!["Cannot spread fragment \"fragA\" within itself via \"fragB\"."]
373 );
374}
375
376#[test]
377fn no_spreading_itself_deeply() {
378 use crate::validation::test_utils::*;
379
380 let mut plan = create_plan_from_rule(Box::new(NoFragmentsCycle::new()));
381 let errors = test_operation_with_schema(
382 "fragment fragA on Dog { ...fragB }
383 fragment fragB on Dog { ...fragC }
384 fragment fragC on Dog { ...fragO }
385 fragment fragX on Dog { ...fragY }
386 fragment fragY on Dog { ...fragZ }
387 fragment fragZ on Dog { ...fragO }
388 fragment fragO on Dog { ...fragP }
389 fragment fragP on Dog { ...fragA, ...fragX }",
390 TEST_SCHEMA,
391 &mut plan,
392 );
393
394 let mes = get_messages(&errors);
395 assert_eq!(mes.len(), 2);
396 assert_eq!(
397 mes,
398 vec![
399 "Cannot spread fragment \"fragA\" within itself via \"fragB\", \"fragC\", \"fragO\", \"fragP\".",
400 "Cannot spread fragment \"fragO\" within itself via \"fragP\", \"fragX\", \"fragY\", \"fragZ\".",
401 ]
402 );
403}
404
405#[test]
406fn no_spreading_itself_deeply_two_paths() {
407 use crate::validation::test_utils::*;
408
409 let mut plan = create_plan_from_rule(Box::new(NoFragmentsCycle::new()));
410 let errors = test_operation_with_schema(
411 "fragment fragA on Dog { ...fragB, ...fragC }
412 fragment fragB on Dog { ...fragA }
413 fragment fragC on Dog { ...fragA }",
414 TEST_SCHEMA,
415 &mut plan,
416 );
417
418 let mes = get_messages(&errors);
419 assert_eq!(mes.len(), 2);
420 assert_eq!(
421 mes,
422 vec![
423 "Cannot spread fragment \"fragA\" within itself via \"fragB\".",
424 "Cannot spread fragment \"fragA\" within itself via \"fragC\".",
425 ]
426 );
427}
428
429#[test]
430fn no_spreading_itself_deeply_two_paths_alt_traverse_order() {
431 use crate::validation::test_utils::*;
432
433 let mut plan = create_plan_from_rule(Box::new(NoFragmentsCycle::new()));
434 let errors = test_operation_with_schema(
435 "
436 fragment fragA on Dog { ...fragC }
437 fragment fragB on Dog { ...fragC }
438 fragment fragC on Dog { ...fragA, ...fragB }
439 ",
440 TEST_SCHEMA,
441 &mut plan,
442 );
443
444 let mes = get_messages(&errors);
445 assert_eq!(mes.len(), 2);
446 assert_eq!(
447 mes,
448 vec![
449 "Cannot spread fragment \"fragA\" within itself via \"fragC\".",
450 "Cannot spread fragment \"fragC\" within itself via \"fragB\".",
451 ]
452 );
453}
454
455#[test]
456fn acyclic_fragment_chain_does_not_overflow_stack() {
457 use crate::validation::test_utils::*;
458
459 let mut plan = create_plan_from_rule(Box::new(NoFragmentsCycle::new()));
460
461 let n = 10_000;
462 let mut doc = String::from("");
463 for i in 1..n {
464 doc.push_str(&format!("fragment F{i} on Dog {{ ...F{} }}\n", i + 1));
465 }
466 doc.push_str(&format!("fragment F{n} on Dog {{ name }}\n"));
467
468 let errors = test_operation_with_schema(&doc, TEST_SCHEMA, &mut plan);
470 let mes = get_messages(&errors);
471 assert_eq!(mes.len(), 0);
472}
473
474#[test]
475fn no_spreading_itself_deeply_and_immediately() {
476 use crate::validation::test_utils::*;
477
478 let mut plan = create_plan_from_rule(Box::new(NoFragmentsCycle::new()));
479 let errors = test_operation_with_schema(
480 "
481 fragment fragA on Dog { ...fragB }
482 fragment fragB on Dog { ...fragB, ...fragC }
483 fragment fragC on Dog { ...fragA, ...fragB }
484 ",
485 TEST_SCHEMA,
486 &mut plan,
487 );
488
489 let mes = get_messages(&errors);
490 assert_eq!(mes.len(), 3);
491 assert_eq!(
492 mes,
493 vec![
494 "Cannot spread fragment \"fragB\" within itself.",
495 "Cannot spread fragment \"fragA\" within itself via \"fragB\", \"fragC\".",
496 "Cannot spread fragment \"fragB\" within itself via \"fragC\".",
497 ]
498 );
499}