Skip to content

Allow raising exception to return an error #178

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jul 3, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 25 additions & 19 deletions jsonrpcserver/dispatcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,18 @@
from jsonschema.validators import validator_for # type: ignore
from pkg_resources import resource_string # type: ignore

from .exceptions import JsonRpcError
from .methods import Methods, global_methods, validate_args
from .request import Request, NOID
from .response import (
InvalidRequestResponse,
MethodNotFoundResponse,
ParseErrorResponse,
Response,
ServerErrorResponse,
from_result,
to_serializable,
)
from .result import InvalidParams, InternalError, Error, Result, Success
from .result import InvalidParams, InternalError, MethodNotFound, Error, Result, Success

default_deserializer = json.loads

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


def call(method: Callable, args: list, kwargs: dict) -> Result:
def call(methods: Methods, method_name: str, args: list, kwargs: dict) -> Result:
"""
Calls a method.

Expand All @@ -55,20 +55,28 @@ def call(method: Callable, args: list, kwargs: dict) -> Result:
Returns:
The Result from the method call.
"""
try:
method = methods.items[method_name]
except KeyError:
return MethodNotFound(method_name)

errors = validate_args(method, *args, **kwargs)
if errors:
return InvalidParams(errors)

try:
result = method(*args, **kwargs)
except JsonRpcError as exc:
return Error(code=exc.code, message=exc.message, data=exc.data)
except Exception as exc: # Other error inside method - server error
logging.exception(exc)
return InternalError(str(exc))
else:
return (
InternalError("The method did not return a Result")
if not isinstance(result, (Success, Error))
else result
)
except Exception as exc: # Other error inside method - server error
logging.exception(exc)
return InternalError(str(exc))


def extract_args(request: Request, context: Any) -> list:
Expand All @@ -90,15 +98,13 @@ def dispatch_request(

Converts the return value (a Result) into a Response.
"""
if request.method in methods.items:
result = call(
methods.items[request.method],
extract_args(request, context),
extract_kwargs(request),
)
return None if request.id is NOID else from_result(result, request.id)
else:
return MethodNotFoundResponse(request.method, request.id)
result = call(
methods,
request.method,
extract_args(request, context),
extract_kwargs(request),
)
return None if request.id is NOID else from_result(result, request.id)


def none_if_empty(x: Any) -> Any:
Expand Down Expand Up @@ -169,8 +175,8 @@ def dispatch_to_response_pure(
testing, not dispatch_to_response or dispatch.

Args:
deserializer: Function that is used to deserialize data.
schema_validator:
deserializer: Function that deserializes the JSON-RPC request.
schema_validator: Function that validates the JSON-RPC request.
context: Will be passed to methods as the first param if not None.
methods: Collection of methods that can be called.
request: The incoming request string.
Expand Down Expand Up @@ -223,8 +229,8 @@ def dispatch_to_response(
internal, global methods object which is populated with the @method
decorator.
context: Will be passed to methods as the first param if not None.
schema_validator:
deserializer: Function that is used to deserialize data.
schema_validator: Function that validates the JSON-RPC request.
deserializer: Function that deserializes the JSON-RPC request.

Returns:
A Response, list of Responses or None.
Expand Down
12 changes: 12 additions & 0 deletions jsonrpcserver/exceptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
"""
An exception can be raised from inside a method to return an error response.

This is an alternative to returning a Result from the method.

See https://github.com/bcb/jsonrpcserver/discussions/158
"""


class JsonRpcError(Exception):
def __init__(self, code: int, message: str, data: str):
self.code, self.message, self.data = (code, message, data)
20 changes: 20 additions & 0 deletions tests/test_dispatcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
dispatch_to_response,
dispatch_to_response_pure,
)
from jsonrpcserver.exceptions import JsonRpcError
from jsonrpcserver.methods import Methods, global_methods
from jsonrpcserver.request import Request, NOID
from jsonrpcserver.response import ErrorResponse, SuccessResponse
Expand Down Expand Up @@ -231,6 +232,25 @@ def not_a_result():
assert response.data == "The method did not return a Result"


def test_dispatch_to_response_pure_raising_exception():
"""Allow raising an exception to return an error."""

def raise_exception():
raise JsonRpcError(code=0, message="foo", data="bar")

response = dispatch_to_response_pure(
deserializer=default_deserializer,
schema_validator=default_schema_validator,
context=None,
methods=Methods(raise_exception),
request='{"jsonrpc": "2.0", "method": "raise_exception", "id": 1}',
)
assert isinstance(response, ErrorResponse)
assert response.code == 0
assert response.message == "foo"
assert response.data == "bar"


# dispatch_to_response


Expand Down