Skip to content

Commit 4e752d5

Browse files
committed
factored out more instance methods. Commit: graphql/graphql-js@cf9be87
1 parent 9d302f5 commit 4e752d5

File tree

1 file changed

+132
-126
lines changed

1 file changed

+132
-126
lines changed

graphql/validation/rules/overlapping_fields_can_be_merged.py

Lines changed: 132 additions & 126 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
191171
def _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

Comments
 (0)