graphql_tools/validation/rules/
overlapping_fields_can_be_merged.rs

1use crate::parser::query::{Definition, TypeCondition};
2use crate::parser::Pos;
3
4use super::ValidationRule;
5use crate::ast::ext::TypeDefinitionExtension;
6use crate::ast::{
7    visit_document, FieldByNameExtension, OperationVisitor, OperationVisitorContext,
8    SchemaDocumentExtension, TypeExtension, ValueExtension,
9};
10use crate::static_graphql::query::*;
11use crate::static_graphql::schema::{
12    Document as SchemaDocument, Field as FieldDefinition, TypeDefinition,
13};
14use crate::validation::utils::{ValidationError, ValidationErrorContext};
15use std::borrow::Borrow;
16use std::collections::HashMap;
17use std::fmt::Debug;
18use std::hash::Hash;
19/// Overlapping fields can be merged
20///
21/// A selection set is only valid if all fields (including spreading any
22/// fragments) either correspond to distinct response names or can be merged
23/// without ambiguity.
24///
25/// See https://spec.graphql.org/draft/#sec-Field-Selection-Merging
26pub struct OverlappingFieldsCanBeMerged<'a> {
27    named_fragments: HashMap<&'a str, &'a FragmentDefinition>,
28    compared_fragments: PairSet<'a>,
29}
30
31/**
32 * Algorithm:
33 *
34 * Conflicts occur when two fields exist in a query which will produce the same
35 * response name, but represent differing values, thus creating a conflict.
36 * The algorithm below finds all conflicts via making a series of comparisons
37 * between fields. In order to compare as few fields as possible, this makes
38 * a series of comparisons "within" sets of fields and "between" sets of fields.
39 *
40 * Given any selection set, a collection produces both a set of fields by
41 * also including all inline fragments, as well as a list of fragments
42 * referenced by fragment spreads.
43 *
44 * A) Each selection set represented in the document first compares "within" its
45 * collected set of fields, finding any conflicts between every pair of
46 * overlapping fields.
47 * Note: This is the *only time* that a the fields "within" a set are compared
48 * to each other. After this only fields "between" sets are compared.
49 *
50 * B) Also, if any fragment is referenced in a selection set, then a
51 * comparison is made "between" the original set of fields and the
52 * referenced fragment.
53 *
54 * C) Also, if multiple fragments are referenced, then comparisons
55 * are made "between" each referenced fragment.
56 *
57 * D) When comparing "between" a set of fields and a referenced fragment, first
58 * a comparison is made between each field in the original set of fields and
59 * each field in the the referenced set of fields.
60 *
61 * E) Also, if any fragment is referenced in the referenced selection set,
62 * then a comparison is made "between" the original set of fields and the
63 * referenced fragment (recursively referring to step D).
64 *
65 * F) When comparing "between" two fragments, first a comparison is made between
66 * each field in the first referenced set of fields and each field in the the
67 * second referenced set of fields.
68 *
69 * G) Also, any fragments referenced by the first must be compared to the
70 * second, and any fragments referenced by the second must be compared to the
71 * first (recursively referring to step F).
72 *
73 * H) When comparing two fields, if both have selection sets, then a comparison
74 * is made "between" both selection sets, first comparing the set of fields in
75 * the first selection set with the set of fields in the second.
76 *
77 * I) Also, if any fragment is referenced in either selection set, then a
78 * comparison is made "between" the other set of fields and the
79 * referenced fragment.
80 *
81 * J) Also, if two fragments are referenced in both selection sets, then a
82 * comparison is made "between" the two fragments.
83 *
84 */
85
86#[derive(Debug)]
87struct Conflict(ConflictReason, Vec<Pos>, Vec<Pos>);
88
89#[derive(Debug, Clone, PartialEq, Eq, Hash)]
90struct ConflictReason(String, ConflictReasonMessage);
91
92#[derive(Debug)]
93struct AstAndDef<'a>(
94    Option<&'a TypeDefinition>,
95    &'a Field,
96    Option<&'a FieldDefinition>,
97);
98
99#[derive(Debug, Clone, PartialEq, Eq, Hash)]
100enum ConflictReasonMessage {
101    Message(String),
102    Nested(Vec<ConflictReason>),
103}
104
105struct PairSet<'a> {
106    data: HashMap<&'a str, HashMap<&'a str, bool>>,
107}
108
109struct OrderedMap<K, V> {
110    data: HashMap<K, V>,
111    insert_order: Vec<K>,
112}
113
114struct OrderedMapIter<'a, K: 'a, V: 'a> {
115    map: &'a HashMap<K, V>,
116    inner: ::std::slice::Iter<'a, K>,
117}
118
119impl<K: Eq + Hash + Clone, V> OrderedMap<K, V> {
120    fn new() -> OrderedMap<K, V> {
121        OrderedMap {
122            data: HashMap::new(),
123            insert_order: Vec::new(),
124        }
125    }
126
127    fn iter(&self) -> OrderedMapIter<K, V> {
128        OrderedMapIter {
129            map: &self.data,
130            inner: self.insert_order.iter(),
131        }
132    }
133
134    fn get<Q: ?Sized>(&self, k: &Q) -> Option<&V>
135    where
136        K: Borrow<Q>,
137        Q: Hash + Eq,
138    {
139        self.data.get(k)
140    }
141
142    fn get_mut<Q: ?Sized>(&mut self, k: &Q) -> Option<&mut V>
143    where
144        K: Borrow<Q>,
145        Q: Hash + Eq,
146    {
147        self.data.get_mut(k)
148    }
149
150    fn contains_key<Q: ?Sized>(&self, k: &Q) -> bool
151    where
152        K: Borrow<Q>,
153        Q: Hash + Eq,
154    {
155        self.data.contains_key(k)
156    }
157
158    fn insert(&mut self, k: K, v: V) -> Option<V> {
159        let result = self.data.insert(k.clone(), v);
160        if result.is_none() {
161            self.insert_order.push(k);
162        }
163        result
164    }
165}
166
167impl<'a, K: Eq + Hash + 'a, V: 'a> Iterator for OrderedMapIter<'a, K, V> {
168    type Item = (&'a K, &'a V);
169
170    fn next(&mut self) -> Option<Self::Item> {
171        self.inner
172            .next()
173            .and_then(|key| self.map.get(key).map(|value| (key, value)))
174    }
175}
176
177impl<'a> PairSet<'a> {
178    fn new() -> PairSet<'a> {
179        PairSet {
180            data: HashMap::new(),
181        }
182    }
183
184    pub fn contains(&self, a: &str, b: &str, mutex: bool) -> bool {
185        if let Some(result) = self.data.get(a).and_then(|s| s.get(b)) {
186            if !mutex {
187                !result
188            } else {
189                true
190            }
191        } else {
192            false
193        }
194    }
195
196    pub fn insert(&mut self, a: &'a str, b: &'a str, mutex: bool) {
197        self.data.entry(a).or_default().insert(b, mutex);
198
199        self.data.entry(b).or_default().insert(a, mutex);
200    }
201}
202
203impl<'a> Default for OverlappingFieldsCanBeMerged<'a> {
204    fn default() -> Self {
205        Self::new()
206    }
207}
208
209impl<'a> OverlappingFieldsCanBeMerged<'a> {
210    pub fn new() -> Self {
211        Self {
212            named_fragments: HashMap::new(),
213            compared_fragments: PairSet::new(),
214        }
215    }
216
217    // Find all conflicts found "within" a selection set, including those found
218    // via spreading in fragments. Called when visiting each SelectionSet in the
219    // GraphQL Document.
220    fn find_conflicts_within_selection_set(
221        &mut self,
222        schema: &'a SchemaDocument,
223        parent_type: Option<&'a TypeDefinition>,
224        selection_set: &'a SelectionSet,
225        visited_fragments: &mut Vec<&'a str>,
226    ) -> Vec<Conflict> {
227        let mut conflicts = Vec::<Conflict>::new();
228
229        let (field_map, fragment_names) =
230            self.get_fields_and_fragment_names(schema, parent_type, selection_set);
231
232        // (A) Find find all conflicts "within" the fields of this selection set.
233        // Note: this is the *only place* `collect_conflicts_within` is called.
234        self.collect_conflicts_within(schema, &mut conflicts, &field_map, visited_fragments);
235
236        // (B) Then collect conflicts between these fields and those represented by
237        // each spread fragment name found.
238        for (i, frag_name1) in fragment_names.iter().enumerate() {
239            self.collect_conflicts_between_fields_and_fragment(
240                schema,
241                &mut conflicts,
242                &field_map,
243                frag_name1,
244                false,
245                visited_fragments,
246            );
247
248            // (C) Then compare this fragment with all other fragments found in this
249            // selection set to collect conflicts between fragments spread together.
250            // This compares each item in the list of fragment names to every other
251            // item in that same list (except for itself).
252            for frag_name2 in &fragment_names[i + 1..] {
253                self.collect_conflicts_between_fragments(
254                    schema,
255                    &mut conflicts,
256                    frag_name1,
257                    frag_name2,
258                    false,
259                    visited_fragments,
260                );
261            }
262        }
263
264        conflicts
265    }
266
267    // Collect all Conflicts "within" one collection of fields.
268    fn collect_conflicts_within(
269        &mut self,
270        schema: &'a SchemaDocument,
271        conflicts: &mut Vec<Conflict>,
272        field_map: &OrderedMap<&'a str, Vec<AstAndDef<'a>>>,
273        visited_fragments: &mut Vec<&'a str>,
274    ) {
275        // A field map is a keyed collection, where each key represents a response
276        // name and the value at that key is a list of all fields which provide that
277        // response name. For every response name, if there are multiple fields, they
278        // must be compared to find a potential conflict.
279        for (out_field_name, fields) in field_map.iter() {
280            // This compares every field in the list to every other field in this list
281            // (except to itself). If the list only has one item, nothing needs to
282            // be compared.
283            for (index, first) in fields.iter().enumerate() {
284                for second in &fields[index + 1..] {
285                    if let Some(conflict) = self.find_conflict(
286                        schema,
287                        out_field_name,
288                        first,
289                        second,
290                        false, // within one collection is never mutually exclusive
291                        visited_fragments,
292                    ) {
293                        conflicts.push(conflict)
294                    }
295                }
296            }
297        }
298    }
299
300    fn is_same_arguments(&self, f1_args: &[(String, Value)], f2_args: &[(String, Value)]) -> bool {
301        if f1_args.len() != f2_args.len() {
302            return false;
303        }
304
305        f1_args.iter().all(|(n1, v1)| {
306            if let Some((_, v2)) = f2_args.iter().find(|&(n2, _)| n1.eq(n2)) {
307                v1.compare(v2)
308            } else {
309                false
310            }
311        })
312    }
313
314    // Two types conflict if both types could not apply to a value simultaneously.
315    // Composite types are ignored as their individual field types will be compared
316    // later recursively. However List and Non-Null types must match.
317    fn is_type_conflict(schema: &SchemaDocument, t1: &Type, t2: &Type) -> bool {
318        if let Type::ListType(t1) = t1 {
319            if let Type::ListType(t2) = t2 {
320                return Self::is_type_conflict(schema, t1, t2);
321            } else {
322                return true;
323            }
324        }
325
326        if let Type::ListType(_) = t2 {
327            return true;
328        }
329
330        if let Type::NonNullType(t1) = t1 {
331            if let Type::NonNullType(t2) = t2 {
332                return Self::is_type_conflict(schema, t1, t2);
333            } else {
334                return true;
335            }
336        }
337
338        if let Type::NonNullType(_) = t2 {
339            return true;
340        }
341
342        let schema_type1 = schema.type_by_name(t1.inner_type());
343        let schema_type2 = schema.type_by_name(t2.inner_type());
344
345        if schema_type1.map(|t| t.is_leaf_type()).unwrap_or(false)
346            || schema_type2.map(|t| t.is_leaf_type()).unwrap_or(false)
347        {
348            t1 != t2
349        } else {
350            false
351        }
352    }
353
354    // Determines if there is a conflict between two particular fields, including
355    // comparing their sub-fields.
356    fn find_conflict(
357        &mut self,
358        schema: &'a SchemaDocument,
359        out_field_name: &str,
360        first: &AstAndDef<'a>,
361        second: &AstAndDef<'a>,
362        parents_mutually_exclusive: bool,
363        visited_fragments: &mut Vec<&'a str>,
364    ) -> Option<Conflict> {
365        let AstAndDef(parent_type1, field1, field1_def) = *first;
366        let AstAndDef(parent_type2, field2, field2_def) = *second;
367
368        // If it is known that two fields could not possibly apply at the same
369        // time, due to the parent types, then it is safe to permit them to diverge
370        // in aliased field or arguments used as they will not present any ambiguity
371        // by differing.
372        // It is known that two parent types could never overlap if they are
373        // different Object types. Interface or Union types might overlap - if not
374        // in the current state of the schema, then perhaps in some future version,
375        // thus may not safely diverge.
376        let mutually_exclusive = parents_mutually_exclusive
377            || (parent_type1.name().ne(parent_type2.name())
378                && parent_type1.is_object_type()
379                && parent_type2.is_object_type());
380
381        if !mutually_exclusive {
382            let name1 = &field1.name;
383            let name2 = &field2.name;
384
385            if name1 != name2 {
386                return Some(Conflict(
387                    ConflictReason(
388                        out_field_name.to_string(),
389                        ConflictReasonMessage::Message(format!(
390                            "\"{}\" and \"{}\" are different fields",
391                            name1, name2
392                        )),
393                    ),
394                    vec![field1.position],
395                    vec![field2.position],
396                ));
397            }
398
399            if !self.is_same_arguments(&field1.arguments, &field2.arguments) {
400                return Some(Conflict(
401                    ConflictReason(
402                        out_field_name.to_string(),
403                        ConflictReasonMessage::Message("they have differing arguments".to_string()),
404                    ),
405                    vec![field1.position],
406                    vec![field2.position],
407                ));
408            }
409        }
410
411        let t1 = field1_def.as_ref().map(|def| &def.field_type);
412        let t2 = field2_def.as_ref().map(|def| &def.field_type);
413
414        if let (Some(t1), Some(t2)) = (t1, t2) {
415            if Self::is_type_conflict(schema, t1, t2) {
416                return Some(Conflict(
417                    ConflictReason(
418                        out_field_name.to_owned(),
419                        ConflictReasonMessage::Message(format!(
420                            "they return conflicting types \"{}\" and \"{}\"",
421                            t1, t2
422                        )),
423                    ),
424                    vec![field1.position],
425                    vec![field2.position],
426                ));
427            }
428        }
429
430        // Collect and compare sub-fields. Use the same "visited fragment names" list
431        // for both collections so fields in a fragment reference are never
432        // compared to themselves.
433        if !field1.selection_set.items.is_empty() && !field2.selection_set.items.is_empty() {
434            let conflicts = self.find_conflicts_between_sub_selection_sets(
435                schema,
436                mutually_exclusive,
437                t1.map(|v| v.inner_type()),
438                &field1.selection_set,
439                t2.map(|v| v.inner_type()),
440                &field2.selection_set,
441                visited_fragments,
442            );
443
444            return self.subfield_conflicts(
445                &conflicts,
446                out_field_name,
447                field1.position,
448                field1.position,
449            );
450        }
451
452        None
453    }
454
455    fn subfield_conflicts(
456        &self,
457        conflicts: &[Conflict],
458        out_field_name: &str,
459        f1_pos: Pos,
460        f2_pos: Pos,
461    ) -> Option<Conflict> {
462        if conflicts.is_empty() {
463            return None;
464        }
465
466        Some(Conflict(
467            ConflictReason(
468                out_field_name.to_string(),
469                ConflictReasonMessage::Nested(conflicts.iter().map(|v| v.0.clone()).collect()),
470            ),
471            vec![f1_pos]
472                .into_iter()
473                .chain(conflicts.iter().flat_map(|v| v.1.clone()))
474                .collect(),
475            vec![f2_pos]
476                .into_iter()
477                .chain(conflicts.iter().flat_map(|v| v.1.clone()))
478                .collect(),
479        ))
480    }
481
482    // Find all conflicts found between two selection sets, including those found
483    // via spreading in fragments. Called when determining if conflicts exist
484    // between the sub-fields of two overlapping fields.
485    #[allow(clippy::too_many_arguments)]
486    fn find_conflicts_between_sub_selection_sets(
487        &mut self,
488        schema: &'a SchemaDocument,
489        mutually_exclusive: bool,
490        parent_type_name1: Option<&str>,
491        selection_set1: &'a SelectionSet,
492        parent_type_name2: Option<&str>,
493        selection_set2: &'a SelectionSet,
494        visited_fragments: &mut Vec<&'a str>,
495    ) -> Vec<Conflict> {
496        let mut conflicts = Vec::<Conflict>::new();
497        let parent_type1 = parent_type_name1.and_then(|t| schema.type_by_name(t));
498        let parent_type2 = parent_type_name2.and_then(|t| schema.type_by_name(t));
499
500        let (field_map1, fragment_names1) =
501            self.get_fields_and_fragment_names(schema, parent_type1, selection_set1);
502        let (field_map2, fragment_names2) =
503            self.get_fields_and_fragment_names(schema, parent_type2, selection_set2);
504
505        // (H) First, collect all conflicts between these two collections of field.
506        self.collect_conflicts_between(
507            schema,
508            &mut conflicts,
509            mutually_exclusive,
510            &field_map1,
511            &field_map2,
512            visited_fragments,
513        );
514
515        // (I) Then collect conflicts between the first collection of fields and
516        // those referenced by each fragment name associated with the second.
517        for fragment_name in &fragment_names2 {
518            self.collect_conflicts_between_fields_and_fragment(
519                schema,
520                &mut conflicts,
521                &field_map1,
522                fragment_name,
523                mutually_exclusive,
524                visited_fragments,
525            );
526        }
527
528        // (I) Then collect conflicts between the second collection of fields and
529        // those referenced by each fragment name associated with the first.
530        for fragment_name in &fragment_names1 {
531            self.collect_conflicts_between_fields_and_fragment(
532                schema,
533                &mut conflicts,
534                &field_map2,
535                fragment_name,
536                mutually_exclusive,
537                visited_fragments,
538            );
539        }
540
541        // (J) Also collect conflicts between any fragment names by the first and
542        // fragment names by the second. This compares each item in the first set of
543        // names to each item in the second set of names.
544        for fragment_name1 in &fragment_names1 {
545            for fragment_name2 in &fragment_names2 {
546                self.collect_conflicts_between_fragments(
547                    schema,
548                    &mut conflicts,
549                    fragment_name1,
550                    fragment_name2,
551                    mutually_exclusive,
552                    visited_fragments,
553                );
554            }
555        }
556
557        conflicts
558    }
559
560    fn collect_conflicts_between_fields_and_fragment(
561        &mut self,
562        schema: &'a SchemaDocument,
563        conflicts: &mut Vec<Conflict>,
564        field_map: &OrderedMap<&'a str, Vec<AstAndDef<'a>>>,
565        fragment_name: &str,
566        mutually_exclusive: bool,
567        visited_fragments: &mut Vec<&'a str>,
568    ) {
569        let fragment = match self.named_fragments.get(fragment_name) {
570            Some(f) => f,
571            None => return,
572        };
573
574        let (field_map2, fragment_names2) =
575            self.get_referenced_fields_and_fragment_names(schema, fragment);
576
577        if fragment_names2.contains(&fragment_name) {
578            return;
579        }
580
581        self.collect_conflicts_between(
582            schema,
583            conflicts,
584            mutually_exclusive,
585            field_map,
586            &field_map2,
587            visited_fragments,
588        );
589
590        for fragment_name2 in &fragment_names2 {
591            if visited_fragments.contains(fragment_name2) {
592                return;
593            }
594
595            visited_fragments.push(fragment_name2);
596
597            self.collect_conflicts_between_fields_and_fragment(
598                schema,
599                conflicts,
600                field_map,
601                fragment_name2,
602                mutually_exclusive,
603                visited_fragments,
604            );
605        }
606    }
607
608    // Collect all conflicts found between two fragments, including via spreading in
609    // any nested fragments.
610    fn collect_conflicts_between_fragments(
611        &mut self,
612        schema: &'a SchemaDocument,
613        conflicts: &mut Vec<Conflict>,
614        fragment_name1: &'a str,
615        fragment_name2: &'a str,
616        mutually_exclusive: bool,
617        visited_fragments: &mut Vec<&'a str>,
618    ) {
619        // No need to compare a fragment to itself.
620        if fragment_name1.eq(fragment_name2) {
621            return;
622        }
623
624        // Memoize so two fragments are not compared for conflicts more than once.
625        if self
626            .compared_fragments
627            .contains(fragment_name1, fragment_name2, mutually_exclusive)
628        {
629            return;
630        }
631
632        self.compared_fragments
633            .insert(fragment_name1, fragment_name2, mutually_exclusive);
634
635        let fragment1 = match self.named_fragments.get(fragment_name1) {
636            Some(f) => f,
637            None => return,
638        };
639
640        let fragment2 = match self.named_fragments.get(fragment_name2) {
641            Some(f) => f,
642            None => return,
643        };
644
645        let (field_map1, fragment_names1) =
646            self.get_referenced_fields_and_fragment_names(schema, fragment1);
647        let (field_map2, fragment_names2) =
648            self.get_referenced_fields_and_fragment_names(schema, fragment2);
649
650        // (F) First, collect all conflicts between these two collections of fields
651        // (not including any nested fragments).
652        self.collect_conflicts_between(
653            schema,
654            conflicts,
655            mutually_exclusive,
656            &field_map1,
657            &field_map2,
658            visited_fragments,
659        );
660
661        // (G) Then collect conflicts between the first fragment and any nested
662        // fragments spread in the second fragment.
663        for fragment_name2 in &fragment_names2 {
664            self.collect_conflicts_between_fragments(
665                schema,
666                conflicts,
667                fragment_name1,
668                fragment_name2,
669                mutually_exclusive,
670                visited_fragments,
671            );
672        }
673
674        // (G) Then collect conflicts between the second fragment and any nested
675        // fragments spread in the first fragment.
676        for fragment_name1 in &fragment_names1 {
677            self.collect_conflicts_between_fragments(
678                schema,
679                conflicts,
680                fragment_name1,
681                fragment_name2,
682                mutually_exclusive,
683                visited_fragments,
684            );
685        }
686    }
687
688    // Given a reference to a fragment, return the represented collection of fields
689    // as well as a list of nested fragment names referenced via fragment spreads.
690    fn get_referenced_fields_and_fragment_names(
691        &self,
692        schema: &'a SchemaDocument,
693        fragment: &'a FragmentDefinition,
694    ) -> (OrderedMap<&'a str, Vec<AstAndDef<'a>>>, Vec<&'a str>) {
695        let TypeCondition::On(type_condition) = &fragment.type_condition;
696        let fragment_type = schema.type_by_name(type_condition);
697
698        self.get_fields_and_fragment_names(schema, fragment_type, &fragment.selection_set)
699    }
700
701    // Collect all Conflicts between two collections of fields. This is similar to,
702    // but different from the `collectConflictsWithin` function above. This check
703    // assumes that `collectConflictsWithin` has already been called on each
704    // provided collection of fields. This is true because this validator traverses
705    // each individual selection set.
706    fn collect_conflicts_between(
707        &mut self,
708        schema: &'a SchemaDocument,
709        conflicts: &mut Vec<Conflict>,
710        mutually_exclusive: bool,
711        field_map1: &OrderedMap<&'a str, Vec<AstAndDef<'a>>>,
712        field_map2: &OrderedMap<&'a str, Vec<AstAndDef<'a>>>,
713        visited_fragments: &mut Vec<&'a str>,
714    ) {
715        // A field map is a keyed collection, where each key represents a response
716        // name and the value at that key is a list of all fields which provide that
717        // response name. For any response name which appears in both provided field
718        // maps, each field from the first field map must be compared to every field
719        // in the second field map to find potential conflicts.
720        for (response_name, fields1) in field_map1.iter() {
721            if let Some(fields2) = field_map2.get(response_name) {
722                for field1 in fields1 {
723                    for field2 in fields2 {
724                        if let Some(conflict) = self.find_conflict(
725                            schema,
726                            response_name,
727                            field1,
728                            field2,
729                            mutually_exclusive,
730                            visited_fragments,
731                        ) {
732                            conflicts.push(conflict);
733                        }
734                    }
735                }
736            }
737        }
738    }
739
740    // Given a selection set, return the collection of fields (a mapping of response
741    // name to field nodes and definitions) as well as a list of fragment names
742    // referenced via fragment spreads.
743    fn get_fields_and_fragment_names(
744        &self,
745        schema: &'a SchemaDocument,
746        parent_type: Option<&'a TypeDefinition>,
747        selection_set: &'a SelectionSet,
748    ) -> (OrderedMap<&'a str, Vec<AstAndDef<'a>>>, Vec<&'a str>) {
749        let mut ast_and_defs = OrderedMap::new();
750        let mut fragment_names = Vec::new();
751
752        Self::collect_fields_and_fragment_names(
753            schema,
754            parent_type,
755            selection_set,
756            &mut ast_and_defs,
757            &mut fragment_names,
758        );
759
760        (ast_and_defs, fragment_names)
761    }
762
763    fn collect_fields_and_fragment_names(
764        schema: &'a SchemaDocument,
765        parent_type: Option<&'a TypeDefinition>,
766        selection_set: &'a SelectionSet,
767        ast_and_defs: &mut OrderedMap<&'a str, Vec<AstAndDef<'a>>>,
768        fragment_names: &mut Vec<&'a str>,
769    ) {
770        for selection in &selection_set.items {
771            match selection {
772                Selection::Field(field) => {
773                    let field_name = &field.name;
774                    let field_def = parent_type.and_then(|t| t.field_by_name(field_name));
775                    let out_field_name = field.alias.as_ref().unwrap_or(field_name).as_str();
776
777                    if !ast_and_defs.contains_key(out_field_name) {
778                        ast_and_defs.insert(out_field_name, Vec::new());
779                    }
780
781                    ast_and_defs
782                        .get_mut(out_field_name)
783                        .unwrap()
784                        .push(AstAndDef(parent_type, field, field_def));
785                }
786                Selection::FragmentSpread(fragment_spread) => {
787                    if !fragment_names
788                        .iter()
789                        .any(|n| (*n).eq(&fragment_spread.fragment_name))
790                    {
791                        fragment_names.push(&fragment_spread.fragment_name);
792                    }
793                }
794                Selection::InlineFragment(inline_fragment) => {
795                    let fragment_type = inline_fragment
796                        .type_condition
797                        .as_ref()
798                        .and_then(|type_condition| {
799                            let TypeCondition::On(type_condition) = type_condition;
800
801                            schema.type_by_name(type_condition)
802                        })
803                        .or(parent_type);
804
805                    Self::collect_fields_and_fragment_names(
806                        schema,
807                        fragment_type,
808                        &inline_fragment.selection_set,
809                        ast_and_defs,
810                        fragment_names,
811                    )
812                }
813            }
814        }
815    }
816}
817
818impl<'a> OperationVisitor<'a, ValidationErrorContext> for OverlappingFieldsCanBeMerged<'a> {
819    fn enter_document(
820        &mut self,
821        _visitor_context: &mut OperationVisitorContext,
822        _: &mut ValidationErrorContext,
823        document: &'a Document,
824    ) {
825        for definition in &document.definitions {
826            if let Definition::Fragment(fragment) = definition {
827                self.named_fragments.insert(&fragment.name, fragment);
828            }
829        }
830    }
831
832    fn enter_selection_set(
833        &mut self,
834        visitor_context: &mut OperationVisitorContext<'a>,
835        user_context: &mut ValidationErrorContext,
836        selection_set: &'a SelectionSet,
837    ) {
838        let parent_type = visitor_context.current_parent_type();
839        let schema = visitor_context.schema;
840        let mut visited_fragments = Vec::new();
841        let found_conflicts = self.find_conflicts_within_selection_set(
842            schema,
843            parent_type,
844            selection_set,
845            &mut visited_fragments,
846        );
847
848        for Conflict(ConflictReason(reason_name, reason_msg), mut p1, p2) in found_conflicts {
849            p1.extend(p2);
850
851            user_context.report_error(ValidationError {
852                error_code: self.error_code(),
853                message: error_message(&reason_name, &reason_msg),
854                locations: p1,
855            });
856        }
857    }
858}
859
860fn error_message(reason_name: &str, reason: &ConflictReasonMessage) -> String {
861    let suffix = "Use different aliases on the fields to fetch both if this was intentional.";
862
863    format!(
864        r#"Fields "{}" conflict because {}. {}"#,
865        reason_name,
866        format_reason(reason),
867        suffix
868    )
869}
870
871fn format_reason(reason: &ConflictReasonMessage) -> String {
872    match *reason {
873        ConflictReasonMessage::Message(ref name) => name.clone(),
874        ConflictReasonMessage::Nested(ref nested) => nested
875            .iter()
876            .map(|ConflictReason(name, subreason)| {
877                format!(
878                    r#"subfields "{}" conflict because {}"#,
879                    name,
880                    format_reason(subreason)
881                )
882            })
883            .collect::<Vec<_>>()
884            .join(" and "),
885    }
886}
887
888impl<'o> ValidationRule for OverlappingFieldsCanBeMerged<'o> {
889    fn error_code<'a>(&self) -> &'a str {
890        "OverlappingFieldsCanBeMerged"
891    }
892
893    fn validate(
894        &self,
895        ctx: &mut OperationVisitorContext,
896        error_collector: &mut ValidationErrorContext,
897    ) {
898        visit_document(
899            &mut OverlappingFieldsCanBeMerged::new(),
900            ctx.operation,
901            ctx,
902            error_collector,
903        );
904    }
905}
906
907#[test]
908fn unique_fields() {
909    use crate::validation::test_utils::*;
910
911    let mut plan = create_plan_from_rule(Box::new(OverlappingFieldsCanBeMerged::new()));
912    let errors = test_operation_with_schema(
913        "fragment uniqueFields on Dog {
914          name
915          nickname
916        }",
917        TEST_SCHEMA,
918        &mut plan,
919    );
920
921    assert_eq!(get_messages(&errors).len(), 0);
922}
923
924#[test]
925fn identical_fields() {
926    use crate::validation::test_utils::*;
927
928    let mut plan = create_plan_from_rule(Box::new(OverlappingFieldsCanBeMerged::new()));
929    let errors = test_operation_with_schema(
930        "fragment mergeIdenticalFields on Dog {
931          name
932          name
933        }",
934        TEST_SCHEMA,
935        &mut plan,
936    );
937
938    assert_eq!(get_messages(&errors).len(), 0);
939}
940
941#[test]
942fn identical_fields_with_identical_variables() {
943    use crate::validation::test_utils::*;
944
945    let mut plan = create_plan_from_rule(Box::new(OverlappingFieldsCanBeMerged::new()));
946    let errors = test_operation_with_schema(
947        r#"fragment mergeIdenticalFieldsWithIdenticalArgs on Dog {
948          doesKnowCommand(dogCommand: $dogCommand)
949          doesKnowCommand(dogCommand: $dogCommand)
950        }"#,
951        TEST_SCHEMA,
952        &mut plan,
953    );
954
955    assert_eq!(get_messages(&errors).len(), 0);
956}
957
958#[test]
959fn identical_fields_with_different_variables() {
960    use crate::validation::test_utils::*;
961
962    let mut plan = create_plan_from_rule(Box::new(OverlappingFieldsCanBeMerged::new()));
963    let errors = test_operation_with_schema(
964        r#"fragment mergeIdenticalFieldsWithIdenticalArgs on Dog {
965          doesKnowCommand(dogCommand: $catCommand)
966          doesKnowCommand(dogCommand: $dogCommand)
967        }"#,
968        TEST_SCHEMA,
969        &mut plan,
970    );
971
972    let messages = get_messages(&errors);
973    assert_eq!(messages.len(), 1);
974    assert_eq!(messages, vec!["Fields \"doesKnowCommand\" conflict because they have differing arguments. Use different aliases on the fields to fetch both if this was intentional."]);
975}
976
977#[test]
978fn identical_fields_and_identical_args() {
979    use crate::validation::test_utils::*;
980
981    let mut plan = create_plan_from_rule(Box::new(OverlappingFieldsCanBeMerged::new()));
982    let errors = test_operation_with_schema(
983        "fragment mergeIdenticalFieldsWithIdenticalArgs on Dog {
984          doesKnowCommand(dogCommand: SIT)
985          doesKnowCommand(dogCommand: SIT)
986        }",
987        TEST_SCHEMA,
988        &mut plan,
989    );
990
991    assert_eq!(get_messages(&errors).len(), 0);
992}
993
994#[test]
995fn identical_fields_and_identical_directives() {
996    use crate::validation::test_utils::*;
997
998    let mut plan = create_plan_from_rule(Box::new(OverlappingFieldsCanBeMerged::new()));
999    let errors = test_operation_with_schema(
1000        "fragment mergeSameFieldsWithSameDirectives on Dog {
1001          name @include(if: true)
1002          name @include(if: true)
1003        }",
1004        TEST_SCHEMA,
1005        &mut plan,
1006    );
1007
1008    assert_eq!(get_messages(&errors).len(), 0);
1009}
1010
1011#[test]
1012fn different_args_different_aliases() {
1013    use crate::validation::test_utils::*;
1014
1015    let mut plan = create_plan_from_rule(Box::new(OverlappingFieldsCanBeMerged::new()));
1016    let errors = test_operation_with_schema(
1017        "fragment differentArgsWithDifferentAliases on Dog {
1018          knowsSit: doesKnowCommand(dogCommand: SIT)
1019          knowsDown: doesKnowCommand(dogCommand: DOWN)
1020        }",
1021        TEST_SCHEMA,
1022        &mut plan,
1023    );
1024
1025    assert_eq!(get_messages(&errors).len(), 0);
1026}
1027
1028#[test]
1029fn different_directives_different_aliases() {
1030    use crate::validation::test_utils::*;
1031
1032    let mut plan = create_plan_from_rule(Box::new(OverlappingFieldsCanBeMerged::new()));
1033    let errors = test_operation_with_schema(
1034        "fragment differentDirectivesWithDifferentAliases on Dog {
1035          nameIfTrue: name @include(if: true)
1036          nameIfFalse: name @include(if: false)
1037        }",
1038        TEST_SCHEMA,
1039        &mut plan,
1040    );
1041
1042    assert_eq!(get_messages(&errors).len(), 0);
1043}
1044
1045#[test]
1046fn different_skip_include_directives() {
1047    use crate::validation::test_utils::*;
1048
1049    let mut plan = create_plan_from_rule(Box::new(OverlappingFieldsCanBeMerged::new()));
1050    let errors = test_operation_with_schema(
1051        "fragment differentDirectivesWithDifferentAliases on Dog {
1052          name @include(if: true)
1053          name @include(if: false)
1054        }",
1055        TEST_SCHEMA,
1056        &mut plan,
1057    );
1058
1059    assert_eq!(get_messages(&errors).len(), 0);
1060}
1061
1062#[test]
1063fn same_alias_different_field_target() {
1064    use crate::validation::test_utils::*;
1065
1066    let mut plan = create_plan_from_rule(Box::new(OverlappingFieldsCanBeMerged::new()));
1067    let errors = test_operation_with_schema(
1068        "fragment sameAliasesWithDifferentFieldTargets on Dog {
1069          fido: name
1070          fido: nickname
1071        }",
1072        TEST_SCHEMA,
1073        &mut plan,
1074    );
1075
1076    let messages = get_messages(&errors);
1077    assert_eq!(messages.len(), 1);
1078    assert_eq!(messages, vec!["Fields \"fido\" conflict because \"name\" and \"nickname\" are different fields. Use different aliases on the fields to fetch both if this was intentional."]);
1079}
1080
1081#[test]
1082fn same_alias_non_overlapping_field_target() {
1083    use crate::validation::test_utils::*;
1084
1085    let mut plan = create_plan_from_rule(Box::new(OverlappingFieldsCanBeMerged::new()));
1086    let errors = test_operation_with_schema(
1087        "fragment sameAliasesWithDifferentFieldTargets on Pet {
1088          ... on Dog {
1089            name
1090          }
1091          ... on Cat {
1092            name: nickname
1093          }
1094        }",
1095        TEST_SCHEMA,
1096        &mut plan,
1097    );
1098
1099    let messages = get_messages(&errors);
1100    assert_eq!(messages.len(), 0);
1101}
1102
1103#[test]
1104fn alias_masking_direct_access() {
1105    use crate::validation::test_utils::*;
1106
1107    let mut plan = create_plan_from_rule(Box::new(OverlappingFieldsCanBeMerged::new()));
1108    let errors = test_operation_with_schema(
1109        "fragment aliasMaskingDirectFieldAccess on Dog {
1110          name: nickname
1111          name
1112        }",
1113        TEST_SCHEMA,
1114        &mut plan,
1115    );
1116
1117    let messages = get_messages(&errors);
1118    assert_eq!(messages.len(), 1);
1119    assert_eq!(messages, vec!["Fields \"name\" conflict because \"nickname\" and \"name\" are different fields. Use different aliases on the fields to fetch both if this was intentional."]);
1120}
1121
1122#[test]
1123fn different_args_second_adds() {
1124    use crate::validation::test_utils::*;
1125
1126    let mut plan = create_plan_from_rule(Box::new(OverlappingFieldsCanBeMerged::new()));
1127    let errors = test_operation_with_schema(
1128        "fragment conflictingArgs on Dog {
1129          doesKnowCommand
1130          doesKnowCommand(dogCommand: HEEL)
1131        }",
1132        TEST_SCHEMA,
1133        &mut plan,
1134    );
1135
1136    let messages = get_messages(&errors);
1137    assert_eq!(messages.len(), 1);
1138    assert_eq!(messages, vec!["Fields \"doesKnowCommand\" conflict because they have differing arguments. Use different aliases on the fields to fetch both if this was intentional."]);
1139}
1140
1141#[test]
1142fn different_args_declared_on_first() {
1143    use crate::validation::test_utils::*;
1144
1145    let mut plan = create_plan_from_rule(Box::new(OverlappingFieldsCanBeMerged::new()));
1146    let errors = test_operation_with_schema(
1147        "fragment conflictingArgs on Dog {
1148          doesKnowCommand(dogCommand: SIT)
1149          doesKnowCommand
1150        }",
1151        TEST_SCHEMA,
1152        &mut plan,
1153    );
1154
1155    let messages = get_messages(&errors);
1156    assert_eq!(messages.len(), 1);
1157    assert_eq!(messages, vec!["Fields \"doesKnowCommand\" conflict because they have differing arguments. Use different aliases on the fields to fetch both if this was intentional."]);
1158}
1159
1160#[test]
1161fn different_arg_values() {
1162    use crate::validation::test_utils::*;
1163
1164    let mut plan = create_plan_from_rule(Box::new(OverlappingFieldsCanBeMerged::new()));
1165    let errors = test_operation_with_schema(
1166        "fragment conflictingArgs on Dog {
1167          doesKnowCommand(dogCommand: SIT)
1168          doesKnowCommand(dogCommand: HEEL)
1169        }",
1170        TEST_SCHEMA,
1171        &mut plan,
1172    );
1173
1174    let messages = get_messages(&errors);
1175    assert_eq!(messages.len(), 1);
1176    assert_eq!(messages, vec!["Fields \"doesKnowCommand\" conflict because they have differing arguments. Use different aliases on the fields to fetch both if this was intentional."]);
1177}
1178
1179#[test]
1180fn conflicting_arg_names() {
1181    use crate::validation::test_utils::*;
1182
1183    let mut plan = create_plan_from_rule(Box::new(OverlappingFieldsCanBeMerged::new()));
1184    let errors = test_operation_with_schema(
1185        "fragment conflictingArgs on Dog {
1186          isAtLocation(x: 0)
1187          isAtLocation(y: 0)
1188        }",
1189        TEST_SCHEMA,
1190        &mut plan,
1191    );
1192
1193    let messages = get_messages(&errors);
1194    assert_eq!(messages.len(), 1);
1195    assert_eq!(messages, vec!["Fields \"isAtLocation\" conflict because they have differing arguments. Use different aliases on the fields to fetch both if this was intentional."]);
1196}
1197
1198#[test]
1199fn allow_different_args_when_possible_with_different_args() {
1200    use crate::validation::test_utils::*;
1201
1202    let mut plan = create_plan_from_rule(Box::new(OverlappingFieldsCanBeMerged::new()));
1203    let errors = test_operation_with_schema(
1204        "fragment conflictingArgs on Pet {
1205          ... on Dog {
1206            name(surname: true)
1207          }
1208          ... on Cat {
1209            name
1210          }
1211        }",
1212        TEST_SCHEMA,
1213        &mut plan,
1214    );
1215
1216    let messages = get_messages(&errors);
1217    assert_eq!(messages.len(), 0);
1218}
1219
1220#[test]
1221fn conflict_in_fragment_spread() {
1222    use crate::validation::test_utils::*;
1223
1224    let mut plan = create_plan_from_rule(Box::new(OverlappingFieldsCanBeMerged::new()));
1225    let errors = test_operation_with_schema(
1226        "query {
1227          ...A
1228          ...B
1229        }
1230        fragment A on Type {
1231          x: a
1232        }
1233        fragment B on Type {
1234          x: b
1235        }",
1236        TEST_SCHEMA,
1237        &mut plan,
1238    );
1239
1240    let messages = get_messages(&errors);
1241    assert_eq!(messages.len(), 1);
1242    assert_eq!(messages, vec!["Fields \"x\" conflict because \"a\" and \"b\" are different fields. Use different aliases on the fields to fetch both if this was intentional."]);
1243}
1244
1245#[test]
1246fn deep_conflict() {
1247    use crate::validation::test_utils::*;
1248
1249    let mut plan = create_plan_from_rule(Box::new(OverlappingFieldsCanBeMerged::new()));
1250    let errors = test_operation_with_schema(
1251        "{
1252          field {
1253            x: a
1254          }
1255          field {
1256            x: b
1257          }
1258        }",
1259        TEST_SCHEMA,
1260        &mut plan,
1261    );
1262
1263    let messages = get_messages(&errors);
1264    assert_eq!(messages.len(), 1);
1265    assert_eq!(messages, vec!["Fields \"field\" conflict because subfields \"x\" conflict because \"a\" and \"b\" are different fields. Use different aliases on the fields to fetch both if this was intentional."]);
1266}
1267
1268#[test]
1269fn report_each_conflict_once() {
1270    use crate::validation::test_utils::*;
1271
1272    let mut plan = create_plan_from_rule(Box::new(OverlappingFieldsCanBeMerged::new()));
1273    let errors = test_operation_with_schema(
1274        "{
1275          f1 {
1276            ...A
1277            ...B
1278          }
1279          f2 {
1280            ...B
1281            ...A
1282          }
1283          f3 {
1284            ...A
1285            ...B
1286            x: c
1287          }
1288        }
1289        fragment A on Type {
1290          x: a
1291        }
1292        fragment B on Type {
1293          x: b
1294        }",
1295        TEST_SCHEMA,
1296        &mut plan,
1297    );
1298
1299    let messages = get_messages(&errors);
1300    assert_eq!(messages.len(), 3);
1301    assert_eq!(messages, vec![
1302      "Fields \"x\" conflict because \"a\" and \"b\" are different fields. Use different aliases on the fields to fetch both if this was intentional.",
1303      "Fields \"x\" conflict because \"c\" and \"a\" are different fields. Use different aliases on the fields to fetch both if this was intentional.",
1304      "Fields \"x\" conflict because \"c\" and \"b\" are different fields. Use different aliases on the fields to fetch both if this was intentional."
1305    ]);
1306}
1307
1308#[cfg(test)]
1309pub static OVERLAPPING_RULE_TEST_SCHEMA: &str = "
1310interface SomeBox {
1311  deepBox: SomeBox
1312  unrelatedField: String
1313}
1314type StringBox implements SomeBox {
1315  scalar: String
1316  deepBox: StringBox
1317  unrelatedField: String
1318  listStringBox: [StringBox]
1319  stringBox: StringBox
1320  intBox: IntBox
1321}
1322type IntBox implements SomeBox {
1323  scalar: Int
1324  deepBox: IntBox
1325  unrelatedField: String
1326  listStringBox: [StringBox]
1327  stringBox: StringBox
1328  intBox: IntBox
1329}
1330interface NonNullStringBox1 {
1331  scalar: String!
1332}
1333type NonNullStringBox1Impl implements SomeBox & NonNullStringBox1 {
1334  scalar: String!
1335  unrelatedField: String
1336  deepBox: SomeBox
1337}
1338interface NonNullStringBox2 {
1339  scalar: String!
1340}
1341type NonNullStringBox2Impl implements SomeBox & NonNullStringBox2 {
1342  scalar: String!
1343  unrelatedField: String
1344  deepBox: SomeBox
1345}
1346type Connection {
1347  edges: [Edge]
1348}
1349type Edge {
1350  node: Node
1351}
1352type Node {
1353  id: ID
1354  name: String
1355}
1356type Query {
1357  someBox: SomeBox
1358  connection: Connection
1359}";
1360
1361#[test]
1362fn conflicting_return_types_which_potentially_overlap() {
1363    use crate::validation::test_utils::*;
1364
1365    let mut plan = create_plan_from_rule(Box::new(OverlappingFieldsCanBeMerged::new()));
1366    let errors = test_operation_with_schema(
1367        "{
1368          someBox {
1369            ...on IntBox {
1370              scalar
1371            }
1372            ...on NonNullStringBox1 {
1373              scalar
1374            }
1375          }
1376        }",
1377        OVERLAPPING_RULE_TEST_SCHEMA,
1378        &mut plan,
1379    );
1380
1381    let messages = get_messages(&errors);
1382    assert_eq!(messages.len(), 1);
1383    assert_eq!(messages, vec![
1384      "Fields \"scalar\" conflict because they return conflicting types \"Int\" and \"String!\". Use different aliases on the fields to fetch both if this was intentional."
1385    ]);
1386}
1387
1388#[test]
1389fn compatible_return_shapes_on_different_return_types() {
1390    use crate::validation::test_utils::*;
1391
1392    let mut plan = create_plan_from_rule(Box::new(OverlappingFieldsCanBeMerged::new()));
1393    let errors = test_operation_with_schema(
1394        "{
1395          someBox {
1396            ... on SomeBox {
1397              deepBox {
1398                unrelatedField
1399              }
1400            }
1401            ... on StringBox {
1402              deepBox {
1403                unrelatedField
1404              }
1405            }
1406          }
1407        }",
1408        OVERLAPPING_RULE_TEST_SCHEMA,
1409        &mut plan,
1410    );
1411
1412    let messages = get_messages(&errors);
1413    assert_eq!(messages.len(), 0);
1414}
1415
1416#[test]
1417fn disallows_differing_return_types_despite_no_overlap() {
1418    use crate::validation::test_utils::*;
1419
1420    let mut plan = create_plan_from_rule(Box::new(OverlappingFieldsCanBeMerged::new()));
1421    let errors = test_operation_with_schema(
1422        "{
1423          someBox {
1424            ... on IntBox {
1425              scalar
1426            }
1427            ... on StringBox {
1428              scalar
1429            }
1430          }
1431        }",
1432        OVERLAPPING_RULE_TEST_SCHEMA,
1433        &mut plan,
1434    );
1435
1436    let messages = get_messages(&errors);
1437    assert_eq!(messages.len(), 1);
1438    assert_eq!(messages, vec![
1439      "Fields \"scalar\" conflict because they return conflicting types \"Int\" and \"String\". Use different aliases on the fields to fetch both if this was intentional."
1440    ]);
1441}
1442
1443#[test]
1444fn reports_correctly_when_a_non_exclusive_follows_an_exclusive() {
1445    use crate::validation::test_utils::*;
1446
1447    let mut plan = create_plan_from_rule(Box::new(OverlappingFieldsCanBeMerged::new()));
1448    let errors = test_operation_with_schema(
1449        "{
1450          someBox {
1451            ... on IntBox {
1452              deepBox {
1453                ...X
1454              }
1455            }
1456          }
1457          someBox {
1458            ... on StringBox {
1459              deepBox {
1460                ...Y
1461              }
1462            }
1463          }
1464          memoed: someBox {
1465            ... on IntBox {
1466              deepBox {
1467                ...X
1468              }
1469            }
1470          }
1471          memoed: someBox {
1472            ... on StringBox {
1473              deepBox {
1474                ...Y
1475              }
1476            }
1477          }
1478          other: someBox {
1479            ...X
1480          }
1481          other: someBox {
1482            ...Y
1483          }
1484        }
1485        fragment X on SomeBox {
1486          scalar
1487        }
1488        fragment Y on SomeBox {
1489          scalar: unrelatedField
1490        }",
1491        OVERLAPPING_RULE_TEST_SCHEMA,
1492        &mut plan,
1493    );
1494
1495    let messages = get_messages(&errors);
1496    assert_eq!(messages.len(), 1);
1497    assert_eq!(messages, vec![
1498      "Fields \"other\" conflict because subfields \"scalar\" conflict because \"scalar\" and \"unrelatedField\" are different fields. Use different aliases on the fields to fetch both if this was intentional."
1499    ]);
1500}
1501
1502#[test]
1503fn disallows_differing_return_type_nullability_despite_no_overlap() {
1504    use crate::validation::test_utils::*;
1505
1506    let mut plan = create_plan_from_rule(Box::new(OverlappingFieldsCanBeMerged::new()));
1507    let errors = test_operation_with_schema(
1508        "{
1509          someBox {
1510            ... on NonNullStringBox1 {
1511              scalar
1512            }
1513            ... on StringBox {
1514              scalar
1515            }
1516          }
1517        }",
1518        OVERLAPPING_RULE_TEST_SCHEMA,
1519        &mut plan,
1520    );
1521
1522    let messages = get_messages(&errors);
1523    assert_eq!(messages.len(), 1);
1524    assert_eq!(messages, vec![
1525      "Fields \"scalar\" conflict because they return conflicting types \"String!\" and \"String\". Use different aliases on the fields to fetch both if this was intentional."
1526    ]);
1527}
1528
1529#[test]
1530fn disallows_differing_return_type_list_despite_no_overlap() {
1531    use crate::validation::test_utils::*;
1532
1533    let mut plan = create_plan_from_rule(Box::new(OverlappingFieldsCanBeMerged::new()));
1534    let errors = test_operation_with_schema(
1535        "{
1536          someBox {
1537            ... on IntBox {
1538              box: listStringBox {
1539                scalar
1540              }
1541            }
1542            ... on StringBox {
1543              box: stringBox {
1544                scalar
1545              }
1546            }
1547          }
1548        }",
1549        OVERLAPPING_RULE_TEST_SCHEMA,
1550        &mut plan,
1551    );
1552
1553    let messages = get_messages(&errors);
1554    assert_eq!(messages.len(), 1);
1555    assert_eq!(messages, vec![
1556      "Fields \"box\" conflict because they return conflicting types \"[StringBox]\" and \"StringBox\". Use different aliases on the fields to fetch both if this was intentional."
1557    ]);
1558
1559    let errors = test_operation_with_schema(
1560        "{
1561            someBox {
1562              ... on IntBox {
1563                box: stringBox {
1564                  scalar
1565                }
1566              }
1567              ... on StringBox {
1568                box: listStringBox {
1569                  scalar
1570                }
1571              }
1572            }
1573          }",
1574        OVERLAPPING_RULE_TEST_SCHEMA,
1575        &mut plan,
1576    );
1577
1578    let messages = get_messages(&errors);
1579    assert_eq!(messages.len(), 1);
1580    assert_eq!(messages, vec![
1581      "Fields \"box\" conflict because they return conflicting types \"StringBox\" and \"[StringBox]\". Use different aliases on the fields to fetch both if this was intentional."
1582    ]);
1583}
1584
1585#[test]
1586fn disallows_differing_subfields() {
1587    use crate::validation::test_utils::*;
1588
1589    let mut plan = create_plan_from_rule(Box::new(OverlappingFieldsCanBeMerged::new()));
1590    let errors = test_operation_with_schema(
1591        "{
1592          someBox {
1593            ... on IntBox {
1594              box: stringBox {
1595                val: scalar
1596                val: unrelatedField
1597              }
1598            }
1599            ... on StringBox {
1600              box: stringBox {
1601                val: scalar
1602              }
1603            }
1604          }
1605        }",
1606        OVERLAPPING_RULE_TEST_SCHEMA,
1607        &mut plan,
1608    );
1609
1610    let messages = get_messages(&errors);
1611    assert_eq!(messages.len(), 1);
1612    assert_eq!(messages, vec![
1613      "Fields \"val\" conflict because \"scalar\" and \"unrelatedField\" are different fields. Use different aliases on the fields to fetch both if this was intentional."
1614    ]);
1615}
1616
1617#[test]
1618fn disallows_differing_deep_return_types_despite_no_overlap() {
1619    use crate::validation::test_utils::*;
1620
1621    let mut plan = create_plan_from_rule(Box::new(OverlappingFieldsCanBeMerged::new()));
1622    let errors = test_operation_with_schema(
1623        "{
1624          someBox {
1625            ... on IntBox {
1626              box: stringBox {
1627                scalar
1628              }
1629            }
1630            ... on StringBox {
1631              box: intBox {
1632                scalar
1633              }
1634            }
1635          }
1636        }",
1637        OVERLAPPING_RULE_TEST_SCHEMA,
1638        &mut plan,
1639    );
1640
1641    let messages = get_messages(&errors);
1642    assert_eq!(messages.len(), 1);
1643    assert_eq!(messages, vec![
1644      "Fields \"box\" conflict because subfields \"scalar\" conflict because they return conflicting types \"String\" and \"Int\". Use different aliases on the fields to fetch both if this was intentional."
1645    ]);
1646}
1647
1648#[test]
1649fn allows_non_conflicting_overlapping_types() {
1650    use crate::validation::test_utils::*;
1651
1652    let mut plan = create_plan_from_rule(Box::new(OverlappingFieldsCanBeMerged::new()));
1653    let errors = test_operation_with_schema(
1654        "{
1655          someBox {
1656            ... on IntBox {
1657              scalar: unrelatedField
1658            }
1659            ... on StringBox {
1660              scalar
1661            }
1662          }
1663        }",
1664        OVERLAPPING_RULE_TEST_SCHEMA,
1665        &mut plan,
1666    );
1667
1668    let messages = get_messages(&errors);
1669    assert_eq!(messages.len(), 0);
1670}
1671
1672#[test]
1673fn same_wrapped_scalar_return_types() {
1674    use crate::validation::test_utils::*;
1675
1676    let mut plan = create_plan_from_rule(Box::new(OverlappingFieldsCanBeMerged::new()));
1677    let errors = test_operation_with_schema(
1678        "{
1679          someBox {
1680            ...on NonNullStringBox1 {
1681              scalar
1682            }
1683            ...on NonNullStringBox2 {
1684              scalar
1685            }
1686          }
1687        }",
1688        OVERLAPPING_RULE_TEST_SCHEMA,
1689        &mut plan,
1690    );
1691
1692    let messages = get_messages(&errors);
1693    assert_eq!(messages.len(), 0);
1694}
1695
1696#[test]
1697fn allows_inline_fragments_without_type_condition() {
1698    use crate::validation::test_utils::*;
1699
1700    let mut plan = create_plan_from_rule(Box::new(OverlappingFieldsCanBeMerged::new()));
1701    let errors = test_operation_with_schema(
1702        "{
1703          a
1704          ... {
1705            a
1706          }
1707        }",
1708        OVERLAPPING_RULE_TEST_SCHEMA,
1709        &mut plan,
1710    );
1711
1712    let messages = get_messages(&errors);
1713    assert_eq!(messages.len(), 0);
1714}
1715
1716#[test]
1717fn compares_deep_types_including_list() {
1718    use crate::validation::test_utils::*;
1719
1720    let mut plan = create_plan_from_rule(Box::new(OverlappingFieldsCanBeMerged::new()));
1721    let errors = test_operation_with_schema(
1722        "{
1723          connection {
1724            ...edgeID
1725            edges {
1726              node {
1727                id: name
1728              }
1729            }
1730          }
1731        }
1732        fragment edgeID on Connection {
1733          edges {
1734            node {
1735              id
1736            }
1737          }
1738        }",
1739        OVERLAPPING_RULE_TEST_SCHEMA,
1740        &mut plan,
1741    );
1742
1743    let messages = get_messages(&errors);
1744    assert_eq!(messages.len(), 1);
1745    assert_eq!(messages, vec![
1746      "Fields \"edges\" conflict because subfields \"node\" conflict because subfields \"id\" conflict because \"name\" and \"id\" are different fields. Use different aliases on the fields to fetch both if this was intentional."
1747    ]);
1748}
1749
1750#[test]
1751fn ignores_unknown_types() {
1752    use crate::validation::test_utils::*;
1753
1754    let mut plan = create_plan_from_rule(Box::new(OverlappingFieldsCanBeMerged::new()));
1755    let errors = test_operation_with_schema(
1756        "{
1757          someBox {
1758            ...on UnknownType {
1759              scalar
1760            }
1761            ...on NonNullStringBox2 {
1762              scalar
1763            }
1764          }
1765        }",
1766        OVERLAPPING_RULE_TEST_SCHEMA,
1767        &mut plan,
1768    );
1769
1770    let messages = get_messages(&errors);
1771    assert_eq!(messages.len(), 0);
1772}
1773
1774#[test]
1775fn does_not_infinite_loop_on_recursive_fragment() {
1776    use crate::validation::test_utils::*;
1777
1778    let mut plan = create_plan_from_rule(Box::new(OverlappingFieldsCanBeMerged::new()));
1779    let errors = test_operation_with_schema(
1780        "fragment fragA on Human { name, relatives { name, ...fragA } }",
1781        TEST_SCHEMA,
1782        &mut plan,
1783    );
1784
1785    let messages = get_messages(&errors);
1786    assert_eq!(messages.len(), 0);
1787}
1788
1789#[test]
1790fn does_not_infinite_loop_on_immediately_recursive_fragment() {
1791    use crate::validation::test_utils::*;
1792
1793    let mut plan = create_plan_from_rule(Box::new(OverlappingFieldsCanBeMerged::new()));
1794    let errors = test_operation_with_schema(
1795        "fragment fragA on Human { name, ...fragA }",
1796        TEST_SCHEMA,
1797        &mut plan,
1798    );
1799
1800    let messages = get_messages(&errors);
1801    assert_eq!(messages.len(), 0);
1802}
1803
1804#[test]
1805fn does_not_infinite_loop_on_transitively_recursive_fragment() {
1806    use crate::validation::test_utils::*;
1807
1808    let mut plan = create_plan_from_rule(Box::new(OverlappingFieldsCanBeMerged::new()));
1809    let errors = test_operation_with_schema(
1810        "
1811        fragment fragA on Human { name, ...fragB }
1812        fragment fragB on Human { name, ...fragC }
1813        fragment fragC on Human { name, ...fragA }
1814      ",
1815        TEST_SCHEMA,
1816        &mut plan,
1817    );
1818
1819    let messages = get_messages(&errors);
1820    assert_eq!(messages.len(), 0);
1821}
1822
1823#[test]
1824fn finds_invalid_case_even_with_immediately_recursive_fragment() {
1825    use crate::validation::test_utils::*;
1826
1827    let mut plan = create_plan_from_rule(Box::new(OverlappingFieldsCanBeMerged::new()));
1828    let errors = test_operation_with_schema(
1829        "
1830        fragment sameAliasesWithDifferentFieldTargets on Dog {
1831          ...sameAliasesWithDifferentFieldTargets
1832          fido: name
1833          fido: nickname
1834        }
1835      ",
1836        TEST_SCHEMA,
1837        &mut plan,
1838    );
1839
1840    let messages = get_messages(&errors);
1841    assert_eq!(messages.len(), 1);
1842    assert_eq!(messages, vec![
1843      "Fields \"fido\" conflict because \"name\" and \"nickname\" are different fields. Use different aliases on the fields to fetch both if this was intentional."
1844    ]);
1845}