Skip to main content

graphql_tools/validation/rules/
possible_fragment_spreads.rs

1use 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
7/// Possible fragment spread
8///
9/// A fragment spread is only valid if the type condition could ever possibly
10/// be true: if there is a non-empty intersection of the possible parent types,
11/// and possible types which pass the type condition.
12///
13/// https://spec.graphql.org/draft/#sec-Fragment-spread-is-possible
14pub 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
28/**
29 * Provided two composite types, determine if they "overlap". Two composite
30 * types overlap when the Sets of possible concrete types for each intersect.
31 *
32 * This is often used to determine if a fragment of a given type could possibly
33 * be visited in a context of another type.
34 *
35 * This function is commutative.
36 */
37pub 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// caught by FragmentsOnCompositeTypesRule
341#[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// caught by KnownFragmentNamesRule
358#[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}