JsonRpcResponse._add_error()   A
last analyzed

Complexity

Conditions 4

Size

Total Lines 19
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 15
dl 0
loc 19
rs 9.65
c 0
b 0
f 0
cc 4
nop 4
1
import typing
2
from dataclasses import dataclass, field
3
4
from .. import constants, errors, typedefs, utils
5
6
7
__all__ = (
8
    'JsonRpcResponse',
9
    'JsonRpcBatchResponse',
10
    'JsonRpcUnlinkedResults',
11
    'JsonRpcDuplicatedResults',
12
)
13
14
15
@dataclass
16
class JsonRpcResponse:
17
    id: typing.Optional[typedefs.JsonRpcIdType] = None
18
    jsonrpc: str = constants.VERSION_2_0
19
    result: typing.Any = None
20
    error: typing.Optional[errors.JsonRpcError] = None
21
    context: typing.MutableMapping = field(default_factory=dict)
22
23
    @property
24
    def is_notification(self) -> bool:
25
        return self.id is None
26
27
    @classmethod
28
    def load(cls,
29
             data: typing.Any, *,
30
             error_map: typing.Optional[typing.Mapping] = None, **kwargs) -> 'JsonRpcResponse':
31
        cls._validate_json_response(data)
32
33
        response = cls(
34
            id=data.get('id'),
35
            jsonrpc=data.get('jsonrpc', constants.VERSION_2_0),
36
            result=data.get('result'),
37
            **kwargs,
38
        )
39
40
        if 'error' in data:
41
            cls._add_error(response, data['error'], error_map=error_map)
42
43
        return response
44
45
    def dump(self) -> typing.Mapping[str, typing.Any]:
46
        data: typing.Dict[str, typing.Any] = {
47
            'id': self.id,
48
            'jsonrpc': self.jsonrpc,
49
        }
50
51
        if self.error is None:
52
            data['result'] = self.result
53
        else:
54
            data['error'] = {'code': self.error.code, 'message': self.error.message}
55
56
            if self.error.data is not None:
57
                data['error']['data'] = self.error.data
58
59
        return data
60
61
    @staticmethod
62
    def _validate_json_response(data: typing.Any) -> None:
63
        if not isinstance(data, typing.Mapping):
64
            raise errors.InvalidRequest
65
66
        utils.validate_jsonrpc(data.get('jsonrpc'))
67
68
        if 'result' not in data and 'error' not in data:
69
            raise errors.InvalidRequest('"result" or "error" not found in data.', data={'raw_response': data})
70
71
    @staticmethod
72
    def _add_error(response: 'JsonRpcResponse',
73
                   error: typing.Any, *,
74
                   error_map: typing.Optional[typing.Mapping] = None) -> None:
75
        if not isinstance(error, typing.Mapping):
76
            raise errors.InvalidRequest
77
78
        if not {'code', 'message'} <= error.keys():
79
            raise errors.InvalidRequest
80
81
        if error_map:
82
            exception_class = error_map.get(error['code'], errors.JsonRpcError)
83
        else:
84
            exception_class = errors.JsonRpcError
85
86
        response.error = exception_class(
87
            message=error['message'],
88
            data=error.get('data'),
89
            code=error['code'],
90
        )
91
92
93
@dataclass
94
class JsonRpcBatchResponse:
95
    responses: typing.Tuple[JsonRpcResponse, ...] = field(default_factory=tuple)
96
97
    @classmethod
98
    def load(cls,
99
             data: typing.Any, *,
100
             error_map: typing.Optional[typing.Mapping] = None,
101
             **kwargs) -> 'JsonRpcBatchResponse':
102
        if not isinstance(data, typing.Sequence):
103
            raise errors.InvalidRequest('A batch request must be of the list type.')
104
105
        return cls(responses=tuple(
106
            JsonRpcResponse.load(item, error_map=error_map, **kwargs)
107
            for item in data
108
        ))
109
110
    def dump(self) -> typing.Tuple[typing.Mapping[str, typing.Any], ...]:
111
        return tuple(response.dump() for response in self.responses)
112
113
114
@dataclass
115
class JsonRpcUnlinkedResults:
116
    results: typing.MutableSequence = field(default_factory=list)
117
118
    def __bool__(self) -> bool:
119
        return len(self.results) > 0
120
121
    def add(self, value: typing.Any) -> None:
122
        self.results.append(value)
123
124
125
@dataclass
126
class JsonRpcDuplicatedResults:
127
    results: typing.MutableSequence = field(default_factory=list)
128
129
    def __bool__(self) -> bool:
130
        return len(self.results) > 0
131
132
    def add(self, value: typing.Any) -> None:
133
        self.results.append(value)
134