graphql_tools/validation/rules/
possible_fragment_spreads.rs1use super::ValidationRule;
2use crate::ast::{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<'doc> OperationVisitor<'doc, 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(&self) -> &'static str {
120 "PossibleFragmentSpreads"
121 }
122
123 fn visitor<'doc>(&self) -> super::ValidationVisitor<'doc> {
124 Box::new(PossibleFragmentSpreads::new())
125 }
126}
127
128#[cfg(test)]
129static RULE_TEST_SCHEMA: &str = "
130 interface Being {
131 name: String
132 }
133 interface Pet implements Being {
134 name: String
135 }
136 type Dog implements Being & Pet {
137 name: String
138 barkVolume: Int
139 }
140 type Cat implements Being & Pet {
141 name: String
142 meowVolume: Int
143 }
144 union CatOrDog = Cat | Dog
145 interface Intelligent {
146 iq: Int
147 }
148 type Human implements Being & Intelligent {
149 name: String
150 pets: [Pet]
151 iq: Int
152 }
153 type Alien implements Being & Intelligent {
154 name: String
155 iq: Int
156 }
157 union DogOrHuman = Dog | Human
158 union HumanOrAlien = Human | Alien
159 type Query {
160 catOrDog: CatOrDog
161 dogOrHuman: DogOrHuman
162 humanOrAlien: HumanOrAlien
163 }
164";
165
166#[test]
167fn of_the_same_object() {
168 use crate::validation::test_utils::*;
169
170 let mut plan = create_plan_from_rule(Box::new(PossibleFragmentSpreads {}));
171 let errors = test_operation_with_schema(
172 "fragment objectWithinObject on Dog { ...dogFragment }
173 fragment dogFragment on Dog { barkVolume }",
174 RULE_TEST_SCHEMA,
175 &mut plan,
176 );
177
178 let messages = get_messages(&errors);
179 assert_eq!(messages.len(), 0);
180}
181
182#[test]
183fn of_the_same_object_with_inline_fragment() {
184 use crate::validation::test_utils::*;
185
186 let mut plan = create_plan_from_rule(Box::new(PossibleFragmentSpreads {}));
187 let errors = test_operation_with_schema(
188 "fragment objectWithinObjectAnon on Dog { ... on Dog { barkVolume } }",
189 RULE_TEST_SCHEMA,
190 &mut plan,
191 );
192
193 let messages = get_messages(&errors);
194 assert_eq!(messages.len(), 0);
195}
196
197#[test]
198fn object_into_an_implemented_interface() {
199 use crate::validation::test_utils::*;
200
201 let mut plan = create_plan_from_rule(Box::new(PossibleFragmentSpreads {}));
202 let errors = test_operation_with_schema(
203 "fragment objectWithinInterface on Pet { ...dogFragment }
204 fragment dogFragment on Dog { barkVolume }",
205 RULE_TEST_SCHEMA,
206 &mut plan,
207 );
208
209 let messages = get_messages(&errors);
210 assert_eq!(messages.len(), 0);
211}
212
213#[test]
214fn object_into_containing_union() {
215 use crate::validation::test_utils::*;
216
217 let mut plan = create_plan_from_rule(Box::new(PossibleFragmentSpreads {}));
218 let errors = test_operation_with_schema(
219 "fragment objectWithinUnion on CatOrDog { ...dogFragment }
220 fragment dogFragment on Dog { barkVolume }",
221 RULE_TEST_SCHEMA,
222 &mut plan,
223 );
224
225 let messages = get_messages(&errors);
226 assert_eq!(messages.len(), 0);
227}
228
229#[test]
230fn union_into_contained_object() {
231 use crate::validation::test_utils::*;
232
233 let mut plan = create_plan_from_rule(Box::new(PossibleFragmentSpreads {}));
234 let errors = test_operation_with_schema(
235 "fragment unionWithinObject on Dog { ...catOrDogFragment }
236 fragment catOrDogFragment on CatOrDog { __typename }",
237 RULE_TEST_SCHEMA,
238 &mut plan,
239 );
240
241 let messages = get_messages(&errors);
242 assert_eq!(messages.len(), 0);
243}
244
245#[test]
246fn union_into_overlapping_interface() {
247 use crate::validation::test_utils::*;
248
249 let mut plan = create_plan_from_rule(Box::new(PossibleFragmentSpreads {}));
250 let errors = test_operation_with_schema(
251 "fragment unionWithinInterface on Pet { ...catOrDogFragment }
252 fragment catOrDogFragment on CatOrDog { __typename }",
253 RULE_TEST_SCHEMA,
254 &mut plan,
255 );
256
257 let messages = get_messages(&errors);
258 assert_eq!(messages.len(), 0);
259}
260
261#[test]
262fn union_into_overlapping_union() {
263 use crate::validation::test_utils::*;
264
265 let mut plan = create_plan_from_rule(Box::new(PossibleFragmentSpreads {}));
266 let errors = test_operation_with_schema(
267 "fragment unionWithinUnion on DogOrHuman { ...catOrDogFragment }
268 fragment catOrDogFragment on CatOrDog { __typename }",
269 RULE_TEST_SCHEMA,
270 &mut plan,
271 );
272
273 let messages = get_messages(&errors);
274 assert_eq!(messages.len(), 0);
275}
276
277#[test]
278fn interface_into_implemented_object() {
279 use crate::validation::test_utils::*;
280
281 let mut plan = create_plan_from_rule(Box::new(PossibleFragmentSpreads {}));
282 let errors = test_operation_with_schema(
283 "fragment interfaceWithinObject on Dog { ...petFragment }
284 fragment petFragment on Pet { name }",
285 RULE_TEST_SCHEMA,
286 &mut plan,
287 );
288
289 let messages = get_messages(&errors);
290 assert_eq!(messages.len(), 0);
291}
292
293#[test]
294fn interface_into_overlapping_interface() {
295 use crate::validation::test_utils::*;
296
297 let mut plan = create_plan_from_rule(Box::new(PossibleFragmentSpreads {}));
298 let errors = test_operation_with_schema(
299 "fragment interfaceWithinInterface on Pet { ...beingFragment }
300 fragment beingFragment on Being { name }",
301 RULE_TEST_SCHEMA,
302 &mut plan,
303 );
304
305 let messages = get_messages(&errors);
306 assert_eq!(messages.len(), 0);
307}
308
309#[test]
310fn interface_into_overlapping_interface_in_inline_fragment() {
311 use crate::validation::test_utils::*;
312
313 let mut plan = create_plan_from_rule(Box::new(PossibleFragmentSpreads {}));
314 let errors = test_operation_with_schema(
315 "fragment interfaceWithinInterface on Pet { ... on Being { name } }",
316 RULE_TEST_SCHEMA,
317 &mut plan,
318 );
319
320 let messages = get_messages(&errors);
321 assert_eq!(messages.len(), 0);
322}
323
324#[test]
325fn interface_into_overlapping_union() {
326 use crate::validation::test_utils::*;
327
328 let mut plan = create_plan_from_rule(Box::new(PossibleFragmentSpreads {}));
329 let errors = test_operation_with_schema(
330 "fragment interfaceWithinUnion on CatOrDog { ...petFragment }
331 fragment petFragment on Pet { name }",
332 RULE_TEST_SCHEMA,
333 &mut plan,
334 );
335
336 let messages = get_messages(&errors);
337 assert_eq!(messages.len(), 0);
338}
339
340#[test]
342fn ignores_incorrect_type() {
343 use crate::validation::test_utils::*;
344
345 let mut plan = create_plan_from_rule(Box::new(PossibleFragmentSpreads {}));
346 let errors = test_operation_with_schema(
347 "fragment petFragment on Pet { ...badInADifferentWay }
348 fragment badInADifferentWay on String { name }",
349 RULE_TEST_SCHEMA,
350 &mut plan,
351 );
352
353 let messages = get_messages(&errors);
354 assert_eq!(messages.len(), 0);
355}
356
357#[test]
359fn ignores_unknown_fragments() {
360 use crate::validation::test_utils::*;
361
362 let mut plan = create_plan_from_rule(Box::new(PossibleFragmentSpreads {}));
363 let errors = test_operation_with_schema(
364 "fragment petFragment on Pet { ...UnknownFragment }",
365 RULE_TEST_SCHEMA,
366 &mut plan,
367 );
368
369 let messages = get_messages(&errors);
370 assert_eq!(messages.len(), 0);
371}
372
373#[test]
374fn different_object_into_object() {
375 use crate::validation::test_utils::*;
376
377 let mut plan = create_plan_from_rule(Box::new(PossibleFragmentSpreads {}));
378 let errors = test_operation_with_schema(
379 "fragment invalidObjectWithinObject on Cat { ...dogFragment }
380 fragment dogFragment on Dog { barkVolume }",
381 RULE_TEST_SCHEMA,
382 &mut plan,
383 );
384
385 let messages = get_messages(&errors);
386 assert_eq!(messages.len(), 1);
387 assert_eq!(messages, vec![
388 "Fragment \"dogFragment\" cannot be spread here as objects of type \"Cat\" can never be of type \"Dog\"."
389 ])
390}
391
392#[test]
393fn different_object_into_object_in_inline_fragment() {
394 use crate::validation::test_utils::*;
395
396 let mut plan = create_plan_from_rule(Box::new(PossibleFragmentSpreads {}));
397 let errors = test_operation_with_schema(
398 "fragment invalidObjectWithinObjectAnon on Cat {
399 ... on Dog { barkVolume }
400 }",
401 RULE_TEST_SCHEMA,
402 &mut plan,
403 );
404
405 let messages = get_messages(&errors);
406 assert_eq!(messages.len(), 1);
407 assert_eq!(
408 messages,
409 vec![
410 "Fragment cannot be spread here as objects of type \"Cat\" can never be of type \"Dog\"."
411 ]
412 )
413}
414
415#[test]
416fn object_into_not_implementing_interface() {
417 use crate::validation::test_utils::*;
418
419 let mut plan = create_plan_from_rule(Box::new(PossibleFragmentSpreads {}));
420 let errors = test_operation_with_schema(
421 "fragment invalidObjectWithinInterface on Pet { ...humanFragment }
422 fragment humanFragment on Human { pets { name } }",
423 RULE_TEST_SCHEMA,
424 &mut plan,
425 );
426
427 let messages = get_messages(&errors);
428 assert_eq!(messages.len(), 1);
429 assert_eq!(
430 messages,
431 vec![
432 "Fragment \"humanFragment\" cannot be spread here as objects of type \"Pet\" can never be of type \"Human\"."
433 ]
434 )
435}
436
437#[test]
438fn object_into_not_containing_union() {
439 use crate::validation::test_utils::*;
440
441 let mut plan = create_plan_from_rule(Box::new(PossibleFragmentSpreads {}));
442 let errors = test_operation_with_schema(
443 "fragment invalidObjectWithinUnion on CatOrDog { ...humanFragment }
444 fragment humanFragment on Human { pets { name } }",
445 RULE_TEST_SCHEMA,
446 &mut plan,
447 );
448
449 let messages = get_messages(&errors);
450 assert_eq!(messages.len(), 1);
451 assert_eq!(
452 messages,
453 vec![
454 "Fragment \"humanFragment\" cannot be spread here as objects of type \"CatOrDog\" can never be of type \"Human\"."
455 ]
456 )
457}
458
459#[test]
460fn union_into_not_contained_object() {
461 use crate::validation::test_utils::*;
462
463 let mut plan = create_plan_from_rule(Box::new(PossibleFragmentSpreads {}));
464 let errors = test_operation_with_schema(
465 "fragment invalidUnionWithinObject on Human { ...catOrDogFragment }
466 fragment catOrDogFragment on CatOrDog { __typename }",
467 RULE_TEST_SCHEMA,
468 &mut plan,
469 );
470
471 let messages = get_messages(&errors);
472 assert_eq!(messages.len(), 1);
473 assert_eq!(
474 messages,
475 vec![
476 "Fragment \"catOrDogFragment\" cannot be spread here as objects of type \"Human\" can never be of type \"CatOrDog\"."
477 ]
478 )
479}
480
481#[test]
482fn union_into_non_overlapping_interface() {
483 use crate::validation::test_utils::*;
484
485 let mut plan = create_plan_from_rule(Box::new(PossibleFragmentSpreads {}));
486 let errors = test_operation_with_schema(
487 "fragment invalidUnionWithinInterface on Pet { ...humanOrAlienFragment }
488 fragment humanOrAlienFragment on HumanOrAlien { __typename }",
489 RULE_TEST_SCHEMA,
490 &mut plan,
491 );
492
493 let messages = get_messages(&errors);
494 assert_eq!(messages.len(), 1);
495 assert_eq!(
496 messages,
497 vec![
498 "Fragment \"humanOrAlienFragment\" cannot be spread here as objects of type \"Pet\" can never be of type \"HumanOrAlien\"."
499 ]
500 )
501}
502
503#[test]
504fn union_into_non_overlapping_union() {
505 use crate::validation::test_utils::*;
506
507 let mut plan = create_plan_from_rule(Box::new(PossibleFragmentSpreads {}));
508 let errors = test_operation_with_schema(
509 "fragment invalidUnionWithinUnion on CatOrDog { ...humanOrAlienFragment }
510 fragment humanOrAlienFragment on HumanOrAlien { __typename }",
511 RULE_TEST_SCHEMA,
512 &mut plan,
513 );
514
515 let messages = get_messages(&errors);
516 assert_eq!(messages.len(), 1);
517 assert_eq!(
518 messages,
519 vec![
520 "Fragment \"humanOrAlienFragment\" cannot be spread here as objects of type \"CatOrDog\" can never be of type \"HumanOrAlien\"."
521 ]
522 )
523}
524
525#[test]
526fn interface_into_non_implementing_object() {
527 use crate::validation::test_utils::*;
528
529 let mut plan = create_plan_from_rule(Box::new(PossibleFragmentSpreads {}));
530 let errors = test_operation_with_schema(
531 "fragment invalidInterfaceWithinObject on Cat { ...intelligentFragment }
532 fragment intelligentFragment on Intelligent { iq }",
533 RULE_TEST_SCHEMA,
534 &mut plan,
535 );
536
537 let messages = get_messages(&errors);
538 assert_eq!(messages.len(), 1);
539 assert_eq!(
540 messages,
541 vec![
542 "Fragment \"intelligentFragment\" cannot be spread here as objects of type \"Cat\" can never be of type \"Intelligent\"."
543 ]
544 )
545}
546
547#[test]
548fn interface_into_non_overlapping_interface() {
549 use crate::validation::test_utils::*;
550
551 let mut plan = create_plan_from_rule(Box::new(PossibleFragmentSpreads {}));
552 let errors = test_operation_with_schema(
553 "fragment invalidInterfaceWithinInterface on Pet {
554 ...intelligentFragment
555 }
556 fragment intelligentFragment on Intelligent { iq }",
557 RULE_TEST_SCHEMA,
558 &mut plan,
559 );
560
561 let messages = get_messages(&errors);
562 assert_eq!(messages.len(), 1);
563 assert_eq!(
564 messages,
565 vec![
566 "Fragment \"intelligentFragment\" cannot be spread here as objects of type \"Pet\" can never be of type \"Intelligent\"."
567 ]
568 )
569}
570
571#[test]
572fn interface_into_non_overlapping_interface_in_inline_fragment() {
573 use crate::validation::test_utils::*;
574
575 let mut plan = create_plan_from_rule(Box::new(PossibleFragmentSpreads {}));
576 let errors = test_operation_with_schema(
577 "fragment invalidInterfaceWithinInterfaceAnon on Pet {
578 ...on Intelligent { iq }
579 }",
580 RULE_TEST_SCHEMA,
581 &mut plan,
582 );
583
584 let messages = get_messages(&errors);
585 assert_eq!(messages.len(), 1);
586 assert_eq!(
587 messages,
588 vec![
589 "Fragment cannot be spread here as objects of type \"Pet\" can never be of type \"Intelligent\"."
590 ]
591 )
592}
593
594#[test]
595fn interface_into_non_overlapping_union() {
596 use crate::validation::test_utils::*;
597
598 let mut plan = create_plan_from_rule(Box::new(PossibleFragmentSpreads {}));
599 let errors = test_operation_with_schema(
600 "fragment invalidInterfaceWithinUnion on CatOrDog { ...intelligentFragment }
601 fragment intelligentFragment on Intelligent { iq }",
602 RULE_TEST_SCHEMA,
603 &mut plan,
604 );
605
606 let messages = get_messages(&errors);
607 assert_eq!(messages.len(), 1);
608 assert_eq!(
609 messages,
610 vec![
611 "Fragment \"intelligentFragment\" cannot be spread here as objects of type \"CatOrDog\" can never be of type \"Intelligent\"."
612 ]
613 )
614}