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