Skip to content

Commit ca223bd

Browse files
authored
Allow raising exception to return an error (#178)
* Allow raising exception to return an error * Add exceptions.py
1 parent f082955 commit ca223bd

File tree

3 files changed

+57
-19
lines changed

3 files changed

+57
-19
lines changed

jsonrpcserver/dispatcher.py

Lines changed: 25 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -19,18 +19,18 @@
1919
from jsonschema.validators import validator_for # type: ignore
2020
from pkg_resources import resource_string # type: ignore
2121

22+
from .exceptions import JsonRpcError
2223
from .methods import Methods, global_methods, validate_args
2324
from .request import Request, NOID
2425
from .response import (
2526
InvalidRequestResponse,
26-
MethodNotFoundResponse,
2727
ParseErrorResponse,
2828
Response,
2929
ServerErrorResponse,
3030
from_result,
3131
to_serializable,
3232
)
33-
from .result import InvalidParams, InternalError, Error, Result, Success
33+
from .result import InvalidParams, InternalError, MethodNotFound, Error, Result, Success
3434

3535
default_deserializer = json.loads
3636

@@ -46,7 +46,7 @@
4646
config.read([".jsonrpcserverrc", os.path.expanduser("~/.jsonrpcserverrc")])
4747

4848

49-
def call(method: Callable, args: list, kwargs: dict) -> Result:
49+
def call(methods: Methods, method_name: str, args: list, kwargs: dict) -> Result:
5050
"""
5151
Calls a method.
5252
@@ -55,20 +55,28 @@ def call(method: Callable, args: list, kwargs: dict) -> Result:
5555
Returns:
5656
The Result from the method call.
5757
"""
58+
try:
59+
method = methods.items[method_name]
60+
except KeyError:
61+
return MethodNotFound(method_name)
62+
5863
errors = validate_args(method, *args, **kwargs)
5964
if errors:
6065
return InvalidParams(errors)
6166

6267
try:
6368
result = method(*args, **kwargs)
69+
except JsonRpcError as exc:
70+
return Error(code=exc.code, message=exc.message, data=exc.data)
71+
except Exception as exc: # Other error inside method - server error
72+
logging.exception(exc)
73+
return InternalError(str(exc))
74+
else:
6475
return (
6576
InternalError("The method did not return a Result")
6677
if not isinstance(result, (Success, Error))
6778
else result
6879
)
69-
except Exception as exc: # Other error inside method - server error
70-
logging.exception(exc)
71-
return InternalError(str(exc))
7280

7381

7482
def extract_args(request: Request, context: Any) -> list:
@@ -90,15 +98,13 @@ def dispatch_request(
9098
9199
Converts the return value (a Result) into a Response.
92100
"""
93-
if request.method in methods.items:
94-
result = call(
95-
methods.items[request.method],
96-
extract_args(request, context),
97-
extract_kwargs(request),
98-
)
99-
return None if request.id is NOID else from_result(result, request.id)
100-
else:
101-
return MethodNotFoundResponse(request.method, request.id)
101+
result = call(
102+
methods,
103+
request.method,
104+
extract_args(request, context),
105+
extract_kwargs(request),
106+
)
107+
return None if request.id is NOID else from_result(result, request.id)
102108

103109

104110
def none_if_empty(x: Any) -> Any:
@@ -169,8 +175,8 @@ def dispatch_to_response_pure(
169175
testing, not dispatch_to_response or dispatch.
170176
171177
Args:
172-
deserializer: Function that is used to deserialize data.
173-
schema_validator:
178+
deserializer: Function that deserializes the JSON-RPC request.
179+
schema_validator: Function that validates the JSON-RPC request.
174180
context: Will be passed to methods as the first param if not None.
175181
methods: Collection of methods that can be called.
176182
request: The incoming request string.
@@ -223,8 +229,8 @@ def dispatch_to_response(
223229
internal, global methods object which is populated with the @method
224230
decorator.
225231
context: Will be passed to methods as the first param if not None.
226-
schema_validator:
227-
deserializer: Function that is used to deserialize data.
232+
schema_validator: Function that validates the JSON-RPC request.
233+
deserializer: Function that deserializes the JSON-RPC request.
228234
229235
Returns:
230236
A Response, list of Responses or None.

jsonrpcserver/exceptions.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
"""
2+
An exception can be raised from inside a method to return an error response.
3+
4+
This is an alternative to returning a Result from the method.
5+
6+
See https://github.com/bcb/jsonrpcserver/discussions/158
7+
"""
8+
9+
10+
class JsonRpcError(Exception):
11+
def __init__(self, code: int, message: str, data: str):
12+
self.code, self.message, self.data = (code, message, data)

tests/test_dispatcher.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
dispatch_to_response,
1212
dispatch_to_response_pure,
1313
)
14+
from jsonrpcserver.exceptions import JsonRpcError
1415
from jsonrpcserver.methods import Methods, global_methods
1516
from jsonrpcserver.request import Request, NOID
1617
from jsonrpcserver.response import ErrorResponse, SuccessResponse
@@ -231,6 +232,25 @@ def not_a_result():
231232
assert response.data == "The method did not return a Result"
232233

233234

235+
def test_dispatch_to_response_pure_raising_exception():
236+
"""Allow raising an exception to return an error."""
237+
238+
def raise_exception():
239+
raise JsonRpcError(code=0, message="foo", data="bar")
240+
241+
response = dispatch_to_response_pure(
242+
deserializer=default_deserializer,
243+
schema_validator=default_schema_validator,
244+
context=None,
245+
methods=Methods(raise_exception),
246+
request='{"jsonrpc": "2.0", "method": "raise_exception", "id": 1}',
247+
)
248+
assert isinstance(response, ErrorResponse)
249+
assert response.code == 0
250+
assert response.message == "foo"
251+
assert response.data == "bar"
252+
253+
234254
# dispatch_to_response
235255

236256

0 commit comments

Comments
 (0)