@@ -20,105 +20,6 @@ def __init__(self, context):
20
20
super (OverlappingFieldsCanBeMerged , self ).__init__ (context )
21
21
self .compared_set = PairSet ()
22
22
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
-
122
23
def leave_SelectionSet (self , node , key , parent , path , ancestors ):
123
24
# Note: we validate on the reverse traversal so deeper conflicts will be
124
25
# caught first, for correct calculation of mutual exclusivity and for
@@ -129,7 +30,7 @@ def leave_SelectionSet(self, node, key, parent, path, ancestors):
129
30
node
130
31
)
131
32
132
- conflicts = self .find_conflicts ( False , field_map )
33
+ conflicts = _find_conflicts ( self .context , False , field_map , self . compared_set )
133
34
if conflicts :
134
35
for (reason_name , reason ), fields1 , fields2 in conflicts :
135
36
self .context .report_error (
@@ -145,32 +46,6 @@ def same_type(type1, type2):
145
46
return is_equal_type (type1 , type2 )
146
47
# return type1.is_same_type(type2)
147
48
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
-
174
49
@classmethod
175
50
def fields_conflict_message (cls , reason_name , reason ):
176
51
return (
@@ -188,6 +63,111 @@ def reason_message(cls, reason):
188
63
return reason
189
64
190
65
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
+
191
171
def _collect_field_asts_and_defs (context , parent_type , selection_set , visited_fragment_names = None , ast_and_defs = None ):
192
172
if visited_fragment_names is None :
193
173
visited_fragment_names = set ()
@@ -305,3 +285,29 @@ def do_types_conflict(type1, type2):
305
285
return type1 != type2
306
286
307
287
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