Skip to main content

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