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;
15pub struct OverlappingFieldsCanBeMerged<'a> {
23 named_fragments: HashMap<&'a str, &'a FragmentDefinition>,
24 compared_fragments: PairSet<'a>,
25}
26
27#[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 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 self.collect_conflicts_within(schema, &mut conflicts, &field_map, visited_fragments);
231
232 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 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 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 for (out_field_name, fields) in field_map.iter() {
276 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, 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 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 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 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 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 #[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 self.collect_conflicts_between(
518 schema,
519 &mut conflicts,
520 mutually_exclusive,
521 &field_map1,
522 &field_map2,
523 visited_fragments,
524 );
525
526 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 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 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 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 if fragment_name1.eq(fragment_name2) {
632 return;
633 }
634
635 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 self.collect_conflicts_between(
664 schema,
665 conflicts,
666 mutually_exclusive,
667 &field_map1,
668 &field_map2,
669 visited_fragments,
670 );
671
672 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 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 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 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 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 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}