commit ae57339500442ef58e300360b7a479ac7e2aa88d
parent 02f99af868d96f5f76edd952bfeb588cee0c8fb2
Author: nolash <dev@holbrook.no>
Date: Fri, 9 Apr 2021 12:33:21 +0200
Add parser
Diffstat:
5 files changed, 221 insertions(+), 0 deletions(-)
diff --git a/.gitignore b/.gitignore
@@ -1,2 +1,5 @@
__pycache__
*.pyc
+dist/
+*.egg-info
+gmon.out
diff --git a/jsonrpc_std/base.py b/jsonrpc_std/base.py
@@ -0,0 +1,4 @@
+class JSONRPCBase:
+ major_version = 2
+ minor_version = 0
+ version_string = '2.0'
diff --git a/jsonrpc_std/error.py b/jsonrpc_std/error.py
@@ -0,0 +1,86 @@
+# local imports
+from .base import JSONRPCBase
+
+class JSONRPCException(Exception, JSONRPCBase):
+ message = 'Unknown'
+
+ def __init__(self, v):
+ context_v = '{} error'.format(self.message)
+ if v != None:
+ context_v += ': ' + v
+
+ super(JSONRPCException, self).__init__(context_v)
+
+
+class JSONRPCCustomException(JSONRPCException):
+ code = -32000
+ message = 'Server'
+
+
+class JSONRPCParseError(JSONRPCException):
+ code = -32700
+ message = 'Parse'
+
+
+class JSONRPCInvalidRequestError(JSONRPCException):
+ code = -32600
+ message = 'Invalid request'
+
+
+class JSONRPCMethodNotFoundError(JSONRPCException):
+ code = -32601
+ message = 'Method not found'
+
+
+class JSONRPCInvalidParametersError(JSONRPCException):
+ code = -32602
+ message = 'Invalid parameters'
+
+
+class JSONRPCInternalError(JSONRPCException):
+ code = -32603
+ message = 'Internal'
+
+
+class JSONRPCUnhandledErrorException(KeyError):
+ pass
+
+
+class JSONRPCErrors:
+ reserved_max = -31999
+ reserved_min = -32768
+ local_max = -32000
+ local_min = -32099
+
+ translations = {
+ -32700: JSONRPCParseError,
+ -32600: JSONRPCInvalidRequestError,
+ -32601: JSONRPCMethodNotFoundError,
+ -32602: JSONRPCInvalidParametersError,
+ -32603: JSONRPCInternalError,
+ }
+
+ @classmethod
+ def add(self, code, exception_object):
+ if code < self.local_min or code > self.local_max:
+ raise ValueError('code must be in range <{},{}>'.format(self.local_min, self.local_max))
+ exc = self.translations.get(code)
+ if exc != None:
+ raise ValueError('code already registered with {}'.format(exc))
+
+ if not issubclass(exception_object, JSONRPCCustomException):
+ raise ValueError('exception object must be a subclass of jsonrpc_base.error.JSONRPCCustomException')
+
+ self.translations[code] = exception_object
+
+
+ @classmethod
+ def get(self, code, v=None):
+ e = self.translations.get(code)
+ if e == None:
+ raise JSONRPCUnhandledErrorException(code)
+ return e(v)
+
+
+class InvalidJSONRPCError(ValueError):
+ pass
diff --git a/jsonrpc_std/interface.py b/jsonrpc_std/interface.py
@@ -0,0 +1,74 @@
+# standard imports
+import uuid
+
+# local imports
+from .base import JSONRPCBase
+from .error import (
+ JSONRPCErrors,
+ )
+
+
+class DefaultErrorParser:
+
+ def translate(self, error):
+ code = error['error']['code']
+ message = error['error']['message']
+ if type(code).__name__ != 'int':
+ raise ValueError('error code is not int by {} in error {}'.format(type(code), error))
+
+ exc = None
+ try:
+ exc = JSONRPCErrors.get(code, message)
+ except KeyError:
+ return JSONRPCUndefinedError(code, message)
+
+
+def jsonrpc_template(request_id=None):
+ if request_id == None:
+ request_id = str(uuid.uuid4())
+
+ return {
+ 'jsonrpc': JSONRPCBase.version_string,
+ 'id': request_id,
+ 'method': None,
+ 'params': [],
+ }
+
+
+def jsonrpc_request(method, request_id=None):
+ req = jsonrpc_template(request_id=request_id)
+ req['method'] = method
+ return req
+
+
+def jsonrpc_result(o, ep):
+ if o.get('error') != None:
+ raise ep.translate(o)
+ return o['result']
+
+
+def jsonrpc_response(request_id, result):
+ return {
+ 'jsonrpc': JSONRPCBase.version_string,
+ 'id': request_id,
+ 'result': result,
+ }
+
+
+def jsonrpc_error(request_id, code, message=None):
+ e = JSONRPCErrors.get(code, message)
+ return {
+ 'jsonrpc': JSONRPCBase.version_string,
+ 'id': request_id,
+ 'error': {
+ 'code': code,
+ 'message': str(e),
+ },
+ }
+
+
+def jsonrpc_is_response_to(request, response):
+ return request['id'] == response['id']
+
+
+
diff --git a/jsonrpc_std/parse.py b/jsonrpc_std/parse.py
@@ -0,0 +1,54 @@
+# standard imports
+import json
+
+# local imports
+from .base import JSONRPCBase
+from .error import (
+ JSONRPCParseError,
+ JSONRPCInvalidRequestError,
+ )
+from .interface import (
+ jsonrpc_request,
+ )
+
+
+def jsonrpc_validate_dict(o):
+ version = o.get('jsonrpc')
+ if version == None:
+ raise JSONRPCParseError('missing jsonrpc version field')
+ elif version != JSONRPCBase.version_string:
+ raise JSONRPCInvalidRequestError('Invalid version {}'.format(version))
+
+ method = o.get('method')
+ if method == None:
+ raise JSONRPCParseError('missing method field')
+
+ params = o.get('params')
+ if params == None:
+ raise JSONRPCParseError('missing params field')
+ elif type(params).__name__ != 'list':
+ raise JSONRPCParseError('params field must be array')
+
+ request_id = o.get('id')
+ if request_id == None:
+ raise JSONRPCParseError('missing id field')
+ if type(request_id).__name__ not in ['str', 'int']:
+ raise JSONRPCInvalidRequestError('invalid id value, must be string or integer')
+
+ return o
+
+
+def jsonrpc_from_str(s):
+ o = json.loads(s)
+ return jsonrpc_from_dict(o)
+
+
+def jsonrpc_from_dict(o):
+ o = jsonrpc_validate_dict(o)
+ req = jsonrpc_request(o['method'], request_id=o['id'])
+ req['params'] = o['params']
+ return req
+
+def jsonrpc_from_file(f):
+ o = json.load(f)
+ return jsonrpc_from_dict(o)