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