Skip to content

Commit 5a54530

Browse files
committed
Always return unformatted errors and serialize error locations properly
1 parent d28b617 commit 5a54530

File tree

15 files changed

+79
-53
lines changed

15 files changed

+79
-53
lines changed

graphql/core/error.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
from .defer import DeferredException
21
from .language.location import get_location
32

43

@@ -39,10 +38,10 @@ def locations(self):
3938

4039

4140
def format_error(error):
42-
if isinstance(error, DeferredException):
43-
error = error.value
44-
4541
return {
4642
'message': error.message,
47-
'locations': error.locations,
43+
'locations': [
44+
{'line': loc.line, 'column': loc.column}
45+
for loc in error.locations
46+
],
4847
}

graphql/core/execution/base.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# -*- coding: utf-8 -*-
2-
2+
from ..defer import DeferredException
33
from ..error import GraphQLError
44
from ..language import ast
55
from ..type.definition import (
@@ -68,6 +68,11 @@ class ExecutionResult(object):
6868

6969
def __init__(self, data=None, errors=None, invalid=False):
7070
self.data = data
71+
if errors:
72+
errors = [
73+
error.value if isinstance(error, DeferredException) else error
74+
for error in errors
75+
]
7176
self.errors = errors
7277
if invalid:
7378
assert data is None

graphql/core/execution/executor.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import functools
33

44
from ..defer import Deferred, DeferredDict, DeferredList, defer, succeed
5-
from ..error import GraphQLError, format_error
5+
from ..error import GraphQLError
66
from ..language import ast
77
from ..language.parser import parse
88
from ..language.source import Source
@@ -71,7 +71,7 @@ def _execute_graphql_query(self, root, ast, operation_name, args, request_contex
7171
lambda error: ctx.errors.append(error)
7272
) \
7373
.add_callback(
74-
lambda data: ExecutionResult(data, list(map(format_error, ctx.errors))),
74+
lambda data: ExecutionResult(data, ctx.errors),
7575
)
7676

7777
def _execute_operation(self, ctx, root, operation, execute_serially):

graphql/core/language/error.py

Lines changed: 14 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,23 @@
1-
from ..compat import native_str
2-
from ..error import Error
1+
from ..error import GraphQLError
32
from .location import get_location
43

54
__all__ = ['LanguageError']
65

76

8-
class LanguageError(Error):
7+
class LanguageError(GraphQLError):
98
def __init__(self, source, position, description):
10-
self.source = source
11-
self.position = position
12-
self.description = description
13-
14-
def __str__(self):
15-
location = get_location(self.source, self.position)
16-
return native_str(u'Syntax Error {} ({}:{}) {}\n\n{}'.format(
17-
self.source.name,
18-
location.line,
19-
location.column,
20-
self.description,
21-
highlight_source_at_location(self.source, location),
22-
), errors='replace')
9+
location = get_location(source, position)
10+
super(LanguageError, self).__init__(
11+
message=u'Syntax Error {} ({}:{}) {}\n\n{}'.format(
12+
source.name,
13+
location.line,
14+
location.column,
15+
description,
16+
highlight_source_at_location(source, location),
17+
),
18+
source=source,
19+
positions=[position],
20+
)
2321

2422

2523
def highlight_source_at_location(source, location):

tests/core_execution/test_concurrent_executor.py

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from graphql.core.error import format_error
12
from graphql.core.execution import Executor
23
from graphql.core.execution.middlewares.sync import SynchronousExecutionMiddleware
34
from graphql.core.defer import succeed, Deferred, fail
@@ -147,9 +148,10 @@ def notPromise(self):
147148
result = executor.execute(doc, Data(), operation_name='Example')
148149
assert not isinstance(result, Deferred)
149150
assert result.data == {"promise": None, 'notPromise': 'i should work'}
150-
assert result.errors == [{'locations': [SourceLocation(line=3, column=9)],
151-
'message': 'You cannot return a Deferred from a resolver '
152-
'when using SynchronousExecutionMiddleware'}]
151+
formatted_errors = list(map(format_error, result.errors))
152+
assert formatted_errors == [{'locations': [dict(line=3, column=9)],
153+
'message': 'You cannot return a Deferred from a resolver '
154+
'when using SynchronousExecutionMiddleware'}]
153155

154156

155157
def test_synchronous_executor_doesnt_support_defers():
@@ -176,9 +178,10 @@ def notPromise(self):
176178
result = executor.execute(doc, Data(), operation_name='Example')
177179
assert not isinstance(result, Deferred)
178180
assert result.data is None
179-
assert result.errors == [{'locations': [SourceLocation(line=3, column=9)],
180-
'message': 'You cannot return a Deferred from a resolver '
181-
'when using SynchronousExecutionMiddleware'}]
181+
formatted_errors = list(map(format_error, result.errors))
182+
assert formatted_errors == [{'locations': [dict(line=3, column=9)],
183+
'message': 'You cannot return a Deferred from a resolver '
184+
'when using SynchronousExecutionMiddleware'}]
182185

183186

184187
def test_executor_defer_failure():
@@ -206,8 +209,9 @@ def notPromise(self):
206209
assert result.called
207210
result = result.result
208211
assert result.data is None
209-
assert result.errors == [{'locations': [SourceLocation(line=3, column=9)],
210-
'message': "Something bad happened! Sucks :("}]
212+
formatted_errors = list(map(format_error, result.errors))
213+
assert formatted_errors == [{'locations': [dict(line=3, column=9)],
214+
'message': "Something bad happened! Sucks :("}]
211215

212216

213217
def test_synchronous_executor_will_synchronously_resolve():

tests/core_execution/test_executor.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,7 @@ def error(self):
231231
result = execute(GraphQLSchema(Type), Data(), doc_ast)
232232
assert result.data == {'ok': 'ok', 'error': None}
233233
assert len(result.errors) == 1
234-
assert result.errors[0]['message'] == 'Error getting error'
234+
assert result.errors[0].message == 'Error getting error'
235235
# TODO: check error location
236236

237237

tests/core_execution/test_gevent.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# flake8: noqa
2-
2+
from graphql.core.error import format_error
33
from graphql.core.execution import Executor
44
from graphql.core.execution.middlewares.gevent import GeventExecutionMiddleware, run_in_greenlet
55
from graphql.core.language.location import SourceLocation
@@ -57,5 +57,6 @@ def resolver_2(context, *_):
5757

5858
executor = Executor(GraphQLSchema(Type), [GeventExecutionMiddleware()])
5959
result = executor.execute(doc)
60-
assert result.errors == [{'locations': [SourceLocation(1, 20)], 'message': 'resolver_2 failed!'}]
60+
formatted_errors = list(map(format_error, result.errors))
61+
assert formatted_errors == [{'locations': [{'line': 1, 'column': 20}], 'message': 'resolver_2 failed!'}]
6162
assert result.data == {'a': 'hey', 'b': None}

tests/core_execution/test_lists.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ def test_non_nullable_list_of_nullables():
5050
# Returns null
5151
result = run(type, None)
5252
assert len(result.errors) == 1
53-
assert result.errors[0]['message'] == 'Cannot return null for non-nullable field DataType.test.'
53+
assert result.errors[0].message == 'Cannot return null for non-nullable field DataType.test.'
5454
# TODO: check error location
5555
assert result.data == {'nest': None}
5656

@@ -62,7 +62,7 @@ def test_nullable_list_of_non_nullables():
6262
# Contains null
6363
result = run(type, [1, None, 2])
6464
assert len(result.errors) == 1
65-
assert result.errors[0]['message'] == 'Cannot return null for non-nullable field DataType.test.'
65+
assert result.errors[0].message == 'Cannot return null for non-nullable field DataType.test.'
6666
# TODO: check error location
6767
assert result.data == {'nest': {'test': None}}
6868
# Returns null
@@ -76,12 +76,12 @@ def test_non_nullable_list_of_non_nullables():
7676
# Contains null
7777
result = run(type, [1, None, 2])
7878
assert len(result.errors) == 1
79-
assert result.errors[0]['message'] == 'Cannot return null for non-nullable field DataType.test.'
79+
assert result.errors[0].message == 'Cannot return null for non-nullable field DataType.test.'
8080
# TODO: check error location
8181
assert result.data == {'nest': None}
8282
# Returns null
8383
result = run(type, None)
8484
assert len(result.errors) == 1
85-
assert result.errors[0]['message'] == 'Cannot return null for non-nullable field DataType.test.'
85+
assert result.errors[0].message == 'Cannot return null for non-nullable field DataType.test.'
8686
# TODO: check error location
8787
assert result.data == {'nest': None}

tests/core_execution/test_mutations.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -128,5 +128,5 @@ def test_evaluates_mutations_correctly_in_the_presense_of_a_failed_mutation():
128128
}
129129
assert len(result.errors) == 2
130130
# TODO: check error location
131-
assert result.errors[0]['message'] == 'Cannot change the number'
132-
assert result.errors[1]['message'] == 'Cannot change the number'
131+
assert result.errors[0].message == 'Cannot change the number'
132+
assert result.errors[1].message == 'Cannot change the number'

tests/core_execution/test_nonnull.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ def test_nulls_a_nullable_field_that_throws_sync():
5454
result = execute(schema, ThrowingData(), ast, 'Q', {})
5555
assert len(result.errors) == 1
5656
# TODO: check error location
57-
assert result.errors[0]['message'] == str(sync_error)
57+
assert result.errors[0].message == str(sync_error)
5858
assert result.data == {
5959
'sync': None
6060
}
@@ -72,7 +72,7 @@ def test_nulls_a_sync_returned_object_that_contains_a_non_nullable_field_that_th
7272
result = execute(schema, ThrowingData(), ast, 'Q', {})
7373
assert len(result.errors) == 1
7474
# TODO: check error location
75-
assert result.errors[0]['message'] == str(non_null_sync_error)
75+
assert result.errors[0].message == str(non_null_sync_error)
7676
assert result.data == {
7777
'nest': None
7878
}
@@ -111,8 +111,8 @@ def test_nulls_a_complex_tree_of_nullable_fields_that_throw():
111111
result = execute(schema, ThrowingData(), ast, 'Q', {})
112112
assert len(result.errors) == 2
113113
# TODO: check error location
114-
assert result.errors[0]['message'] == str(sync_error)
115-
assert result.errors[1]['message'] == str(sync_error)
114+
assert result.errors[0].message == str(sync_error)
115+
assert result.errors[1].message == str(sync_error)
116116
assert result.data == {
117117
'nest': {
118118
'sync': None,
@@ -149,7 +149,7 @@ def test_nulls_a_sync_returned_object_that_contains_a_non_nullable_field_that_re
149149
result = execute(schema, NullingData(), ast, 'Q', {})
150150
assert len(result.errors) == 1
151151
# TODO: check error location
152-
assert result.errors[0]['message'] == 'Cannot return null for non-nullable field DataType.nonNullSync.'
152+
assert result.errors[0].message == 'Cannot return null for non-nullable field DataType.nonNullSync.'
153153
assert result.data == {
154154
'nest': None
155155
}
@@ -206,7 +206,7 @@ def test_nulls_the_top_level_if_sync_non_nullable_field_throws():
206206
assert result.data is None
207207
assert len(result.errors) == 1
208208
# TODO: check error location
209-
assert result.errors[0]['message'] == str(non_null_sync_error)
209+
assert result.errors[0].message == str(non_null_sync_error)
210210

211211

212212
def test_nulls_the_top_level_if_sync_non_nullable_field_returns_null():
@@ -218,4 +218,4 @@ def test_nulls_the_top_level_if_sync_non_nullable_field_returns_null():
218218
assert result.data is None
219219
assert len(result.errors) == 1
220220
# TODO: check error location
221-
assert result.errors[0]['message'] == 'Cannot return null for non-nullable field DataType.nonNullSync.'
221+
assert result.errors[0].message == 'Cannot return null for non-nullable field DataType.nonNullSync.'

0 commit comments

Comments
 (0)