graphql_tools/validation/rules/
possible_fragment_spreads.rs1use super::ValidationRule;
2use crate::ast::{visit_document, OperationVisitor, OperationVisitorContext};
3use crate::static_graphql::query::TypeCondition;
4use crate::static_graphql::schema;
5use crate::validation::utils::{ValidationError, ValidationErrorContext};
6
7pub struct PossibleFragmentSpreads;
15
16impl Default for PossibleFragmentSpreads {
17 fn default() -> Self {
18 Self::new()
19 }
20}
21
22impl PossibleFragmentSpreads {
23 pub fn new() -> Self {
24 Self {}
25 }
26}
27
28pub fn do_types_overlap(
38 schema: &schema::Document,
39 t1: &schema::TypeDefinition,
40 t2: &schema::TypeDefinition,
41) -> bool {
42 if t1.name().eq(t2.name()) {
43 return true;
44 }
45
46 if t1.is_abstract_type() {
47 if t2.is_abstract_type() {
48 let possible_types = t1.possible_types(schema);
49
50 return possible_types
51 .into_iter()
52 .filter(|possible_type| t2.has_concrete_sub_type(possible_type))
53 .count()
54 > 0;
55 }
56
57 return t1.has_sub_type(t2);
58 }
59
60 if t2.is_abstract_type() {
61 return t2.has_sub_type(t1);
62 }
63
64 false
65}
66
67impl<'a> OperationVisitor<'a, ValidationErrorContext> for PossibleFragmentSpreads {
68 fn enter_inline_fragment(
69 &mut self,
70 visitor_context: &mut OperationVisitorContext,
71 user_context: &mut ValidationErrorContext,
72 _inline_fragment: &crate::static_graphql::query::InlineFragment,
73 ) {
74 if let Some(frag_schema_type) = visitor_context.current_type() {
75 if let Some(parent_type) = visitor_context.current_parent_type() {
76 if frag_schema_type.is_composite_type()
77 && parent_type.is_composite_type()
78 && !do_types_overlap(visitor_context.schema, frag_schema_type, parent_type)
79 {
80 user_context.report_error(ValidationError {error_code: self.error_code(),
81 locations: vec![],
82 message: format!("Fragment cannot be spread here as objects of type \"{}\" can never be of type \"{}\".", parent_type.name(), frag_schema_type.name()),
83 })
84 }
85 }
86 }
87 }
88
89 fn enter_fragment_spread(
90 &mut self,
91 visitor_context: &mut OperationVisitorContext,
92 user_context: &mut ValidationErrorContext,
93 fragment_spread: &crate::static_graphql::query::FragmentSpread,
94 ) {
95 if let Some(actual_fragment) = visitor_context
96 .known_fragments
97 .get(fragment_spread.fragment_name.as_str())
98 {
99 let TypeCondition::On(fragment_type_name) = &actual_fragment.type_condition;
100
101 if let Some(fragment_type) = visitor_context.schema.type_by_name(fragment_type_name) {
102 if let Some(parent_type) = visitor_context.current_parent_type() {
103 if fragment_type.is_composite_type()
104 && parent_type.is_composite_type()
105 && !do_types_overlap(visitor_context.schema, fragment_type, parent_type)
106 {
107 user_context.report_error(ValidationError {error_code: self.error_code(),
108 locations: vec![],
109 message: format!("Fragment \"{}\" cannot be spread here as objects of type \"{}\" can never be of type \"{}\".", actual_fragment.name, parent_type.name(), fragment_type_name),
110 })
111 }
112 }
113 }
114 }
115 }
116}
117
118impl ValidationRule for PossibleFragmentSpreads {
119 fn error_code<'a>(&self) -> &'a str {
120 "PossibleFragmentSpreads"
121 }
122
123 fn validate(
124 &self,
125 ctx: &mut OperationVisitorContext,
126 error_collector: &mut ValidationErrorContext,
127 ) {
128 visit_document(
129 &mut PossibleFragmentSpreads::new(),
130 ctx.operation,
131 ctx,
132 error_collector,
133 );
134 }
135}
136
137#[cfg(test)]
138static RULE_TEST_SCHEMA: &str = "
139 interface Being {
140 name: String
141 }
142 interface Pet implements Being {
143 name: String
144 }
145 type Dog implements Being & Pet {
146 name: String
147 barkVolume: Int
148 }
149 type Cat implements Being & Pet {
150 name: String
151 meowVolume: Int
152 }
153 union CatOrDog = Cat | Dog
154 interface Intelligent {
155 iq: Int
156 }
157 type Human implements Being & Intelligent {
158 name: String
159 pets: [Pet]
160 iq: Int
161 }
162 type Alien implements Being & Intelligent {
163 name: String
164 iq: Int
165 }
166 union DogOrHuman = Dog | Human
167 union HumanOrAlien = Human | Alien
168 type Query {
169 catOrDog: CatOrDog
170 dogOrHuman: DogOrHuman
171 humanOrAlien: HumanOrAlien
172 }
173";
174
175#[test]
176fn of_the_same_object() {
177 use crate::validation::test_utils::*;
178
179 let mut plan = create_plan_from_rule(Box::new(PossibleFragmentSpreads {}));
180 let errors = test_operation_with_schema(
181 "fragment objectWithinObject on Dog { ...dogFragment }
182 fragment dogFragment on Dog { barkVolume }",
183 RULE_TEST_SCHEMA,
184 &mut plan,
185 );
186
187 let messages = get_messages(&errors);
188 assert_eq!(messages.len(), 0);
189}
190
191#[test]
192fn of_the_same_object_with_inline_fragment() {
193 use crate::validation::test_utils::*;
194
195 let mut plan = create_plan_from_rule(Box::new(PossibleFragmentSpreads {}));
196 let errors = test_operation_with_schema(
197 "fragment objectWithinObjectAnon on Dog { ... on Dog { barkVolume } }",
198 RULE_TEST_SCHEMA,
199 &mut plan,
200 );
201
202 let messages = get_messages(&errors);
203 assert_eq!(messages.len(), 0);
204}
205
206#[test]
207fn object_into_an_implemented_interface() {
208 use crate::validation::test_utils::*;
209
210 let mut plan = create_plan_from_rule(Box::new(PossibleFragmentSpreads {}));
211 let errors = test_operation_with_schema(
212 "fragment objectWithinInterface on Pet { ...dogFragment }
213 fragment dogFragment on Dog { barkVolume }",
214 RULE_TEST_SCHEMA,
215 &mut plan,
216 );
217
218 let messages = get_messages(&errors);
219 assert_eq!(messages.len(), 0);
220}
221
222#[test]
223fn object_into_containing_union() {
224 use crate::validation::test_utils::*;
225
226 let mut plan = create_plan_from_rule(Box::new(PossibleFragmentSpreads {}));
227 let errors = test_operation_with_schema(
228 "fragment objectWithinUnion on CatOrDog { ...dogFragment }
229 fragment dogFragment on Dog { barkVolume }",
230 RULE_TEST_SCHEMA,
231 &mut plan,
232 );
233
234 let messages = get_messages(&errors);
235 assert_eq!(messages.len(), 0);
236}
237
238#[test]
239fn union_into_contained_object() {
240 use crate::validation::test_utils::*;
241
242 let mut plan = create_plan_from_rule(Box::new(PossibleFragmentSpreads {}));
243 let errors = test_operation_with_schema(
244 "fragment unionWithinObject on Dog { ...catOrDogFragment }
245 fragment catOrDogFragment on CatOrDog { __typename }",
246 RULE_TEST_SCHEMA,
247 &mut plan,
248 );
249
250 let messages = get_messages(&errors);
251 assert_eq!(messages.len(), 0);
252}
253
254#[test]
255fn union_into_overlapping_interface() {
256 use crate::validation::test_utils::*;
257
258 let mut plan = create_plan_from_rule(Box::new(PossibleFragmentSpreads {}));
259 let errors = test_operation_with_schema(
260 "fragment unionWithinInterface on Pet { ...catOrDogFragment }
261 fragment catOrDogFragment on CatOrDog { __typename }",
262 RULE_TEST_SCHEMA,
263 &mut plan,
264 );
265
266 let messages = get_messages(&errors);
267 assert_eq!(messages.len(), 0);
268}
269
270#[test]
271fn union_into_overlapping_union() {
272 use crate::validation::test_utils::*;
273
274 let mut plan = create_plan_from_rule(Box::new(PossibleFragmentSpreads {}));
275 let errors = test_operation_with_schema(
276 "fragment unionWithinUnion on DogOrHuman { ...catOrDogFragment }
277 fragment catOrDogFragment on CatOrDog { __typename }",
278 RULE_TEST_SCHEMA,
279 &mut plan,
280 );
281
282 let messages = get_messages(&errors);
283 assert_eq!(messages.len(), 0);
284}
285
286#[test]
287fn interface_into_implemented_object() {
288 use crate::validation::test_utils::*;
289
290 let mut plan = create_plan_from_rule(Box::new(PossibleFragmentSpreads {}));
291 let errors = test_operation_with_schema(
292 "fragment interfaceWithinObject on Dog { ...petFragment }
293 fragment petFragment on Pet { name }",
294 RULE_TEST_SCHEMA,
295 &mut plan,
296 );
297
298 let messages = get_messages(&errors);
299 assert_eq!(messages.len(), 0);
300}
301
302#[test]
303fn interface_into_overlapping_interface() {
304 use crate::validation::test_utils::*;
305
306 let mut plan = create_plan_from_rule(Box::new(PossibleFragmentSpreads {}));
307 let errors = test_operation_with_schema(
308 "fragment interfaceWithinInterface on Pet { ...beingFragment }
309 fragment beingFragment on Being { name }",
310 RULE_TEST_SCHEMA,
311 &mut plan,
312 );
313
314 let messages = get_messages(&errors);
315 assert_eq!(messages.len(), 0);
316}
317
318#[test]
319fn interface_into_overlapping_interface_in_inline_fragment() {
320 use crate::validation::test_utils::*;
321
322 let mut plan = create_plan_from_rule(Box::new(PossibleFragmentSpreads {}));
323 let errors = test_operation_with_schema(
324 "fragment interfaceWithinInterface on Pet { ... on Being { name } }",
325 RULE_TEST_SCHEMA,
326 &mut plan,
327 );
328
329 let messages = get_messages(&errors);
330 assert_eq!(messages.len(), 0);
331}
332
333#[test]
334fn interface_into_overlapping_union() {
335 use crate::validation::test_utils::*;
336
337 let mut plan = create_plan_from_rule(Box::new(PossibleFragmentSpreads {}));
338 let errors = test_operation_with_schema(
339 "fragment interfaceWithinUnion on CatOrDog { ...petFragment }
340 fragment petFragment on Pet { name }",
341 RULE_TEST_SCHEMA,
342 &mut plan,
343 );
344
345 let messages = get_messages(&errors);
346 assert_eq!(messages.len(), 0);
347}
348
349#[test]
351fn ignores_incorrect_type() {
352 use crate::validation::test_utils::*;
353
354 let mut plan = create_plan_from_rule(Box::new(PossibleFragmentSpreads {}));
355 let errors = test_operation_with_schema(
356 "fragment petFragment on Pet { ...badInADifferentWay }
357 fragment badInADifferentWay on String { name }",
358 RULE_TEST_SCHEMA,
359 &mut plan,
360 );
361
362 let messages = get_messages(&errors);
363 assert_eq!(messages.len(), 0);
364}
365
366#[test]
368fn ignores_unknown_fragments() {
369 use crate::validation::test_utils::*;
370
371 let mut plan = create_plan_from_rule(Box::new(PossibleFragmentSpreads {}));
372 let errors = test_operation_with_schema(
373 "fragment petFragment on Pet { ...UnknownFragment }",
374 RULE_TEST_SCHEMA,
375 &mut plan,
376 );
377
378 let messages = get_messages(&errors);
379 assert_eq!(messages.len(), 0);
380}
381
382#[test]
383fn different_object_into_object() {
384 use crate::validation::test_utils::*;
385
386 let mut plan = create_plan_from_rule(Box::new(PossibleFragmentSpreads {}));
387 let errors = test_operation_with_schema(
388 "fragment invalidObjectWithinObject on Cat { ...dogFragment }
389 fragment dogFragment on Dog { barkVolume }",
390 RULE_TEST_SCHEMA,
391 &mut plan,
392 );
393
394 let messages = get_messages(&errors);
395 assert_eq!(messages.len(), 1);
396 assert_eq!(messages, vec![
397 "Fragment \"dogFragment\" cannot be spread here as objects of type \"Cat\" can never be of type \"Dog\"."
398 ])
399}
400
401#[test]
402fn different_object_into_object_in_inline_fragment() {
403 use crate::validation::test_utils::*;
404
405 let mut plan = create_plan_from_rule(Box::new(PossibleFragmentSpreads {}));
406 let errors = test_operation_with_schema(
407 "fragment invalidObjectWithinObjectAnon on Cat {
408 ... on Dog { barkVolume }
409 }",
410 RULE_TEST_SCHEMA,
411 &mut plan,
412 );
413
414 let messages = get_messages(&errors);
415 assert_eq!(messages.len(), 1);
416 assert_eq!(
417 messages,
418 vec![
419 "Fragment cannot be spread here as objects of type \"Cat\" can never be of type \"Dog\"."
420 ]
421 )
422}
423
424#[test]
425fn object_into_not_implementing_interface() {
426 use crate::validation::test_utils::*;
427
428 let mut plan = create_plan_from_rule(Box::new(PossibleFragmentSpreads {}));
429 let errors = test_operation_with_schema(
430 "fragment invalidObjectWithinInterface on Pet { ...humanFragment }
431 fragment humanFragment on Human { pets { name } }",
432 RULE_TEST_SCHEMA,
433 &mut plan,
434 );
435
436 let messages = get_messages(&errors);
437 assert_eq!(messages.len(), 1);
438 assert_eq!(
439 messages,
440 vec![
441 "Fragment \"humanFragment\" cannot be spread here as objects of type \"Pet\" can never be of type \"Human\"."
442 ]
443 )
444}
445
446#[test]
447fn object_into_not_containing_union() {
448 use crate::validation::test_utils::*;
449
450 let mut plan = create_plan_from_rule(Box::new(PossibleFragmentSpreads {}));
451 let errors = test_operation_with_schema(
452 "fragment invalidObjectWithinUnion on CatOrDog { ...humanFragment }
453 fragment humanFragment on Human { pets { name } }",
454 RULE_TEST_SCHEMA,
455 &mut plan,
456 );
457
458 let messages = get_messages(&errors);
459 assert_eq!(messages.len(), 1);
460 assert_eq!(
461 messages,
462 vec![
463 "Fragment \"humanFragment\" cannot be spread here as objects of type \"CatOrDog\" can never be of type \"Human\"."
464 ]
465 )
466}
467
468#[test]
469fn union_into_not_contained_object() {
470 use crate::validation::test_utils::*;
471
472 let mut plan = create_plan_from_rule(Box::new(PossibleFragmentSpreads {}));
473 let errors = test_operation_with_schema(
474 "fragment invalidUnionWithinObject on Human { ...catOrDogFragment }
475 fragment catOrDogFragment on CatOrDog { __typename }",
476 RULE_TEST_SCHEMA,
477 &mut plan,
478 );
479
480 let messages = get_messages(&errors);
481 assert_eq!(messages.len(), 1);
482 assert_eq!(
483 messages,
484 vec![
485 "Fragment \"catOrDogFragment\" cannot be spread here as objects of type \"Human\" can never be of type \"CatOrDog\"."
486 ]
487 )
488}
489
490#[test]
491fn union_into_non_overlapping_interface() {
492 use crate::validation::test_utils::*;
493
494 let mut plan = create_plan_from_rule(Box::new(PossibleFragmentSpreads {}));
495 let errors = test_operation_with_schema(
496 "fragment invalidUnionWithinInterface on Pet { ...humanOrAlienFragment }
497 fragment humanOrAlienFragment on HumanOrAlien { __typename }",
498 RULE_TEST_SCHEMA,
499 &mut plan,
500 );
501
502 let messages = get_messages(&errors);
503 assert_eq!(messages.len(), 1);
504 assert_eq!(
505 messages,
506 vec![
507 "Fragment \"humanOrAlienFragment\" cannot be spread here as objects of type \"Pet\" can never be of type \"HumanOrAlien\"."
508 ]
509 )
510}
511
512#[test]
513fn union_into_non_overlapping_union() {
514 use crate::validation::test_utils::*;
515
516 let mut plan = create_plan_from_rule(Box::new(PossibleFragmentSpreads {}));
517 let errors = test_operation_with_schema(
518 "fragment invalidUnionWithinUnion on CatOrDog { ...humanOrAlienFragment }
519 fragment humanOrAlienFragment on HumanOrAlien { __typename }",
520 RULE_TEST_SCHEMA,
521 &mut plan,
522 );
523
524 let messages = get_messages(&errors);
525 assert_eq!(messages.len(), 1);
526 assert_eq!(
527 messages,
528 vec![
529 "Fragment \"humanOrAlienFragment\" cannot be spread here as objects of type \"CatOrDog\" can never be of type \"HumanOrAlien\"."
530 ]
531 )
532}
533
534#[test]
535fn interface_into_non_implementing_object() {
536 use crate::validation::test_utils::*;
537
538 let mut plan = create_plan_from_rule(Box::new(PossibleFragmentSpreads {}));
539 let errors = test_operation_with_schema(
540 "fragment invalidInterfaceWithinObject on Cat { ...intelligentFragment }
541 fragment intelligentFragment on Intelligent { iq }",
542 RULE_TEST_SCHEMA,
543 &mut plan,
544 );
545
546 let messages = get_messages(&errors);
547 assert_eq!(messages.len(), 1);
548 assert_eq!(
549 messages,
550 vec![
551 "Fragment \"intelligentFragment\" cannot be spread here as objects of type \"Cat\" can never be of type \"Intelligent\"."
552 ]
553 )
554}
555
556#[test]
557fn interface_into_non_overlapping_interface() {
558 use crate::validation::test_utils::*;
559
560 let mut plan = create_plan_from_rule(Box::new(PossibleFragmentSpreads {}));
561 let errors = test_operation_with_schema(
562 "fragment invalidInterfaceWithinInterface on Pet {
563 ...intelligentFragment
564 }
565 fragment intelligentFragment on Intelligent { iq }",
566 RULE_TEST_SCHEMA,
567 &mut plan,
568 );
569
570 let messages = get_messages(&errors);
571 assert_eq!(messages.len(), 1);
572 assert_eq!(
573 messages,
574 vec![
575 "Fragment \"intelligentFragment\" cannot be spread here as objects of type \"Pet\" can never be of type \"Intelligent\"."
576 ]
577 )
578}
579
580#[test]
581fn interface_into_non_overlapping_interface_in_inline_fragment() {
582 use crate::validation::test_utils::*;
583
584 let mut plan = create_plan_from_rule(Box::new(PossibleFragmentSpreads {}));
585 let errors = test_operation_with_schema(
586 "fragment invalidInterfaceWithinInterfaceAnon on Pet {
587 ...on Intelligent { iq }
588 }",
589 RULE_TEST_SCHEMA,
590 &mut plan,
591 );
592
593 let messages = get_messages(&errors);
594 assert_eq!(messages.len(), 1);
595 assert_eq!(
596 messages,
597 vec![
598 "Fragment cannot be spread here as objects of type \"Pet\" can never be of type \"Intelligent\"."
599 ]
600 )
601}