-
Notifications
You must be signed in to change notification settings - Fork 34
Expand file tree
/
Copy pathresponse_handler.py
More file actions
159 lines (140 loc) · 5.74 KB
/
response_handler.py
File metadata and controls
159 lines (140 loc) · 5.74 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
"""Dispatch logic for mapping Overkiz API error responses to specific errors."""
from __future__ import annotations
from http import HTTPStatus
from json import JSONDecodeError
from typing import TYPE_CHECKING
from pyoverkiz.exceptions import (
AccessDeniedToGatewayError,
ActionGroupSetupNotFoundError,
ApplicationNotAllowedError,
BadCredentialsError,
BaseOverkizError,
DuplicateActionOnDeviceError,
ExecutionQueueFullError,
InvalidCommandError,
InvalidEventListenerIdError,
InvalidTokenError,
MaintenanceError,
MissingAPIKeyError,
MissingAuthorizationTokenError,
NoRegisteredEventListenerError,
NoSuchResourceError,
NoSuchTokenError,
NotAuthenticatedError,
OverkizError,
ResourceAccessDeniedError,
ServiceUnavailableError,
SessionAndBearerInSameRequestError,
TooManyAttemptsBannedError,
TooManyConcurrentRequestsError,
TooManyExecutionsError,
TooManyRequestsError,
UnknownObjectError,
UnknownUserError,
)
if TYPE_CHECKING:
from aiohttp import ClientResponse
# Primary dispatch: (errorCode, message_substring) -> error class.
# Checked in order; first match wins. Use errorCode as the primary key to
# reduce brittleness across cloud vs. local API variants.
_ERROR_CODE_MESSAGE_MAP: list[tuple[str, str | None, type[BaseOverkizError]]] = [
# --- errorCode is the sole discriminator ---
("DUPLICATE_FIELD_OR_VALUE", None, DuplicateActionOnDeviceError),
("INVALID_FIELD_VALUE", None, ActionGroupSetupNotFoundError),
("INVALID_API_CALL", None, NoSuchResourceError),
("EXEC_QUEUE_FULL", None, ExecutionQueueFullError),
# --- errorCode + message substring ---
("AUTHENTICATION_ERROR", "Too many requests", TooManyRequestsError),
("AUTHENTICATION_ERROR", "Bad credentials", BadCredentialsError),
("AUTHENTICATION_ERROR", "API key is required", MissingAPIKeyError),
("AUTHENTICATION_ERROR", "No such user account", UnknownUserError),
("RESOURCE_ACCESS_DENIED", "Not authenticated", NotAuthenticatedError),
(
"RESOURCE_ACCESS_DENIED",
"Missing authorization token",
MissingAuthorizationTokenError,
),
(
"RESOURCE_ACCESS_DENIED",
"too many concurrent requests",
TooManyConcurrentRequestsError,
),
("RESOURCE_ACCESS_DENIED", "Too many executions", TooManyExecutionsError),
(
"RESOURCE_ACCESS_DENIED",
"Access denied to gateway",
AccessDeniedToGatewayError,
),
(
"RESOURCE_ACCESS_DENIED",
"cannot be accessed through this application",
ApplicationNotAllowedError,
),
("UNSUPPORTED_OPERATION", "No such command", InvalidCommandError),
(
"UNSPECIFIED_ERROR",
"Invalid event listener id",
InvalidEventListenerIdError,
),
(
"UNSPECIFIED_ERROR",
"No registered event listener",
NoRegisteredEventListenerError,
),
("UNSPECIFIED_ERROR", "Unknown object", UnknownObjectError),
]
# Message-only fallback patterns for responses where the errorCode alone is
# not enough or may vary across API versions.
_MESSAGE_FALLBACK_MAP: list[tuple[str, type[BaseOverkizError]]] = [
("Too many executions", TooManyExecutionsError),
(
"Cannot use JSESSIONID and bearer token",
SessionAndBearerInSameRequestError,
),
("Too many attempts with an invalid token", TooManyAttemptsBannedError),
("Invalid token", InvalidTokenError),
("Not such token with UUID", NoSuchTokenError),
("Unknown user", UnknownUserError),
("No such command", InvalidCommandError),
("No registered event listener", NoRegisteredEventListenerError),
("Unknown object", UnknownObjectError),
("Access denied to gateway", AccessDeniedToGatewayError),
]
# Fallback when errorCode is recognized but no message pattern matched.
_ERROR_CODE_FALLBACK_MAP: dict[str, type[BaseOverkizError]] = {
"AUTHENTICATION_ERROR": BadCredentialsError,
"RESOURCE_ACCESS_DENIED": ResourceAccessDeniedError,
}
async def check_response(response: ClientResponse) -> None:
"""Check the response returned by the OverKiz API."""
if response.status in (HTTPStatus.OK, HTTPStatus.NO_CONTENT):
return
try:
result = await response.json(content_type=None)
except JSONDecodeError as error:
result = await response.text()
if "is down for maintenance" in result:
raise MaintenanceError("Server is down for maintenance") from error
if response.status == HTTPStatus.SERVICE_UNAVAILABLE:
raise ServiceUnavailableError(result) from error
raise OverkizError(
f"Unknown error while requesting {response.url}. {response.status} - {result}"
) from error
error_code = result.get("errorCode", "")
if error_code:
# Error messages between cloud and local servers differ slightly in quoting and punctuation.
# Normalise so substring matching works across both variants.
message = message.strip('".') if (message := result.get("error")) else ""
# 1. Primary dispatch: match on errorCode (+ optional message substring)
for code, pattern, error_class in _ERROR_CODE_MESSAGE_MAP:
if error_code == code and (pattern is None or pattern in message):
raise error_class(message)
# 2. Message-only fallback for patterns that may appear under varying errorCodes
for pattern, error_class in _MESSAGE_FALLBACK_MAP:
if pattern in message:
raise error_class(message)
# 3. errorCode category fallback (known code, unknown message)
if error_code in _ERROR_CODE_FALLBACK_MAP:
raise _ERROR_CODE_FALLBACK_MAP[error_code](message or str(result))
# Undefined Overkiz error
raise OverkizError(result)