Skip to main content

graphql_tools/validation/rules/
possible_fragment_spreads.rs

1use 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
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<'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// caught by FragmentsOnCompositeTypesRule
350#[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// caught by KnownFragmentNamesRule
367#[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}