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