Skip to content

Commit

Permalink
pyln: add RPCException for finer method failure control.
Browse files Browse the repository at this point in the history
Allows caller to set code and exact message to be returned.

Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
Changelog-Added: pyln-client: plugins can now return RPCException for finer control over error returns.
  • Loading branch information
rustyrussell committed Dec 15, 2020
1 parent d971e3d commit 63123cc
Show file tree
Hide file tree
Showing 3 changed files with 54 additions and 6 deletions.
3 changes: 2 additions & 1 deletion contrib/pyln-client/pyln/client/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from .lightning import LightningRpc, RpcError, Millisatoshi
from .plugin import Plugin, monkey_patch
from .plugin import Plugin, monkey_patch, RPCException


__version__ = "0.8.0"
Expand All @@ -9,6 +9,7 @@
"LightningRpc",
"Plugin",
"RpcError",
"RPCException",
"Millisatoshi",
"__version__",
"monkey_patch"
Expand Down
22 changes: 18 additions & 4 deletions contrib/pyln-client/pyln/client/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,14 @@ def __init__(self, name: str, func: Callable[..., JSONType],
self.after: List[str] = []


class RPCException(Exception):
# -32600 == "Invalid Request"
def __init__(self, message: str, code: int = -32600):
self.code = code
self.message = message
super().__init__("RPCException: {}".format(message))


class Request(dict):
"""A request object that wraps params and allows async return
"""
Expand Down Expand Up @@ -102,21 +110,27 @@ def set_result(self, result: Any) -> None:
self.state = RequestState.FINISHED
self.termination_tb = "".join(traceback.extract_stack().format()[:-1])

def set_exception(self, exc: Exception) -> None:
def set_exception(self, exc: Union[Exception, RPCException]) -> None:
if self.state != RequestState.PENDING:
assert(self.termination_tb is not None)
raise ValueError(
"Cannot set the exception of a request that is not pending, "
"current state is {state}. Request previously terminated at\n"
"{tb}".format(state=self.state, tb=self.termination_tb))
self.exc = exc
if isinstance(exc, RPCException):
code = exc.code
message = exc.message
else:
code = -32600 # "Invalid Request"
message = ("Error while processing {method}: {exc}"
.format(method=self.method, exc=str(exc)))
self._write_result({
'jsonrpc': '2.0',
'id': self.id,
"error": {
"code": -32600, # "Invalid Request"
"message": "Error while processing {method}: {exc}"
.format(method=self.method, exc=str(exc)),
"code": code,
"message": message,
# 'data' field "may be omitted."
"traceback": traceback.format_exc(),
},
Expand Down
35 changes: 34 additions & 1 deletion contrib/pyln-client/tests/test_plugin.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from pyln.client import Plugin
from pyln.client.plugin import Request, Millisatoshi
from pyln.client.plugin import Request, Millisatoshi, RPCException
import itertools
import pytest # type: ignore

Expand Down Expand Up @@ -172,6 +172,39 @@ def test1(name):
assert call_list == []


def test_method_exceptions():
"""A bunch of tests that should fail calling the methods."""
p = Plugin(autopatch=False)

def fake_write_result(resultdict):
global result_dict
result_dict = resultdict

@p.method("test_raise")
def test_raise():
raise RPCException("testing RPCException", code=-1000)

req = Request(p, 1, "test_raise", {})
req._write_result = fake_write_result
p._dispatch_request(req)
assert result_dict['jsonrpc'] == '2.0'
assert result_dict['id'] == 1
assert result_dict['error']['code'] == -1000
assert result_dict['error']['message'] == "testing RPCException"

@p.method("test_raise2")
def test_raise2():
raise Exception("normal exception")

req = Request(p, 1, "test_raise2", {})
req._write_result = fake_write_result
p._dispatch_request(req)
assert result_dict['jsonrpc'] == '2.0'
assert result_dict['id'] == 1
assert result_dict['error']['code'] == -32600
assert result_dict['error']['message'] == "Error while processing test_raise2: normal exception"


def test_positional_inject():
p = Plugin()
rdict = Request(
Expand Down

0 comments on commit 63123cc

Please sign in to comment.