@@ -20,105 +20,6 @@ def __init__(self, context):
2020 super (OverlappingFieldsCanBeMerged , self ).__init__ (context )
2121 self .compared_set = PairSet ()
2222
23- def find_conflicts (self , parent_fields_are_mutually_exclusive , field_map ):
24- conflicts = []
25- for response_name , fields in field_map .items ():
26- field_len = len (fields )
27- if field_len <= 1 :
28- continue
29-
30- for field_a in fields :
31- for field_b in fields :
32- conflict = self .find_conflict (
33- parent_fields_are_mutually_exclusive ,
34- response_name ,
35- field_a ,
36- field_b
37- )
38- if conflict :
39- conflicts .append (conflict )
40-
41- return conflicts
42-
43- def find_conflict (self , parent_fields_are_mutually_exclusive , response_name , field1 , field2 ):
44- parent_type1 , ast1 , def1 = field1
45- parent_type2 , ast2 , def2 = field2
46-
47- # Not a pair
48- if ast1 is ast2 :
49- return
50-
51- # Memoize, do not report the same issue twice.
52- # Note: Two overlapping ASTs could be encountered both when
53- # `parentFieldsAreMutuallyExclusive` is true and is false, which could
54- # produce different results (when `true` being a subset of `false`).
55- # However we do not need to include this piece of information when
56- # memoizing since this rule visits leaf fields before their parent fields,
57- # ensuring that `parentFieldsAreMutuallyExclusive` is `false` the first
58- # time two overlapping fields are encountered, ensuring that the full
59- # set of validation rules are always checked when necessary.
60-
61- # if parent_type1 != parent_type2 and \
62- # isinstance(parent_type1, GraphQLObjectType) and \
63- # isinstance(parent_type2, GraphQLObjectType):
64- # return
65-
66- if self .compared_set .has (ast1 , ast2 ):
67- return
68-
69- self .compared_set .add (ast1 , ast2 )
70-
71- # The return type for each field.
72- type1 = def1 and def1 .type
73- type2 = def2 and def2 .type
74-
75- # If it is known that two fields could not possibly apply at the same
76- # time, due to the parent types, then it is safe to permit them to diverge
77- # in aliased field or arguments used as they will not present any ambiguity
78- # by differing.
79- # It is known that two parent types could never overlap if they are
80- # different Object types. Interface or Union types might overlap - if not
81- # in the current state of the schema, then perhaps in some future version,
82- # thus may not safely diverge.
83-
84- fields_are_mutually_exclusive = (
85- parent_fields_are_mutually_exclusive or (
86- parent_type1 != parent_type2 and
87- isinstance (parent_type1 , GraphQLObjectType ) and
88- isinstance (parent_type2 , GraphQLObjectType )
89- )
90- )
91-
92- if not fields_are_mutually_exclusive :
93- name1 = ast1 .name .value
94- name2 = ast2 .name .value
95-
96- if name1 != name2 :
97- return (
98- (response_name , '{} and {} are different fields' .format (name1 , name2 )),
99- [ast1 ],
100- [ast2 ]
101- )
102-
103- if not self .same_arguments (ast1 .arguments , ast2 .arguments ):
104- return (
105- (response_name , 'they have differing arguments' ),
106- [ast1 ],
107- [ast2 ]
108- )
109-
110- if type1 and type2 and do_types_conflict (type1 , type2 ):
111- return (
112- (response_name , 'they return conflicting types {} and {}' .format (type1 , type2 )),
113- [ast1 ],
114- [ast2 ]
115- )
116-
117- subfield_map = _get_subfield_map (self .context , ast1 , type1 , ast2 , type2 )
118- if subfield_map :
119- conflicts = self .find_conflicts (fields_are_mutually_exclusive , subfield_map )
120- return _subfield_conflicts (conflicts , response_name , ast1 , ast2 )
121-
12223 def leave_SelectionSet (self , node , key , parent , path , ancestors ):
12324 # Note: we validate on the reverse traversal so deeper conflicts will be
12425 # caught first, for correct calculation of mutual exclusivity and for
@@ -129,7 +30,7 @@ def leave_SelectionSet(self, node, key, parent, path, ancestors):
12930 node
13031 )
13132
132- conflicts = self .find_conflicts ( False , field_map )
33+ conflicts = _find_conflicts ( self .context , False , field_map , self . compared_set )
13334 if conflicts :
13435 for (reason_name , reason ), fields1 , fields2 in conflicts :
13536 self .context .report_error (
@@ -145,32 +46,6 @@ def same_type(type1, type2):
14546 return is_equal_type (type1 , type2 )
14647 # return type1.is_same_type(type2)
14748
148- @staticmethod
149- def same_value (value1 , value2 ):
150- return (not value1 and not value2 ) or print_ast (value1 ) == print_ast (value2 )
151-
152- @classmethod
153- def same_arguments (cls , arguments1 , arguments2 ):
154- # Check to see if they are empty arguments or nones. If they are, we can
155- # bail out early.
156- if not (arguments1 or arguments2 ):
157- return True
158-
159- if len (arguments1 ) != len (arguments2 ):
160- return False
161-
162- arguments2_values_to_arg = {a .name .value : a for a in arguments2 }
163-
164- for argument1 in arguments1 :
165- argument2 = arguments2_values_to_arg .get (argument1 .name .value )
166- if not argument2 :
167- return False
168-
169- if not cls .same_value (argument1 .value , argument2 .value ):
170- return False
171-
172- return True
173-
17449 @classmethod
17550 def fields_conflict_message (cls , reason_name , reason ):
17651 return (
@@ -188,6 +63,111 @@ def reason_message(cls, reason):
18863 return reason
18964
19065
66+ def _find_conflict (context , parent_fields_are_mutually_exclusive , response_name , field1 , field2 , compared_set ):
67+ """Determines if there is a conflict between two particular fields."""
68+ parent_type1 , ast1 , def1 = field1
69+ parent_type2 , ast2 , def2 = field2
70+
71+ # Not a pair
72+ if ast1 is ast2 :
73+ return
74+
75+ # Memoize, do not report the same issue twice.
76+ # Note: Two overlapping ASTs could be encountered both when
77+ # `parentFieldsAreMutuallyExclusive` is true and is false, which could
78+ # produce different results (when `true` being a subset of `false`).
79+ # However we do not need to include this piece of information when
80+ # memoizing since this rule visits leaf fields before their parent fields,
81+ # ensuring that `parentFieldsAreMutuallyExclusive` is `false` the first
82+ # time two overlapping fields are encountered, ensuring that the full
83+ # set of validation rules are always checked when necessary.
84+
85+ # if parent_type1 != parent_type2 and \
86+ # isinstance(parent_type1, GraphQLObjectType) and \
87+ # isinstance(parent_type2, GraphQLObjectType):
88+ # return
89+
90+ if compared_set .has (ast1 , ast2 ):
91+ return
92+
93+ compared_set .add (ast1 , ast2 )
94+
95+ # The return type for each field.
96+ type1 = def1 and def1 .type
97+ type2 = def2 and def2 .type
98+
99+ # If it is known that two fields could not possibly apply at the same
100+ # time, due to the parent types, then it is safe to permit them to diverge
101+ # in aliased field or arguments used as they will not present any ambiguity
102+ # by differing.
103+ # It is known that two parent types could never overlap if they are
104+ # different Object types. Interface or Union types might overlap - if not
105+ # in the current state of the schema, then perhaps in some future version,
106+ # thus may not safely diverge.
107+
108+ fields_are_mutually_exclusive = (
109+ parent_fields_are_mutually_exclusive or (
110+ parent_type1 != parent_type2 and
111+ isinstance (parent_type1 , GraphQLObjectType ) and
112+ isinstance (parent_type2 , GraphQLObjectType )
113+ )
114+ )
115+
116+ if not fields_are_mutually_exclusive :
117+ name1 = ast1 .name .value
118+ name2 = ast2 .name .value
119+
120+ if name1 != name2 :
121+ return (
122+ (response_name , '{} and {} are different fields' .format (name1 , name2 )),
123+ [ast1 ],
124+ [ast2 ]
125+ )
126+
127+ if not _same_arguments (ast1 .arguments , ast2 .arguments ):
128+ return (
129+ (response_name , 'they have differing arguments' ),
130+ [ast1 ],
131+ [ast2 ]
132+ )
133+
134+ if type1 and type2 and do_types_conflict (type1 , type2 ):
135+ return (
136+ (response_name , 'they return conflicting types {} and {}' .format (type1 , type2 )),
137+ [ast1 ],
138+ [ast2 ]
139+ )
140+
141+ subfield_map = _get_subfield_map (context , ast1 , type1 , ast2 , type2 )
142+ if subfield_map :
143+ conflicts = _find_conflicts (context , fields_are_mutually_exclusive , subfield_map , compared_set )
144+ return _subfield_conflicts (conflicts , response_name , ast1 , ast2 )
145+
146+
147+ def _find_conflicts (context , parent_fields_are_mutually_exclusive , field_map , compared_set ):
148+ """Find all Conflicts within a collection of fields."""
149+ conflicts = []
150+ for response_name , fields in field_map .items ():
151+ field_len = len (fields )
152+ if field_len <= 1 :
153+ continue
154+
155+ for field_a in fields :
156+ for field_b in fields :
157+ conflict = _find_conflict (
158+ context ,
159+ parent_fields_are_mutually_exclusive ,
160+ response_name ,
161+ field_a ,
162+ field_b ,
163+ compared_set
164+ )
165+ if conflict :
166+ conflicts .append (conflict )
167+
168+ return conflicts
169+
170+
191171def _collect_field_asts_and_defs (context , parent_type , selection_set , visited_fragment_names = None , ast_and_defs = None ):
192172 if visited_fragment_names is None :
193173 visited_fragment_names = set ()
@@ -305,3 +285,29 @@ def do_types_conflict(type1, type2):
305285 return type1 != type2
306286
307287 return False
288+
289+
290+ def _same_value (value1 , value2 ):
291+ return (not value1 and not value2 ) or print_ast (value1 ) == print_ast (value2 )
292+
293+
294+ def _same_arguments (arguments1 , arguments2 ):
295+ # Check to see if they are empty arguments or nones. If they are, we can
296+ # bail out early.
297+ if not (arguments1 or arguments2 ):
298+ return True
299+
300+ if len (arguments1 ) != len (arguments2 ):
301+ return False
302+
303+ arguments2_values_to_arg = {a .name .value : a for a in arguments2 }
304+
305+ for argument1 in arguments1 :
306+ argument2 = arguments2_values_to_arg .get (argument1 .name .value )
307+ if not argument2 :
308+ return False
309+
310+ if not _same_value (argument1 .value , argument2 .value ):
311+ return False
312+
313+ return True
0 commit comments