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::{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<'doc> {
23    named_fragments: HashMap<&'doc str, &'doc FragmentDefinition>,
24    compared_fragments: PairSet<'doc>,
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<'doc>(
90    Option<&'doc TypeDefinition>,
91    &'doc Field,
92    Option<&'doc FieldDefinition>,
93);
94
95#[derive(Debug, Clone, PartialEq, Eq, Hash)]
96enum ConflictReasonMessage {
97    Message(String),
98    Nested(Vec<ConflictReason>),
99}
100
101struct PairSet<'doc> {
102    data: HashMap<&'doc str, HashMap<&'doc str, bool>>,
103}
104
105struct OrderedMap<K, V> {
106    data: HashMap<K, V>,
107    insert_order: Vec<K>,
108}
109
110struct OrderedMapIter<'doc, K: 'doc, V: 'doc> {
111    map: &'doc HashMap<K, V>,
112    inner: ::std::slice::Iter<'doc, 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<'doc, K: Eq + Hash + 'doc, V: 'doc> Iterator for OrderedMapIter<'doc, K, V> {
164    type Item = (&'doc K, &'doc 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<'doc> PairSet<'doc> {
174    fn new() -> PairSet<'doc> {
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: &'doc str, b: &'doc 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 Default for OverlappingFieldsCanBeMerged<'_> {
200    fn default() -> Self {
201        Self::new()
202    }
203}
204
205impl<'doc> OverlappingFieldsCanBeMerged<'doc> {
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: &'doc SchemaDocument,
219        parent_type: Option<&'doc TypeDefinition>,
220        selection_set: &'doc SelectionSet,
221        visited_fragments: &mut Vec<&'doc 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: &'doc SchemaDocument,
267        conflicts: &mut Vec<Conflict>,
268        field_map: &OrderedMap<&'doc str, Vec<AstAndDef<'doc>>>,
269        visited_fragments: &mut Vec<&'doc 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: &'doc SchemaDocument,
355        out_field_name: &str,
356        first: &AstAndDef<'doc>,
357        second: &AstAndDef<'doc>,
358        parents_mutually_exclusive: bool,
359        visited_fragments: &mut Vec<&'doc 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: &'doc SchemaDocument,
500        mutually_exclusive: bool,
501        parent_type_name1: Option<&str>,
502        selection_set1: &'doc SelectionSet,
503        parent_type_name2: Option<&str>,
504        selection_set2: &'doc SelectionSet,
505        visited_fragments: &mut Vec<&'doc 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: &'doc SchemaDocument,
574        conflicts: &mut Vec<Conflict>,
575        field_map: &OrderedMap<&'doc str, Vec<AstAndDef<'doc>>>,
576        fragment_name: &str,
577        mutually_exclusive: bool,
578        visited_fragments: &mut Vec<&'doc 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: &'doc SchemaDocument,
624        conflicts: &mut Vec<Conflict>,
625        fragment_name1: &'doc str,
626        fragment_name2: &'doc str,
627        mutually_exclusive: bool,
628        visited_fragments: &mut Vec<&'doc 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: &'doc SchemaDocument,
704        fragment: &'doc FragmentDefinition,
705    ) -> (OrderedMap<&'doc str, Vec<AstAndDef<'doc>>>, Vec<&'doc 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: &'doc SchemaDocument,
720        conflicts: &mut Vec<Conflict>,
721        mutually_exclusive: bool,
722        field_map1: &OrderedMap<&'doc str, Vec<AstAndDef<'doc>>>,
723        field_map2: &OrderedMap<&'doc str, Vec<AstAndDef<'doc>>>,
724        visited_fragments: &mut Vec<&'doc 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: &'doc SchemaDocument,
757        parent_type: Option<&'doc TypeDefinition>,
758        selection_set: &'doc SelectionSet,
759    ) -> (OrderedMap<&'doc str, Vec<AstAndDef<'doc>>>, Vec<&'doc 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: &'doc SchemaDocument,
776        parent_type: Option<&'doc TypeDefinition>,
777        selection_set: &'doc SelectionSet,
778        ast_and_defs: &mut OrderedMap<&'doc str, Vec<AstAndDef<'doc>>>,
779        fragment_names: &mut Vec<&'doc 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<'doc> OperationVisitor<'doc, ValidationErrorContext> for OverlappingFieldsCanBeMerged<'doc> {
830    fn enter_document(
831        &mut self,
832        _visitor_context: &mut OperationVisitorContext,
833        _: &mut ValidationErrorContext,
834        document: &'doc 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<'doc>,
846        user_context: &mut ValidationErrorContext,
847        selection_set: &'doc 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 ValidationRule for OverlappingFieldsCanBeMerged<'_> {
900    fn error_code(&self) -> &'static str {
901        "OverlappingFieldsCanBeMerged"
902    }
903
904    fn visitor<'doc>(&self) -> super::ValidationVisitor<'doc> {
905        Box::new(OverlappingFieldsCanBeMerged::new())
906    }
907}
908
909#[test]
910fn unique_fields() {
911    use crate::validation::test_utils::*;
912
913    let mut plan = create_plan_from_rule(Box::new(OverlappingFieldsCanBeMerged::new()));
914    let errors = test_operation_with_schema(
915        "fragment uniqueFields on Dog {
916          name
917          nickname
918        }",
919        TEST_SCHEMA,
920        &mut plan,
921    );
922
923    assert_eq!(get_messages(&errors).len(), 0);
924}
925
926#[test]
927fn identical_fields() {
928    use crate::validation::test_utils::*;
929
930    let mut plan = create_plan_from_rule(Box::new(OverlappingFieldsCanBeMerged::new()));
931    let errors = test_operation_with_schema(
932        "fragment mergeIdenticalFields on Dog {
933          name
934          name
935        }",
936        TEST_SCHEMA,
937        &mut plan,
938    );
939
940    assert_eq!(get_messages(&errors).len(), 0);
941}
942
943#[test]
944fn identical_fields_with_identical_variables() {
945    use crate::validation::test_utils::*;
946
947    let mut plan = create_plan_from_rule(Box::new(OverlappingFieldsCanBeMerged::new()));
948    let errors = test_operation_with_schema(
949        r#"fragment mergeIdenticalFieldsWithIdenticalArgs on Dog {
950          doesKnowCommand(dogCommand: $dogCommand)
951          doesKnowCommand(dogCommand: $dogCommand)
952        }"#,
953        TEST_SCHEMA,
954        &mut plan,
955    );
956
957    assert_eq!(get_messages(&errors).len(), 0);
958}
959
960#[test]
961fn identical_fields_with_different_variables() {
962    use crate::validation::test_utils::*;
963
964    let mut plan = create_plan_from_rule(Box::new(OverlappingFieldsCanBeMerged::new()));
965    let errors = test_operation_with_schema(
966        r#"fragment mergeIdenticalFieldsWithIdenticalArgs on Dog {
967          doesKnowCommand(dogCommand: $catCommand)
968          doesKnowCommand(dogCommand: $dogCommand)
969        }"#,
970        TEST_SCHEMA,
971        &mut plan,
972    );
973
974    let messages = get_messages(&errors);
975    assert_eq!(messages.len(), 1);
976    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."]);
977}
978
979#[test]
980fn identical_fields_and_identical_args() {
981    use crate::validation::test_utils::*;
982
983    let mut plan = create_plan_from_rule(Box::new(OverlappingFieldsCanBeMerged::new()));
984    let errors = test_operation_with_schema(
985        "fragment mergeIdenticalFieldsWithIdenticalArgs on Dog {
986          doesKnowCommand(dogCommand: SIT)
987          doesKnowCommand(dogCommand: SIT)
988        }",
989        TEST_SCHEMA,
990        &mut plan,
991    );
992
993    assert_eq!(get_messages(&errors).len(), 0);
994}
995
996#[test]
997fn identical_fields_and_identical_directives() {
998    use crate::validation::test_utils::*;
999
1000    let mut plan = create_plan_from_rule(Box::new(OverlappingFieldsCanBeMerged::new()));
1001    let errors = test_operation_with_schema(
1002        "fragment mergeSameFieldsWithSameDirectives on Dog {
1003          name @include(if: true)
1004          name @include(if: true)
1005        }",
1006        TEST_SCHEMA,
1007        &mut plan,
1008    );
1009
1010    assert_eq!(get_messages(&errors).len(), 0);
1011}
1012
1013#[test]
1014fn different_args_different_aliases() {
1015    use crate::validation::test_utils::*;
1016
1017    let mut plan = create_plan_from_rule(Box::new(OverlappingFieldsCanBeMerged::new()));
1018    let errors = test_operation_with_schema(
1019        "fragment differentArgsWithDifferentAliases on Dog {
1020          knowsSit: doesKnowCommand(dogCommand: SIT)
1021          knowsDown: doesKnowCommand(dogCommand: DOWN)
1022        }",
1023        TEST_SCHEMA,
1024        &mut plan,
1025    );
1026
1027    assert_eq!(get_messages(&errors).len(), 0);
1028}
1029
1030#[test]
1031fn different_directives_different_aliases() {
1032    use crate::validation::test_utils::*;
1033
1034    let mut plan = create_plan_from_rule(Box::new(OverlappingFieldsCanBeMerged::new()));
1035    let errors = test_operation_with_schema(
1036        "fragment differentDirectivesWithDifferentAliases on Dog {
1037          nameIfTrue: name @include(if: true)
1038          nameIfFalse: name @include(if: false)
1039        }",
1040        TEST_SCHEMA,
1041        &mut plan,
1042    );
1043
1044    assert_eq!(get_messages(&errors).len(), 0);
1045}
1046
1047#[test]
1048fn different_skip_include_directives() {
1049    use crate::validation::test_utils::*;
1050
1051    let mut plan = create_plan_from_rule(Box::new(OverlappingFieldsCanBeMerged::new()));
1052    let errors = test_operation_with_schema(
1053        "fragment differentDirectivesWithDifferentAliases on Dog {
1054          name @include(if: true)
1055          name @include(if: false)
1056        }",
1057        TEST_SCHEMA,
1058        &mut plan,
1059    );
1060
1061    assert_eq!(get_messages(&errors).len(), 0);
1062}
1063
1064#[test]
1065fn same_alias_different_field_target() {
1066    use crate::validation::test_utils::*;
1067
1068    let mut plan = create_plan_from_rule(Box::new(OverlappingFieldsCanBeMerged::new()));
1069    let errors = test_operation_with_schema(
1070        "fragment sameAliasesWithDifferentFieldTargets on Dog {
1071          fido: name
1072          fido: nickname
1073        }",
1074        TEST_SCHEMA,
1075        &mut plan,
1076    );
1077
1078    let messages = get_messages(&errors);
1079    assert_eq!(messages.len(), 1);
1080    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."]);
1081}
1082
1083#[test]
1084fn same_alias_non_overlapping_field_target() {
1085    use crate::validation::test_utils::*;
1086
1087    let mut plan = create_plan_from_rule(Box::new(OverlappingFieldsCanBeMerged::new()));
1088    let errors = test_operation_with_schema(
1089        "fragment sameAliasesWithDifferentFieldTargets on Pet {
1090          ... on Dog {
1091            name
1092          }
1093          ... on Cat {
1094            name: nickname
1095          }
1096        }",
1097        TEST_SCHEMA,
1098        &mut plan,
1099    );
1100
1101    let messages = get_messages(&errors);
1102    assert_eq!(messages.len(), 0);
1103}
1104
1105#[test]
1106fn alias_masking_direct_access() {
1107    use crate::validation::test_utils::*;
1108
1109    let mut plan = create_plan_from_rule(Box::new(OverlappingFieldsCanBeMerged::new()));
1110    let errors = test_operation_with_schema(
1111        "fragment aliasMaskingDirectFieldAccess on Dog {
1112          name: nickname
1113          name
1114        }",
1115        TEST_SCHEMA,
1116        &mut plan,
1117    );
1118
1119    let messages = get_messages(&errors);
1120    assert_eq!(messages.len(), 1);
1121    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."]);
1122}
1123
1124#[test]
1125fn different_args_second_adds() {
1126    use crate::validation::test_utils::*;
1127
1128    let mut plan = create_plan_from_rule(Box::new(OverlappingFieldsCanBeMerged::new()));
1129    let errors = test_operation_with_schema(
1130        "fragment conflictingArgs on Dog {
1131          doesKnowCommand
1132          doesKnowCommand(dogCommand: HEEL)
1133        }",
1134        TEST_SCHEMA,
1135        &mut plan,
1136    );
1137
1138    let messages = get_messages(&errors);
1139    assert_eq!(messages.len(), 1);
1140    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."]);
1141}
1142
1143#[test]
1144fn different_args_declared_on_first() {
1145    use crate::validation::test_utils::*;
1146
1147    let mut plan = create_plan_from_rule(Box::new(OverlappingFieldsCanBeMerged::new()));
1148    let errors = test_operation_with_schema(
1149        "fragment conflictingArgs on Dog {
1150          doesKnowCommand(dogCommand: SIT)
1151          doesKnowCommand
1152        }",
1153        TEST_SCHEMA,
1154        &mut plan,
1155    );
1156
1157    let messages = get_messages(&errors);
1158    assert_eq!(messages.len(), 1);
1159    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."]);
1160}
1161
1162#[test]
1163fn different_arg_values() {
1164    use crate::validation::test_utils::*;
1165
1166    let mut plan = create_plan_from_rule(Box::new(OverlappingFieldsCanBeMerged::new()));
1167    let errors = test_operation_with_schema(
1168        "fragment conflictingArgs on Dog {
1169          doesKnowCommand(dogCommand: SIT)
1170          doesKnowCommand(dogCommand: HEEL)
1171        }",
1172        TEST_SCHEMA,
1173        &mut plan,
1174    );
1175
1176    let messages = get_messages(&errors);
1177    assert_eq!(messages.len(), 1);
1178    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."]);
1179}
1180
1181#[test]
1182fn conflicting_arg_names() {
1183    use crate::validation::test_utils::*;
1184
1185    let mut plan = create_plan_from_rule(Box::new(OverlappingFieldsCanBeMerged::new()));
1186    let errors = test_operation_with_schema(
1187        "fragment conflictingArgs on Dog {
1188          isAtLocation(x: 0)
1189          isAtLocation(y: 0)
1190        }",
1191        TEST_SCHEMA,
1192        &mut plan,
1193    );
1194
1195    let messages = get_messages(&errors);
1196    assert_eq!(messages.len(), 1);
1197    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."]);
1198}
1199
1200#[test]
1201fn allow_different_args_when_possible_with_different_args() {
1202    use crate::validation::test_utils::*;
1203
1204    let mut plan = create_plan_from_rule(Box::new(OverlappingFieldsCanBeMerged::new()));
1205    let errors = test_operation_with_schema(
1206        "fragment conflictingArgs on Pet {
1207          ... on Dog {
1208            name(surname: true)
1209          }
1210          ... on Cat {
1211            name
1212          }
1213        }",
1214        TEST_SCHEMA,
1215        &mut plan,
1216    );
1217
1218    let messages = get_messages(&errors);
1219    assert_eq!(messages.len(), 0);
1220}
1221
1222#[test]
1223fn conflict_in_fragment_spread() {
1224    use crate::validation::test_utils::*;
1225
1226    let mut plan = create_plan_from_rule(Box::new(OverlappingFieldsCanBeMerged::new()));
1227    let errors = test_operation_with_schema(
1228        "query {
1229          ...A
1230          ...B
1231        }
1232        fragment A on Type {
1233          x: a
1234        }
1235        fragment B on Type {
1236          x: b
1237        }",
1238        TEST_SCHEMA,
1239        &mut plan,
1240    );
1241
1242    let messages = get_messages(&errors);
1243    assert_eq!(messages.len(), 1);
1244    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."]);
1245}
1246
1247#[test]
1248fn deep_conflict() {
1249    use crate::validation::test_utils::*;
1250
1251    let mut plan = create_plan_from_rule(Box::new(OverlappingFieldsCanBeMerged::new()));
1252    let errors = test_operation_with_schema(
1253        "{
1254          field {
1255            x: a
1256          }
1257          field {
1258            x: b
1259          }
1260        }",
1261        TEST_SCHEMA,
1262        &mut plan,
1263    );
1264
1265    let messages = get_messages(&errors);
1266    assert_eq!(messages.len(), 1);
1267    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."]);
1268}
1269
1270#[test]
1271fn report_each_conflict_once() {
1272    use crate::validation::test_utils::*;
1273
1274    let mut plan = create_plan_from_rule(Box::new(OverlappingFieldsCanBeMerged::new()));
1275    let errors = test_operation_with_schema(
1276        "{
1277          f1 {
1278            ...A
1279            ...B
1280          }
1281          f2 {
1282            ...B
1283            ...A
1284          }
1285          f3 {
1286            ...A
1287            ...B
1288            x: c
1289          }
1290        }
1291        fragment A on Type {
1292          x: a
1293        }
1294        fragment B on Type {
1295          x: b
1296        }",
1297        TEST_SCHEMA,
1298        &mut plan,
1299    );
1300
1301    let messages = get_messages(&errors);
1302    assert_eq!(messages.len(), 3);
1303    assert_eq!(messages, vec![
1304      "Fields \"x\" conflict because \"a\" and \"b\" are different fields. Use different aliases on the fields to fetch both if this was intentional.",
1305      "Fields \"x\" conflict because \"c\" and \"a\" are different fields. Use different aliases on the fields to fetch both if this was intentional.",
1306      "Fields \"x\" conflict because \"c\" and \"b\" are different fields. Use different aliases on the fields to fetch both if this was intentional."
1307    ]);
1308}
1309
1310#[cfg(test)]
1311pub static OVERLAPPING_RULE_TEST_SCHEMA: &str = "
1312interface SomeBox {
1313  deepBox: SomeBox
1314  unrelatedField: String
1315}
1316type StringBox implements SomeBox {
1317  scalar: String
1318  deepBox: StringBox
1319  unrelatedField: String
1320  listStringBox: [StringBox]
1321  stringBox: StringBox
1322  intBox: IntBox
1323}
1324type IntBox implements SomeBox {
1325  scalar: Int
1326  deepBox: IntBox
1327  unrelatedField: String
1328  listStringBox: [StringBox]
1329  stringBox: StringBox
1330  intBox: IntBox
1331}
1332interface NonNullStringBox1 {
1333  scalar: String!
1334}
1335type NonNullStringBox1Impl implements SomeBox & NonNullStringBox1 {
1336  scalar: String!
1337  unrelatedField: String
1338  deepBox: SomeBox
1339}
1340interface NonNullStringBox2 {
1341  scalar: String!
1342}
1343type NonNullStringBox2Impl implements SomeBox & NonNullStringBox2 {
1344  scalar: String!
1345  unrelatedField: String
1346  deepBox: SomeBox
1347}
1348type Connection {
1349  edges: [Edge]
1350}
1351type Edge {
1352  node: Node
1353}
1354type Node {
1355  id: ID
1356  name: String
1357}
1358type Query {
1359  someBox: SomeBox
1360  connection: Connection
1361}";
1362
1363#[test]
1364fn conflicting_return_types_which_potentially_overlap() {
1365    use crate::validation::test_utils::*;
1366
1367    let mut plan = create_plan_from_rule(Box::new(OverlappingFieldsCanBeMerged::new()));
1368    let errors = test_operation_with_schema(
1369        "{
1370          someBox {
1371            ...on IntBox {
1372              scalar
1373            }
1374            ...on NonNullStringBox1 {
1375              scalar
1376            }
1377          }
1378        }",
1379        OVERLAPPING_RULE_TEST_SCHEMA,
1380        &mut plan,
1381    );
1382
1383    let messages = get_messages(&errors);
1384    assert_eq!(messages.len(), 1);
1385    assert_eq!(messages, vec![
1386      "Fields \"scalar\" conflict because they return conflicting types \"Int\" and \"String!\". Use different aliases on the fields to fetch both if this was intentional."
1387    ]);
1388}
1389
1390#[test]
1391fn compatible_return_shapes_on_different_return_types() {
1392    use crate::validation::test_utils::*;
1393
1394    let mut plan = create_plan_from_rule(Box::new(OverlappingFieldsCanBeMerged::new()));
1395    let errors = test_operation_with_schema(
1396        "{
1397          someBox {
1398            ... on SomeBox {
1399              deepBox {
1400                unrelatedField
1401              }
1402            }
1403            ... on StringBox {
1404              deepBox {
1405                unrelatedField
1406              }
1407            }
1408          }
1409        }",
1410        OVERLAPPING_RULE_TEST_SCHEMA,
1411        &mut plan,
1412    );
1413
1414    let messages = get_messages(&errors);
1415    assert_eq!(messages.len(), 0);
1416}
1417
1418#[test]
1419fn disallows_differing_return_types_despite_no_overlap() {
1420    use crate::validation::test_utils::*;
1421
1422    let mut plan = create_plan_from_rule(Box::new(OverlappingFieldsCanBeMerged::new()));
1423    let errors = test_operation_with_schema(
1424        "{
1425          someBox {
1426            ... on IntBox {
1427              scalar
1428            }
1429            ... on StringBox {
1430              scalar
1431            }
1432          }
1433        }",
1434        OVERLAPPING_RULE_TEST_SCHEMA,
1435        &mut plan,
1436    );
1437
1438    let messages = get_messages(&errors);
1439    assert_eq!(messages.len(), 1);
1440    assert_eq!(messages, vec![
1441      "Fields \"scalar\" conflict because they return conflicting types \"Int\" and \"String\". Use different aliases on the fields to fetch both if this was intentional."
1442    ]);
1443}
1444
1445#[test]
1446fn reports_correctly_when_a_non_exclusive_follows_an_exclusive() {
1447    use crate::validation::test_utils::*;
1448
1449    let mut plan = create_plan_from_rule(Box::new(OverlappingFieldsCanBeMerged::new()));
1450    let errors = test_operation_with_schema(
1451        "{
1452          someBox {
1453            ... on IntBox {
1454              deepBox {
1455                ...X
1456              }
1457            }
1458          }
1459          someBox {
1460            ... on StringBox {
1461              deepBox {
1462                ...Y
1463              }
1464            }
1465          }
1466          memoed: someBox {
1467            ... on IntBox {
1468              deepBox {
1469                ...X
1470              }
1471            }
1472          }
1473          memoed: someBox {
1474            ... on StringBox {
1475              deepBox {
1476                ...Y
1477              }
1478            }
1479          }
1480          other: someBox {
1481            ...X
1482          }
1483          other: someBox {
1484            ...Y
1485          }
1486        }
1487        fragment X on SomeBox {
1488          scalar
1489        }
1490        fragment Y on SomeBox {
1491          scalar: unrelatedField
1492        }",
1493        OVERLAPPING_RULE_TEST_SCHEMA,
1494        &mut plan,
1495    );
1496
1497    let messages = get_messages(&errors);
1498    assert_eq!(messages.len(), 1);
1499    assert_eq!(messages, vec![
1500      "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."
1501    ]);
1502}
1503
1504#[test]
1505fn disallows_differing_return_type_nullability_despite_no_overlap() {
1506    use crate::validation::test_utils::*;
1507
1508    let mut plan = create_plan_from_rule(Box::new(OverlappingFieldsCanBeMerged::new()));
1509    let errors = test_operation_with_schema(
1510        "{
1511          someBox {
1512            ... on NonNullStringBox1 {
1513              scalar
1514            }
1515            ... on StringBox {
1516              scalar
1517            }
1518          }
1519        }",
1520        OVERLAPPING_RULE_TEST_SCHEMA,
1521        &mut plan,
1522    );
1523
1524    let messages = get_messages(&errors);
1525    assert_eq!(messages.len(), 1);
1526    assert_eq!(messages, vec![
1527      "Fields \"scalar\" conflict because they return conflicting types \"String!\" and \"String\". Use different aliases on the fields to fetch both if this was intentional."
1528    ]);
1529}
1530
1531#[test]
1532fn disallows_differing_return_type_list_despite_no_overlap() {
1533    use crate::validation::test_utils::*;
1534
1535    let mut plan = create_plan_from_rule(Box::new(OverlappingFieldsCanBeMerged::new()));
1536    let errors = test_operation_with_schema(
1537        "{
1538          someBox {
1539            ... on IntBox {
1540              box: listStringBox {
1541                scalar
1542              }
1543            }
1544            ... on StringBox {
1545              box: stringBox {
1546                scalar
1547              }
1548            }
1549          }
1550        }",
1551        OVERLAPPING_RULE_TEST_SCHEMA,
1552        &mut plan,
1553    );
1554
1555    let messages = get_messages(&errors);
1556    assert_eq!(messages.len(), 1);
1557    assert_eq!(messages, vec![
1558      "Fields \"box\" conflict because they return conflicting types \"[StringBox]\" and \"StringBox\". Use different aliases on the fields to fetch both if this was intentional."
1559    ]);
1560
1561    let errors = test_operation_with_schema(
1562        "{
1563            someBox {
1564              ... on IntBox {
1565                box: stringBox {
1566                  scalar
1567                }
1568              }
1569              ... on StringBox {
1570                box: listStringBox {
1571                  scalar
1572                }
1573              }
1574            }
1575          }",
1576        OVERLAPPING_RULE_TEST_SCHEMA,
1577        &mut plan,
1578    );
1579
1580    let messages = get_messages(&errors);
1581    assert_eq!(messages.len(), 1);
1582    assert_eq!(messages, vec![
1583      "Fields \"box\" conflict because they return conflicting types \"StringBox\" and \"[StringBox]\". Use different aliases on the fields to fetch both if this was intentional."
1584    ]);
1585}
1586
1587#[test]
1588fn disallows_differing_subfields() {
1589    use crate::validation::test_utils::*;
1590
1591    let mut plan = create_plan_from_rule(Box::new(OverlappingFieldsCanBeMerged::new()));
1592    let errors = test_operation_with_schema(
1593        "{
1594          someBox {
1595            ... on IntBox {
1596              box: stringBox {
1597                val: scalar
1598                val: unrelatedField
1599              }
1600            }
1601            ... on StringBox {
1602              box: stringBox {
1603                val: scalar
1604              }
1605            }
1606          }
1607        }",
1608        OVERLAPPING_RULE_TEST_SCHEMA,
1609        &mut plan,
1610    );
1611
1612    let messages = get_messages(&errors);
1613    assert_eq!(messages.len(), 1);
1614    assert_eq!(messages, vec![
1615      "Fields \"val\" conflict because \"scalar\" and \"unrelatedField\" are different fields. Use different aliases on the fields to fetch both if this was intentional."
1616    ]);
1617}
1618
1619#[test]
1620fn disallows_differing_deep_return_types_despite_no_overlap() {
1621    use crate::validation::test_utils::*;
1622
1623    let mut plan = create_plan_from_rule(Box::new(OverlappingFieldsCanBeMerged::new()));
1624    let errors = test_operation_with_schema(
1625        "{
1626          someBox {
1627            ... on IntBox {
1628              box: stringBox {
1629                scalar
1630              }
1631            }
1632            ... on StringBox {
1633              box: intBox {
1634                scalar
1635              }
1636            }
1637          }
1638        }",
1639        OVERLAPPING_RULE_TEST_SCHEMA,
1640        &mut plan,
1641    );
1642
1643    let messages = get_messages(&errors);
1644    assert_eq!(messages.len(), 1);
1645    assert_eq!(messages, vec![
1646      "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."
1647    ]);
1648}
1649
1650#[test]
1651fn allows_non_conflicting_overlapping_types() {
1652    use crate::validation::test_utils::*;
1653
1654    let mut plan = create_plan_from_rule(Box::new(OverlappingFieldsCanBeMerged::new()));
1655    let errors = test_operation_with_schema(
1656        "{
1657          someBox {
1658            ... on IntBox {
1659              scalar: unrelatedField
1660            }
1661            ... on StringBox {
1662              scalar
1663            }
1664          }
1665        }",
1666        OVERLAPPING_RULE_TEST_SCHEMA,
1667        &mut plan,
1668    );
1669
1670    let messages = get_messages(&errors);
1671    assert_eq!(messages.len(), 0);
1672}
1673
1674#[test]
1675fn same_wrapped_scalar_return_types() {
1676    use crate::validation::test_utils::*;
1677
1678    let mut plan = create_plan_from_rule(Box::new(OverlappingFieldsCanBeMerged::new()));
1679    let errors = test_operation_with_schema(
1680        "{
1681          someBox {
1682            ...on NonNullStringBox1 {
1683              scalar
1684            }
1685            ...on NonNullStringBox2 {
1686              scalar
1687            }
1688          }
1689        }",
1690        OVERLAPPING_RULE_TEST_SCHEMA,
1691        &mut plan,
1692    );
1693
1694    let messages = get_messages(&errors);
1695    assert_eq!(messages.len(), 0);
1696}
1697
1698#[test]
1699fn allows_inline_fragments_without_type_condition() {
1700    use crate::validation::test_utils::*;
1701
1702    let mut plan = create_plan_from_rule(Box::new(OverlappingFieldsCanBeMerged::new()));
1703    let errors = test_operation_with_schema(
1704        "{
1705          a
1706          ... {
1707            a
1708          }
1709        }",
1710        OVERLAPPING_RULE_TEST_SCHEMA,
1711        &mut plan,
1712    );
1713
1714    let messages = get_messages(&errors);
1715    assert_eq!(messages.len(), 0);
1716}
1717
1718#[test]
1719fn compares_deep_types_including_list() {
1720    use crate::validation::test_utils::*;
1721
1722    let mut plan = create_plan_from_rule(Box::new(OverlappingFieldsCanBeMerged::new()));
1723    let errors = test_operation_with_schema(
1724        "{
1725          connection {
1726            ...edgeID
1727            edges {
1728              node {
1729                id: name
1730              }
1731            }
1732          }
1733        }
1734        fragment edgeID on Connection {
1735          edges {
1736            node {
1737              id
1738            }
1739          }
1740        }",
1741        OVERLAPPING_RULE_TEST_SCHEMA,
1742        &mut plan,
1743    );
1744
1745    let messages = get_messages(&errors);
1746    assert_eq!(messages.len(), 1);
1747    assert_eq!(messages, vec![
1748      "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."
1749    ]);
1750}
1751
1752#[test]
1753fn ignores_unknown_types() {
1754    use crate::validation::test_utils::*;
1755
1756    let mut plan = create_plan_from_rule(Box::new(OverlappingFieldsCanBeMerged::new()));
1757    let errors = test_operation_with_schema(
1758        "{
1759          someBox {
1760            ...on UnknownType {
1761              scalar
1762            }
1763            ...on NonNullStringBox2 {
1764              scalar
1765            }
1766          }
1767        }",
1768        OVERLAPPING_RULE_TEST_SCHEMA,
1769        &mut plan,
1770    );
1771
1772    let messages = get_messages(&errors);
1773    assert_eq!(messages.len(), 0);
1774}
1775
1776#[test]
1777fn does_not_infinite_loop_on_recursive_fragment() {
1778    use crate::validation::test_utils::*;
1779
1780    let mut plan = create_plan_from_rule(Box::new(OverlappingFieldsCanBeMerged::new()));
1781    let errors = test_operation_with_schema(
1782        "fragment fragA on Human { name, relatives { name, ...fragA } }",
1783        TEST_SCHEMA,
1784        &mut plan,
1785    );
1786
1787    let messages = get_messages(&errors);
1788    assert_eq!(messages.len(), 0);
1789}
1790
1791#[test]
1792fn does_not_infinite_loop_on_immediately_recursive_fragment() {
1793    use crate::validation::test_utils::*;
1794
1795    let mut plan = create_plan_from_rule(Box::new(OverlappingFieldsCanBeMerged::new()));
1796    let errors = test_operation_with_schema(
1797        "fragment fragA on Human { name, ...fragA }",
1798        TEST_SCHEMA,
1799        &mut plan,
1800    );
1801
1802    let messages = get_messages(&errors);
1803    assert_eq!(messages.len(), 0);
1804}
1805
1806#[test]
1807fn does_not_infinite_loop_on_transitively_recursive_fragment() {
1808    use crate::validation::test_utils::*;
1809
1810    let mut plan = create_plan_from_rule(Box::new(OverlappingFieldsCanBeMerged::new()));
1811    let errors = test_operation_with_schema(
1812        "
1813        fragment fragA on Human { name, ...fragB }
1814        fragment fragB on Human { name, ...fragC }
1815        fragment fragC on Human { name, ...fragA }
1816      ",
1817        TEST_SCHEMA,
1818        &mut plan,
1819    );
1820
1821    let messages = get_messages(&errors);
1822    assert_eq!(messages.len(), 0);
1823}
1824
1825#[test]
1826fn finds_invalid_case_even_with_immediately_recursive_fragment() {
1827    use crate::validation::test_utils::*;
1828
1829    let mut plan = create_plan_from_rule(Box::new(OverlappingFieldsCanBeMerged::new()));
1830    let errors = test_operation_with_schema(
1831        "
1832        fragment sameAliasesWithDifferentFieldTargets on Dog {
1833          ...sameAliasesWithDifferentFieldTargets
1834          fido: name
1835          fido: nickname
1836        }
1837      ",
1838        TEST_SCHEMA,
1839        &mut plan,
1840    );
1841
1842    let messages = get_messages(&errors);
1843    assert_eq!(messages.len(), 1);
1844    assert_eq!(messages, vec![
1845      "Fields \"fido\" conflict because \"name\" and \"nickname\" are different fields. Use different aliases on the fields to fetch both if this was intentional."
1846    ]);
1847}
1848
1849#[test]
1850fn ignores_unknown_fragments() {
1851    use crate::validation::test_utils::*;
1852
1853    let mut plan = create_plan_from_rule(Box::new(OverlappingFieldsCanBeMerged::new()));
1854    let errors = test_operation_with_schema("{ dog ...UnknownFragment }", TEST_SCHEMA, &mut plan);
1855
1856    let messages = get_messages(&errors);
1857    assert_eq!(messages.len(), 0);
1858}
1859
1860#[test]
1861fn does_not_infinite_loop_on_recursive_fragment_with_a_field_named_after_fragment() {
1862    use crate::validation::test_utils::*;
1863
1864    let mut plan = create_plan_from_rule(Box::new(OverlappingFieldsCanBeMerged::new()));
1865    let errors = test_operation_with_schema(
1866        "fragment fragA on Human { fragA, ...fragA }",
1867        TEST_SCHEMA,
1868        &mut plan,
1869    );
1870
1871    let messages = get_messages(&errors);
1872    assert_eq!(messages.len(), 0);
1873}
1874
1875#[test]
1876fn does_not_infinite_loop_on_recursive_fragments_separated_by_fields() {
1877    use crate::validation::test_utils::*;
1878
1879    let mut plan = create_plan_from_rule(Box::new(OverlappingFieldsCanBeMerged::new()));
1880    let errors = test_operation_with_schema(
1881        "{ ...fragA }
1882        fragment fragA on Human { ...fragB }
1883        fragment fragB on Human { name, ...fragA }",
1884        TEST_SCHEMA,
1885        &mut plan,
1886    );
1887
1888    let messages = get_messages(&errors);
1889    assert_eq!(messages.len(), 0);
1890}
1891
1892#[test]
1893fn deep_conflict_with_multiple_issues() {
1894    use crate::validation::test_utils::*;
1895
1896    let mut plan = create_plan_from_rule(Box::new(OverlappingFieldsCanBeMerged::new()));
1897    let errors = test_operation_with_schema(
1898        "{
1899          field {
1900            x: a
1901            y: b
1902          }
1903          field {
1904            x: b
1905            y: a
1906          }
1907        }",
1908        TEST_SCHEMA,
1909        &mut plan,
1910    );
1911
1912    let messages = get_messages(&errors);
1913    assert_eq!(messages.len(), 1);
1914    assert_eq!(messages, vec![
1915      "Fields \"field\" conflict because subfields \"x\" conflict because \"a\" and \"b\" are different fields and subfields \"y\" conflict because \"b\" and \"a\" are different fields. Use different aliases on the fields to fetch both if this was intentional."
1916    ]);
1917}
1918
1919#[test]
1920fn very_deep_conflict() {
1921    use crate::validation::test_utils::*;
1922
1923    let mut plan = create_plan_from_rule(Box::new(OverlappingFieldsCanBeMerged::new()));
1924    let errors = test_operation_with_schema(
1925        "{
1926          field {
1927            deepField {
1928              x: a
1929            }
1930          }
1931          field {
1932            deepField {
1933              x: b
1934            }
1935          }
1936        }",
1937        TEST_SCHEMA,
1938        &mut plan,
1939    );
1940
1941    let messages = get_messages(&errors);
1942    assert_eq!(messages.len(), 1);
1943    assert_eq!(messages, vec![
1944      "Fields \"field\" conflict because subfields \"deepField\" 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."
1945    ]);
1946}
1947
1948#[test]
1949fn reports_deep_conflict_to_nearest_common_ancestor() {
1950    use crate::validation::test_utils::*;
1951
1952    let mut plan = create_plan_from_rule(Box::new(OverlappingFieldsCanBeMerged::new()));
1953    let errors = test_operation_with_schema(
1954        "{
1955          field {
1956            x: a
1957          }
1958          field {
1959            ...F
1960          }
1961        }
1962        fragment F on Type {
1963          x: b
1964        }",
1965        TEST_SCHEMA,
1966        &mut plan,
1967    );
1968
1969    let messages = get_messages(&errors);
1970    assert_eq!(messages.len(), 1);
1971    assert_eq!(messages, vec![
1972      "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."
1973    ]);
1974}
1975
1976#[test]
1977fn reports_deep_conflict_to_nearest_common_ancestor_in_fragments() {
1978    use crate::validation::test_utils::*;
1979
1980    let mut plan = create_plan_from_rule(Box::new(OverlappingFieldsCanBeMerged::new()));
1981    let errors = test_operation_with_schema(
1982        "{
1983          field {
1984            ...F
1985          }
1986          field {
1987            ...G
1988          }
1989        }
1990        fragment F on Type {
1991          x: a
1992        }
1993        fragment G on Type {
1994          x: b
1995        }",
1996        TEST_SCHEMA,
1997        &mut plan,
1998    );
1999
2000    let messages = get_messages(&errors);
2001    assert_eq!(messages.len(), 1);
2002    assert_eq!(messages, vec![
2003      "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."
2004    ]);
2005}
2006
2007#[test]
2008fn reports_deep_conflict_in_nested_fragments() {
2009    use crate::validation::test_utils::*;
2010
2011    let mut plan = create_plan_from_rule(Box::new(OverlappingFieldsCanBeMerged::new()));
2012    let errors = test_operation_with_schema(
2013        "{
2014          field {
2015            ...F
2016          }
2017          field {
2018            x: b
2019          }
2020        }
2021        fragment F on Type {
2022          x: a
2023        }",
2024        TEST_SCHEMA,
2025        &mut plan,
2026    );
2027
2028    let messages = get_messages(&errors);
2029    assert_eq!(messages.len(), 1);
2030    assert_eq!(messages, vec![
2031      "Fields \"field\" conflict because subfields \"x\" conflict because \"b\" and \"a\" are different fields. Use different aliases on the fields to fetch both if this was intentional."
2032    ]);
2033}
2034
2035#[test]
2036fn reports_deep_conflict_after_nested_fragments() {
2037    use crate::validation::test_utils::*;
2038
2039    let mut plan = create_plan_from_rule(Box::new(OverlappingFieldsCanBeMerged::new()));
2040    let errors = test_operation_with_schema(
2041        "{
2042          field {
2043            x: a
2044          }
2045          ...F
2046        }
2047        fragment F on Type {
2048          field {
2049            x: b
2050          }
2051        }",
2052        TEST_SCHEMA,
2053        &mut plan,
2054    );
2055
2056    let messages = get_messages(&errors);
2057    assert_eq!(messages.len(), 1);
2058    assert_eq!(messages, vec![
2059      "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."
2060    ]);
2061}
2062
2063#[test]
2064fn finds_invalid_cases_even_with_field_named_after_fragment() {
2065    use crate::validation::test_utils::*;
2066
2067    let mut plan = create_plan_from_rule(Box::new(OverlappingFieldsCanBeMerged::new()));
2068    let errors = test_operation_with_schema(
2069        "fragment fragA on Type {
2070          fragA,
2071          ...fragA,
2072          x: a,
2073          x: b
2074        }",
2075        TEST_SCHEMA,
2076        &mut plan,
2077    );
2078
2079    let messages = get_messages(&errors);
2080    assert_eq!(messages.len(), 1);
2081    assert_eq!(messages, vec![
2082      "Fields \"x\" conflict because \"a\" and \"b\" are different fields. Use different aliases on the fields to fetch both if this was intentional."
2083    ]);
2084}
2085
2086#[test]
2087fn allows_different_order_of_args() {
2088    use crate::validation::test_utils::*;
2089
2090    let schema = "
2091      type Query {
2092        f: Type
2093      }
2094      type Type {
2095        f(a: Int, b: Int): Int
2096      }
2097    ";
2098    let mut plan = create_plan_from_rule(Box::new(OverlappingFieldsCanBeMerged::new()));
2099    let errors = test_operation_with_schema(
2100        "{ f { f(a: 1, b: 2) } f { f(b: 2, a: 1) } }",
2101        schema,
2102        &mut plan,
2103    );
2104
2105    let messages = get_messages(&errors);
2106    assert_eq!(messages.len(), 0);
2107}
2108
2109#[test]
2110fn allows_different_order_of_input_object_fields_in_arg_values() {
2111    use crate::validation::test_utils::*;
2112
2113    let schema = "
2114      type Query {
2115        f(order: Input): String
2116      }
2117      input Input {
2118        a: Int
2119        b: Int
2120      }
2121    ";
2122    let mut plan = create_plan_from_rule(Box::new(OverlappingFieldsCanBeMerged::new()));
2123    let errors = test_operation_with_schema(
2124        "{ f(order: {a: 1, b: 2}) f(order: {b: 2, a: 1}) }",
2125        schema,
2126        &mut plan,
2127    );
2128
2129    let messages = get_messages(&errors);
2130    assert_eq!(messages.len(), 0);
2131}
2132
2133#[test]
2134fn works_for_field_names_that_are_js_keywords() {
2135    use crate::validation::test_utils::*;
2136
2137    let schema = "
2138      type Query {
2139        null: String
2140        true: String
2141        false: String
2142      }
2143    ";
2144    let mut plan = create_plan_from_rule(Box::new(OverlappingFieldsCanBeMerged::new()));
2145    let errors = test_operation_with_schema("{ null true false }", schema, &mut plan);
2146
2147    let messages = get_messages(&errors);
2148    assert_eq!(messages.len(), 0);
2149}