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;
15pub struct OverlappingFieldsCanBeMerged<'doc> {
23 named_fragments: HashMap<&'doc str, &'doc FragmentDefinition>,
24 compared_fragments: PairSet<'doc>,
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<'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 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 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: &'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 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: &'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 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: &'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 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: &'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 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 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: &'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 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 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: &'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}