change returned error data structure to be JSONRPC standard compliant

This commit is contained in:
Lex Berezhny 2019-12-20 16:14:26 -05:00
parent 9f0fba063d
commit 57ee16d565

View file

@ -212,44 +212,46 @@ class JSONRPCError:
CODE_AUTHENTICATION_ERROR: 401, CODE_AUTHENTICATION_ERROR: 401,
} }
def __init__(self, message, code=CODE_APPLICATION_ERROR, traceback=None, data=None, name=None): def __init__(self, code: int, message: str, data: dict=None):
assert isinstance(code, int), "'code' must be an int" assert code and isinstance(code, int), "'code' must be an int"
assert (data is None or isinstance(data, dict)), "'data' must be None or a dict" assert message and isinstance(message, str), "'message' must be a string"
assert data is None or isinstance(data, dict), "'data' must be None or a dict"
self.code = code self.code = code
self.name = name or 'Exception'
if message is None:
message = self.MESSAGES[code] if code in self.MESSAGES else "API Error"
self.message = message self.message = message
self.data = {} if data is None else data self.data = data or {}
self.traceback = []
if traceback is not None:
self.traceback = trace_lines = traceback.split("\n")
for i, t in enumerate(trace_lines):
if "--- <exception caught here> ---" in t:
if len(trace_lines) > i + 1:
self.traceback = [j for j in trace_lines[i + 1:] if j]
break
def to_dict(self): def to_dict(self):
return { return {
'code': self.code, 'code': self.code,
'name': self.name,
'message': self.message, 'message': self.message,
'data': self.data, 'data': self.data,
'traceback': self.traceback
} }
@classmethod @staticmethod
def create_command_exception(cls, e, command, args, kwargs, traceback): def filter_traceback(traceback):
if 'password' in kwargs and isinstance(kwargs['password'], str): result = []
kwargs['password'] = '*'*len(kwargs['password']) if traceback is not None:
return cls(str(e), traceback=traceback, name=e.__class__.__name__, data={ result = trace_lines = traceback.split("\n")
'command': command, 'args': args, 'kwargs': kwargs, for i, t in enumerate(trace_lines):
}) if "--- <exception caught here> ---" in t:
if len(trace_lines) > i + 1:
result = [j for j in trace_lines[i + 1:] if j]
break
return result
@classmethod @classmethod
def create_rpc_exception(cls, e, code): def create_command_exception(cls, command, args, kwargs, exception, traceback):
return cls(str(e), name=e.__class__.__name__, code=code) if 'password' in kwargs and isinstance(kwargs['password'], str):
kwargs['password'] = '*'*len(kwargs['password'])
return cls(
cls.CODE_APPLICATION_ERROR, str(exception), {
'name': exception.__class__.__name__,
'traceback': cls.filter_traceback(traceback),
'command': command,
'args': args,
'kwargs': kwargs,
}
)
class UnknownAPIMethodError(Exception): class UnknownAPIMethodError(Exception):
@ -514,8 +516,9 @@ class Daemon(metaclass=JSONRPCServerType):
except: except:
log.exception('Failed to encode JSON RPC result:') log.exception('Failed to encode JSON RPC result:')
encoded_result = jsonrpc_dumps_pretty(JSONRPCError( encoded_result = jsonrpc_dumps_pretty(JSONRPCError(
JSONRPCError.CODE_APPLICATION_ERROR,
'After successfully executing the command, failed to encode result for JSON RPC response.', 'After successfully executing the command, failed to encode result for JSON RPC response.',
JSONRPCError.CODE_APPLICATION_ERROR, format_exc() {'traceback': format_exc()}
), ledger=ledger) ), ledger=ledger)
return web.Response( return web.Response(
text=encoded_result, text=encoded_result,
@ -569,17 +572,17 @@ class Daemon(metaclass=JSONRPCServerType):
try: try:
function_name = data['method'] function_name = data['method']
except KeyError: except KeyError:
return JSONRPCError.create_rpc_exception( return JSONRPCError(
CommandError("Missing 'method' value in request."), JSONRPCError.CODE_METHOD_NOT_FOUND,
JSONRPCError.CODE_METHOD_NOT_FOUND "Missing 'method' value in request."
) )
try: try:
fn = self._get_jsonrpc_method(function_name) fn = self._get_jsonrpc_method(function_name)
except UnknownAPIMethodError: except UnknownAPIMethodError:
return JSONRPCError.create_rpc_exception( return JSONRPCError(
CommandDoesNotExistError(function_name), JSONRPCError.CODE_METHOD_NOT_FOUND,
JSONRPCError.CODE_METHOD_NOT_FOUND str(CommandDoesNotExistError(function_name))
) )
if args in ([{}], []): if args in ([{}], []):
@ -594,9 +597,9 @@ class Daemon(metaclass=JSONRPCServerType):
isinstance(args[0], list) and isinstance(args[1], dict): isinstance(args[0], list) and isinstance(args[1], dict):
_args, _kwargs = args _args, _kwargs = args
else: else:
return JSONRPCError.create_rpc_exception( return JSONRPCError(
CommandError(f"Invalid parameters format: {args}"), JSONRPCError.CODE_INVALID_PARAMS,
JSONRPCError.CODE_INVALID_PARAMS f"Invalid parameters format: {args}"
) )
if is_transactional_function(function_name): if is_transactional_function(function_name):
@ -608,9 +611,9 @@ class Daemon(metaclass=JSONRPCServerType):
params_error, function_name, ', '.join(erroneous_params) params_error, function_name, ', '.join(erroneous_params)
) )
log.warning(params_error_message) log.warning(params_error_message)
return JSONRPCError.create_rpc_exception( return JSONRPCError(
CommandError(params_error_message), JSONRPCError.CODE_INVALID_PARAMS,
JSONRPCError.CODE_INVALID_PARAMS params_error_message,
) )
try: try:
@ -624,7 +627,7 @@ class Daemon(metaclass=JSONRPCServerType):
except Exception as e: # pylint: disable=broad-except except Exception as e: # pylint: disable=broad-except
log.exception("error handling api request") log.exception("error handling api request")
return JSONRPCError.create_command_exception( return JSONRPCError.create_command_exception(
e, function_name, _args, _kwargs, format_exc() command=function_name, args=_args, kwargs=_kwargs, exception=e, traceback=format_exc()
) )
def _verify_method_is_callable(self, function_path): def _verify_method_is_callable(self, function_path):