graphql_tools/validation/rules/
no_fragments_cycle.rs1use super::ValidationRule;
2use crate::ast::ext::AstNodeWithName;
3use crate::ast::{visit_document, 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<'a>(
35 &mut self,
36 root: &'a FragmentDefinition,
37 spread_paths: &mut Vec<&'a FragmentSpread>,
38 spread_path_index_by_name: &mut HashMap<&'a str, usize>,
39 known_fragments: &'a HashMap<&'a str, &'a FragmentDefinition>,
40 error_context: &mut ValidationErrorContext,
41 ) {
42 enum Frame<'a> {
46 EnterFragment(&'a FragmentDefinition),
47 ExitFragment(&'a str), EnterSpread(&'a FragmentSpread), PopSpread, }
51
52 let mut stack: Vec<Frame<'a>> = 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<'a> OperationVisitor<'a, 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<'a>(&self) -> &'a str {
158 "NoFragmentsCycle"
159 }
160
161 fn validate(
162 &self,
163 ctx: &mut OperationVisitorContext,
164 error_collector: &mut ValidationErrorContext,
165 ) {
166 visit_document(
167 &mut NoFragmentsCycle::new(),
168 ctx.operation,
169 ctx,
170 error_collector,
171 );
172 }
173}
174
175#[test]
176fn single_reference_is_valid() {
177 use crate::validation::test_utils::*;
178
179 let mut plan = create_plan_from_rule(Box::new(NoFragmentsCycle::new()));
180 let errors = test_operation_with_schema(
181 "fragment fragA on Dog { ...fragB }
182 fragment fragB on Dog { name }",
183 TEST_SCHEMA,
184 &mut plan,
185 );
186
187 let mes = get_messages(&errors).len();
188 assert_eq!(mes, 0);
189}
190
191#[test]
192fn spreading_twice_is_not_circular() {
193 use crate::validation::test_utils::*;
194
195 let mut plan = create_plan_from_rule(Box::new(NoFragmentsCycle::new()));
196 let errors = test_operation_with_schema(
197 "fragment fragA on Dog { ...fragB, ...fragB }
198 fragment fragB on Dog { name }",
199 TEST_SCHEMA,
200 &mut plan,
201 );
202
203 let mes = get_messages(&errors).len();
204 assert_eq!(mes, 0);
205}
206
207#[test]
208fn spreading_twice_indirectly_is_not_circular() {
209 use crate::validation::test_utils::*;
210
211 let mut plan = create_plan_from_rule(Box::new(NoFragmentsCycle::new()));
212 let errors = test_operation_with_schema(
213 "fragment fragA on Dog { ...fragB, ...fragC }
214 fragment fragB on Dog { ...fragC }
215 fragment fragC on Dog { name }",
216 TEST_SCHEMA,
217 &mut plan,
218 );
219
220 let mes = get_messages(&errors).len();
221 assert_eq!(mes, 0);
222}
223
224#[test]
225fn double_spread_within_abstract_types() {
226 use crate::validation::test_utils::*;
227
228 let mut plan = create_plan_from_rule(Box::new(NoFragmentsCycle::new()));
229 let errors = test_operation_with_schema(
230 "fragment nameFragment on Pet {
231 ... on Dog { name }
232 ... on Cat { name }
233 }
234
235 fragment spreadsInAnon on Pet {
236 ... on Dog { ...nameFragment }
237 ... on Cat { ...nameFragment }
238 }",
239 TEST_SCHEMA,
240 &mut plan,
241 );
242
243 let mes = get_messages(&errors).len();
244 assert_eq!(mes, 0);
245}
246
247#[test]
248fn does_not_false_positive_on_unknown_fragment() {
249 use crate::validation::test_utils::*;
250
251 let mut plan = create_plan_from_rule(Box::new(NoFragmentsCycle::new()));
252 let errors = test_operation_with_schema(
253 "fragment nameFragment on Pet {
254 ...UnknownFragment
255 }",
256 TEST_SCHEMA,
257 &mut plan,
258 );
259
260 let mes = get_messages(&errors).len();
261 assert_eq!(mes, 0);
262}
263
264#[test]
265fn spreading_recursively_within_field_fails() {
266 use crate::validation::test_utils::*;
267
268 let mut plan = create_plan_from_rule(Box::new(NoFragmentsCycle::new()));
269 let errors = test_operation_with_schema(
270 "fragment fragA on Human { relatives { ...fragA } }",
271 TEST_SCHEMA,
272 &mut plan,
273 );
274
275 let mes = get_messages(&errors);
276 assert_eq!(mes.len(), 1);
277 assert_eq!(mes, vec!["Cannot spread fragment \"fragA\" within itself."]);
278}
279
280#[test]
281fn no_spreading_itself_directly() {
282 use crate::validation::test_utils::*;
283
284 let mut plan = create_plan_from_rule(Box::new(NoFragmentsCycle::new()));
285 let errors = test_operation_with_schema(
286 "
287 fragment fragA on Dog { ...fragA }",
288 TEST_SCHEMA,
289 &mut plan,
290 );
291
292 let mes = get_messages(&errors);
293 assert_eq!(mes.len(), 1);
294 assert_eq!(mes, vec!["Cannot spread fragment \"fragA\" within itself."]);
295}
296
297#[test]
298fn no_spreading_itself_directly_within_inline_fragment() {
299 use crate::validation::test_utils::*;
300
301 let mut plan = create_plan_from_rule(Box::new(NoFragmentsCycle::new()));
302 let errors = test_operation_with_schema(
303 "fragment fragA on Pet {
304 ... on Dog {
305 ...fragA
306 }
307 }",
308 TEST_SCHEMA,
309 &mut plan,
310 );
311
312 let mes = get_messages(&errors);
313 assert_eq!(mes.len(), 1);
314 assert_eq!(mes, vec!["Cannot spread fragment \"fragA\" within itself."]);
315}
316
317#[test]
318fn no_spreading_itself_indirectly() {
319 use crate::validation::test_utils::*;
320
321 let mut plan = create_plan_from_rule(Box::new(NoFragmentsCycle::new()));
322 let errors = test_operation_with_schema(
323 "fragment fragA on Dog { ...fragB }
324 fragment fragB on Dog { ...fragA }",
325 TEST_SCHEMA,
326 &mut plan,
327 );
328
329 let mes = get_messages(&errors);
330 assert_eq!(mes.len(), 1);
331 assert_eq!(
332 mes,
333 vec!["Cannot spread fragment \"fragA\" within itself via \"fragB\"."]
334 );
335}
336
337#[test]
338fn no_spreading_itself_indirectly_reports_opposite_order() {
339 use crate::validation::test_utils::*;
340
341 let mut plan = create_plan_from_rule(Box::new(NoFragmentsCycle::new()));
342 let errors = test_operation_with_schema(
343 "fragment fragB on Dog { ...fragA }
344 fragment fragA on Dog { ...fragB }",
345 TEST_SCHEMA,
346 &mut plan,
347 );
348
349 let mes = get_messages(&errors);
350 assert_eq!(mes.len(), 1);
351 assert_eq!(
352 mes,
353 vec!["Cannot spread fragment \"fragB\" within itself via \"fragA\"."]
354 );
355}
356
357#[test]
358fn no_spreading_itself_indirectly_within_inline_fragment() {
359 use crate::validation::test_utils::*;
360
361 let mut plan = create_plan_from_rule(Box::new(NoFragmentsCycle::new()));
362 let errors = test_operation_with_schema(
363 "fragment fragA on Pet {
364 ... on Dog {
365 ...fragB
366 }
367 }
368 fragment fragB on Pet {
369 ... on Dog {
370 ...fragA
371 }
372 }",
373 TEST_SCHEMA,
374 &mut plan,
375 );
376
377 let mes = get_messages(&errors);
378 assert_eq!(mes.len(), 1);
379 assert_eq!(
380 mes,
381 vec!["Cannot spread fragment \"fragA\" within itself via \"fragB\"."]
382 );
383}
384
385#[test]
386fn no_spreading_itself_deeply() {
387 use crate::validation::test_utils::*;
388
389 let mut plan = create_plan_from_rule(Box::new(NoFragmentsCycle::new()));
390 let errors = test_operation_with_schema(
391 "fragment fragA on Dog { ...fragB }
392 fragment fragB on Dog { ...fragC }
393 fragment fragC on Dog { ...fragO }
394 fragment fragX on Dog { ...fragY }
395 fragment fragY on Dog { ...fragZ }
396 fragment fragZ on Dog { ...fragO }
397 fragment fragO on Dog { ...fragP }
398 fragment fragP on Dog { ...fragA, ...fragX }",
399 TEST_SCHEMA,
400 &mut plan,
401 );
402
403 let mes = get_messages(&errors);
404 assert_eq!(mes.len(), 2);
405 assert_eq!(
406 mes,
407 vec![
408 "Cannot spread fragment \"fragA\" within itself via \"fragB\", \"fragC\", \"fragO\", \"fragP\".",
409 "Cannot spread fragment \"fragO\" within itself via \"fragP\", \"fragX\", \"fragY\", \"fragZ\".",
410 ]
411 );
412}
413
414#[test]
415fn no_spreading_itself_deeply_two_paths() {
416 use crate::validation::test_utils::*;
417
418 let mut plan = create_plan_from_rule(Box::new(NoFragmentsCycle::new()));
419 let errors = test_operation_with_schema(
420 "fragment fragA on Dog { ...fragB, ...fragC }
421 fragment fragB on Dog { ...fragA }
422 fragment fragC on Dog { ...fragA }",
423 TEST_SCHEMA,
424 &mut plan,
425 );
426
427 let mes = get_messages(&errors);
428 assert_eq!(mes.len(), 2);
429 assert_eq!(
430 mes,
431 vec![
432 "Cannot spread fragment \"fragA\" within itself via \"fragB\".",
433 "Cannot spread fragment \"fragA\" within itself via \"fragC\".",
434 ]
435 );
436}
437
438#[test]
439fn no_spreading_itself_deeply_two_paths_alt_traverse_order() {
440 use crate::validation::test_utils::*;
441
442 let mut plan = create_plan_from_rule(Box::new(NoFragmentsCycle::new()));
443 let errors = test_operation_with_schema(
444 "
445 fragment fragA on Dog { ...fragC }
446 fragment fragB on Dog { ...fragC }
447 fragment fragC on Dog { ...fragA, ...fragB }
448 ",
449 TEST_SCHEMA,
450 &mut plan,
451 );
452
453 let mes = get_messages(&errors);
454 assert_eq!(mes.len(), 2);
455 assert_eq!(
456 mes,
457 vec![
458 "Cannot spread fragment \"fragA\" within itself via \"fragC\".",
459 "Cannot spread fragment \"fragC\" within itself via \"fragB\".",
460 ]
461 );
462}
463
464#[test]
465fn acyclic_fragment_chain_does_not_overflow_stack() {
466 use crate::validation::test_utils::*;
467
468 let mut plan = create_plan_from_rule(Box::new(NoFragmentsCycle::new()));
469
470 let n = 10_000;
471 let mut doc = String::from("");
472 for i in 1..n {
473 doc.push_str(&format!("fragment F{i} on Dog {{ ...F{} }}\n", i + 1));
474 }
475 doc.push_str(&format!("fragment F{n} on Dog {{ name }}\n"));
476
477 let errors = test_operation_with_schema(&doc, TEST_SCHEMA, &mut plan);
479 let mes = get_messages(&errors);
480 assert_eq!(mes.len(), 0);
481}
482
483#[test]
484fn no_spreading_itself_deeply_and_immediately() {
485 use crate::validation::test_utils::*;
486
487 let mut plan = create_plan_from_rule(Box::new(NoFragmentsCycle::new()));
488 let errors = test_operation_with_schema(
489 "
490 fragment fragA on Dog { ...fragB }
491 fragment fragB on Dog { ...fragB, ...fragC }
492 fragment fragC on Dog { ...fragA, ...fragB }
493 ",
494 TEST_SCHEMA,
495 &mut plan,
496 );
497
498 let mes = get_messages(&errors);
499 assert_eq!(mes.len(), 3);
500 assert_eq!(
501 mes,
502 vec![
503 "Cannot spread fragment \"fragB\" within itself.",
504 "Cannot spread fragment \"fragA\" within itself via \"fragB\", \"fragC\".",
505 "Cannot spread fragment \"fragB\" within itself via \"fragC\".",
506 ]
507 );
508}