Test Failed
Pull Request — master (#5)
by Michael
15:16
created

JsonRpcBatchResponse.to_list()   A

Complexity

Conditions 1

Size

Total Lines 2
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 2
dl 0
loc 2
rs 10
c 0
b 0
f 0
cc 1
nop 1
1
import typing
2
from dataclasses import dataclass, field
3
4
from .. import constants, errors, utils
5
6
7
__all__ = (
8
    'JsonRpcResponse',
9
    'JsonRpcBatchResponse',
10
)
11
12
13
@dataclass
14
class JsonRpcResponse:
15
    jsonrpc: str = constants.VERSION_2_0
16
    msg_id: typing.Any = constants.NOTHING
17
    result: typing.Any = constants.NOTHING
18
    error: typing.Optional[errors.JsonRpcError] = None
19
    context: dict = field(default_factory=dict)
20
21
    @property
22
    def is_notification(self) -> bool:
23
        return self.msg_id in constants.EMPTY_VALUES
24
25
    @classmethod
26
    def from_dict(cls, data: dict, *, error_map: typing.Optional[dict] = None, **kwargs) -> 'JsonRpcResponse':
27
        cls._validate_json_response(data)
28
29
        response = cls(
30
            msg_id=data.get('id', constants.NOTHING),
31
            jsonrpc=data.get('jsonrpc', constants.VERSION_2_0),
32
            result=data.get('result'),
33
            **kwargs,
34
        )
35
36
        if 'error' in data:
37
            cls._add_error(response, data['error'], error_map=error_map)
38
39
        return response
40
41
    def to_dict(self) -> typing.Optional[dict]:
42
        data = {'jsonrpc': self.jsonrpc}
43
44
        if self.msg_id in constants.EMPTY_VALUES:
45
            data['id'] = None
46
        else:
47
            data['id'] = self.msg_id
48
49
        if self.error is constants.NOTHING:
50
            data['result'] = self.result
51
        else:
52
            data['error'] = {'code': self.error.code, 'message': self.error.message}
53
54
            if self.error.data is not None:
55
                data['error']['data'] = self.error.data
56
57
        return data
58
59
    @staticmethod
60
    def _validate_json_response(data: typing.Any) -> None:
61
        if not isinstance(data, dict):
62
            raise errors.InvalidRequest
63
64
        utils.validate_jsonrpc(data.get('jsonrpc'))
65
66
        if 'result' not in data and 'error' not in data:
67
            raise errors.InvalidRequest('"result" or "error" not found in data.', data={'raw_response': data})
68
69
    @staticmethod
70
    def _add_error(response: 'JsonRpcResponse', error: typing.Any, *, error_map: typing.Optional[dict] = None) -> None:
71
        if not isinstance(error, dict):
72
            raise errors.InvalidRequest
73
74
        if not ({'code', 'message'}) <= error.keys():
75
            raise errors.InvalidRequest
76
77
        if error_map:
78
            exception_class = error_map.get(error['code'], errors.JsonRpcError)
79
        else:
80
            exception_class = errors.JsonRpcError
81
82
        response.error = exception_class(
83
            message=error['message'],
84
            data=error.get('data'),
85
            code=error['code'],
86
        )
87
88
89
@dataclass
90
class JsonRpcBatchResponse:
91
    responses: typing.List[JsonRpcResponse] = field(default_factory=list)
92
93
    def to_list(self) -> typing.List[dict]:
94
        return [response.to_dict() for response in self.responses]
95
96
    @classmethod
97
    def from_list(cls, data: list, *, error_map: typing.Optional[dict] = None, **kwargs) -> 'JsonRpcBatchResponse':
98
        responses = [
99
            JsonRpcResponse.from_dict(item, error_map=error_map, **kwargs)
100
            for item in data
101
        ]
102
103
        return cls(responses=responses)
104