Skip to content

Commit c9f1345

Browse files
committed
Improved GraphQLError with path attached
1 parent 2322798 commit c9f1345

File tree

9 files changed

+156
-35
lines changed

9 files changed

+156
-35
lines changed

graphql/error/format_error.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,7 @@ def format_error(error):
1111
{'line': loc.line, 'column': loc.column}
1212
for loc in error.locations
1313
]
14+
if error.path is not None:
15+
formatted_error['path'] = error.path
1416

1517
return formatted_error

graphql/execution/executor.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -404,7 +404,7 @@ def complete_value(exe_context, return_type, field_asts, info, path, result):
404404
resolved
405405
),
406406
lambda error: Promise.rejected(
407-
GraphQLLocatedError(field_asts, original_error=error))
407+
GraphQLLocatedError(field_asts, original_error=error, path=path))
408408
)
409409

410410
# print return_type, type(result)
@@ -552,7 +552,8 @@ def complete_nonnull_value(exe_context, return_type, field_asts, info, path, res
552552
raise GraphQLError(
553553
'Cannot return null for non-nullable field {}.{}.'.format(
554554
info.parent_type, info.field_name),
555-
field_asts
555+
field_asts,
556+
path=path
556557
)
557558

558559
return completed

graphql/execution/tests/test_executor_asyncio.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,11 @@ def resolver_2(context, *_):
8686

8787
result = execute(GraphQLSchema(Type), ast, executor=AsyncioExecutor())
8888
formatted_errors = list(map(format_error, result.errors))
89-
assert formatted_errors == [{'locations': [{'line': 1, 'column': 20}], 'message': 'resolver_2 failed!'}]
89+
assert formatted_errors == [{
90+
'locations': [{'line': 1, 'column': 20}],
91+
'path': ['b'],
92+
'message': 'resolver_2 failed!'
93+
}]
9094
assert result.data == {'a': 'hey', 'b': None}
9195

9296

graphql/execution/tests/test_executor_gevent.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,11 @@ def resolver_2(context, *_):
5959

6060
result = execute(GraphQLSchema(Type), ast, executor=GeventExecutor())
6161
formatted_errors = list(map(format_error, result.errors))
62-
assert formatted_errors == [{'locations': [{'line': 1, 'column': 20}], 'message': 'resolver_2 failed!'}]
62+
assert formatted_errors == [{
63+
'locations': [{'line': 1, 'column': 20}],
64+
'path': ['b'],
65+
'message': 'resolver_2 failed!'
66+
}]
6367
assert result.data == {'a': 'hey', 'b': None}
6468

6569

graphql/execution/tests/test_executor_thread.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -206,13 +206,13 @@ def handle_results(result):
206206
'syncReturnErrorList': ['sync0', None, 'sync2', None]
207207
}
208208
assert sorted(list(map(format_error, result.errors)), key=sort_key) == sorted([
209-
{'locations': [{'line': 4, 'column': 9}], 'message': 'Error getting syncError'},
210-
{'locations': [{'line': 5, 'column': 9}], 'message': 'Error getting syncReturnError'},
211-
{'locations': [{'line': 6, 'column': 9}], 'message': 'Error getting syncReturnErrorList1'},
212-
{'locations': [{'line': 6, 'column': 9}], 'message': 'Error getting syncReturnErrorList3'},
213-
{'locations': [{'line': 8, 'column': 9}], 'message': 'Error getting asyncReject'},
214-
{'locations': [{'line': 9, 'column': 9}], 'message': 'An unknown error occurred.'},
215-
{'locations': [{'line': 10, 'column': 9}], 'message': 'Error getting asyncReturnError'}
209+
{'locations': [{'line': 4, 'column': 9}], 'path':['syncError'], 'message': 'Error getting syncError'},
210+
{'locations': [{'line': 5, 'column': 9}], 'path':['syncReturnError'], 'message': 'Error getting syncReturnError'},
211+
{'locations': [{'line': 6, 'column': 9}], 'path':['syncReturnErrorList', 1], 'message': 'Error getting syncReturnErrorList1'},
212+
{'locations': [{'line': 6, 'column': 9}], 'path':['syncReturnErrorList', 3], 'message': 'Error getting syncReturnErrorList3'},
213+
{'locations': [{'line': 8, 'column': 9}], 'path':['asyncReject'], 'message': 'Error getting asyncReject'},
214+
{'locations': [{'line': 9, 'column': 9}], 'path':['asyncEmptyReject'], 'message': 'An unknown error occurred.'},
215+
{'locations': [{'line': 10, 'column': 9}], 'path':['asyncReturnError'], 'message': 'Error getting asyncReturnError'}
216216
], key=sort_key)
217217

218218
handle_results(execute(schema, ast, Data(), executor=ThreadExecutor()))

graphql/execution/tests/test_lists.py

Lines changed: 50 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,11 @@ class Test_ListOfT_Promise_Array_T: # [T] Promise<Array<T>>
5959
test_returns_null = check(resolved(None), {'data': {'nest': {'test': None}}})
6060
test_rejected = check(lambda: rejected(Exception('bad')), {
6161
'data': {'nest': {'test': None}},
62-
'errors': [{'locations': [{'column': 10, 'line': 1}], 'message': 'bad'}]
62+
'errors': [{
63+
'locations': [{'column': 10, 'line': 1}],
64+
'path': ['nest', 'test'],
65+
'message': 'bad'
66+
}]
6367
})
6468

6569

@@ -70,7 +74,11 @@ class Test_ListOfT_Array_Promise_T: # [T] Array<Promise<T>>
7074
test_contains_null = check([resolved(1), resolved(None), resolved(2)], {'data': {'nest': {'test': [1, None, 2]}}})
7175
test_contains_reject = check(lambda: [resolved(1), rejected(Exception('bad')), resolved(2)], {
7276
'data': {'nest': {'test': [1, None, 2]}},
73-
'errors': [{'locations': [{'column': 10, 'line': 1}], 'message': 'bad'}]
77+
'errors': [{
78+
'locations': [{'column': 10, 'line': 1}],
79+
'path': ['nest', 'test', 1],
80+
'message': 'bad'
81+
}]
7482
})
7583

7684

@@ -82,6 +90,7 @@ class Test_NotNullListOfT_Array_T: # [T]! Array<T>
8290
test_returns_null = check(resolved(None), {
8391
'data': {'nest': None},
8492
'errors': [{'locations': [{'column': 10, 'line': 1}],
93+
'path': ['nest', 'test'],
8594
'message': 'Cannot return null for non-nullable field DataType.test.'}]
8695
})
8796

@@ -94,12 +103,17 @@ class Test_NotNullListOfT_Promise_Array_T: # [T]! Promise<Array<T>>>
94103
test_returns_null = check(resolved(None), {
95104
'data': {'nest': None},
96105
'errors': [{'locations': [{'column': 10, 'line': 1}],
106+
'path': ['nest', 'test'],
97107
'message': 'Cannot return null for non-nullable field DataType.test.'}]
98108
})
99109

100110
test_rejected = check(lambda: rejected(Exception('bad')), {
101111
'data': {'nest': None},
102-
'errors': [{'locations': [{'column': 10, 'line': 1}], 'message': 'bad'}]
112+
'errors': [{
113+
'locations': [{'column': 10, 'line': 1}],
114+
'path': ['nest', 'test'],
115+
'message': 'bad'
116+
}]
103117
})
104118

105119

@@ -109,7 +123,11 @@ class Test_NotNullListOfT_Array_Promise_T: # [T]! Promise<Array<T>>>
109123
test_contains_null = check([resolved(1), resolved(None), resolved(2)], {'data': {'nest': {'test': [1, None, 2]}}})
110124
test_contains_reject = check(lambda: [resolved(1), rejected(Exception('bad')), resolved(2)], {
111125
'data': {'nest': {'test': [1, None, 2]}},
112-
'errors': [{'locations': [{'column': 10, 'line': 1}], 'message': 'bad'}]
126+
'errors': [{
127+
'locations': [{'column': 10, 'line': 1}],
128+
'path': ['nest', 'test', 1],
129+
'message': 'bad'
130+
}]
113131
})
114132

115133

@@ -120,6 +138,7 @@ class TestListOfNotNullT_Array_T: # [T!] Array<T>
120138
test_contains_null = check([1, None, 2], {
121139
'data': {'nest': {'test': None}},
122140
'errors': [{'locations': [{'column': 10, 'line': 1}],
141+
'path': ['nest', 'test', 1],
123142
'message': 'Cannot return null for non-nullable field DataType.test.'}]
124143
})
125144
test_returns_null = check(None, {'data': {'nest': {'test': None}}})
@@ -132,14 +151,19 @@ class TestListOfNotNullT_Promise_Array_T: # [T!] Promise<Array<T>>
132151
test_contains_null = check(resolved([1, None, 2]), {
133152
'data': {'nest': {'test': None}},
134153
'errors': [{'locations': [{'column': 10, 'line': 1}],
154+
'path': ['nest', 'test', 1],
135155
'message': 'Cannot return null for non-nullable field DataType.test.'}]
136156
})
137157

138158
test_returns_null = check(resolved(None), {'data': {'nest': {'test': None}}})
139159

140160
test_rejected = check(lambda: rejected(Exception('bad')), {
141161
'data': {'nest': {'test': None}},
142-
'errors': [{'locations': [{'column': 10, 'line': 1}], 'message': 'bad'}]
162+
'errors': [{
163+
'locations': [{'column': 10, 'line': 1}],
164+
'path': ['nest', 'test'],
165+
'message': 'bad'
166+
}]
143167
})
144168

145169

@@ -150,11 +174,16 @@ class TestListOfNotNullT_Array_Promise_T: # [T!] Array<Promise<T>>
150174
test_contains_null = check([resolved(1), resolved(None), resolved(2)], {
151175
'data': {'nest': {'test': None}},
152176
'errors': [{'locations': [{'column': 10, 'line': 1}],
177+
'path': ['nest', 'test', 1],
153178
'message': 'Cannot return null for non-nullable field DataType.test.'}]
154179
})
155180
test_contains_reject = check(lambda: [resolved(1), rejected(Exception('bad')), resolved(2)], {
156181
'data': {'nest': {'test': None}},
157-
'errors': [{'locations': [{'column': 10, 'line': 1}], 'message': 'bad'}]
182+
'errors': [{
183+
'locations': [{'column': 10, 'line': 1}],
184+
'path': ['nest', 'test', 1],
185+
'message': 'bad'
186+
}]
158187
})
159188

160189

@@ -165,11 +194,13 @@ class TestNotNullListOfNotNullT_Array_T: # [T!]! Array<T>
165194
test_contains_null = check([1, None, 2], {
166195
'data': {'nest': None},
167196
'errors': [{'locations': [{'column': 10, 'line': 1}],
197+
'path': ['nest', 'test', 1],
168198
'message': 'Cannot return null for non-nullable field DataType.test.'}]
169199
})
170200
test_returns_null = check(None, {
171201
'data': {'nest': None},
172202
'errors': [{'locations': [{'column': 10, 'line': 1}],
203+
'path': ['nest', 'test'],
173204
'message': 'Cannot return null for non-nullable field DataType.test.'}]
174205
})
175206

@@ -181,18 +212,24 @@ class TestNotNullListOfNotNullT_Promise_Array_T: # [T!]! Promise<Array<T>>
181212
test_contains_null = check(resolved([1, None, 2]), {
182213
'data': {'nest': None},
183214
'errors': [{'locations': [{'column': 10, 'line': 1}],
215+
'path': ['nest', 'test', 1],
184216
'message': 'Cannot return null for non-nullable field DataType.test.'}]
185217
})
186218

187219
test_returns_null = check(resolved(None), {
188220
'data': {'nest': None},
189221
'errors': [{'locations': [{'column': 10, 'line': 1}],
222+
'path': ['nest', 'test'],
190223
'message': 'Cannot return null for non-nullable field DataType.test.'}]
191224
})
192225

193226
test_rejected = check(lambda: rejected(Exception('bad')), {
194227
'data': {'nest': None},
195-
'errors': [{'locations': [{'column': 10, 'line': 1}], 'message': 'bad'}]
228+
'errors': [{
229+
'locations': [{'column': 10, 'line': 1}],
230+
'path': ['nest', 'test'],
231+
'message': 'bad'
232+
}]
196233
})
197234

198235

@@ -203,9 +240,14 @@ class TestNotNullListOfNotNullT_Array_Promise_T: # [T!]! Array<Promise<T>>
203240
test_contains_null = check([resolved(1), resolved(None), resolved(2)], {
204241
'data': {'nest': None},
205242
'errors': [{'locations': [{'column': 10, 'line': 1}],
243+
'path': ['nest', 'test', 1],
206244
'message': 'Cannot return null for non-nullable field DataType.test.'}]
207245
})
208246
test_contains_reject = check(lambda: [resolved(1), rejected(Exception('bad')), resolved(2)], {
209247
'data': {'nest': None},
210-
'errors': [{'locations': [{'column': 10, 'line': 1}], 'message': 'bad'}]
248+
'errors': [{
249+
'locations': [{'column': 10, 'line': 1}],
250+
'path': ['nest', 'test', 1],
251+
'message': 'bad'
252+
}]
211253
})

0 commit comments

Comments
 (0)