Skip to content

Commit e5afd0b

Browse files
authored
Merge pull request #85 from alexaustin007/main
fix: handle JSONDecodeError in _handle_response to prevent crashes
2 parents 343a06e + f0ddb85 commit e5afd0b

File tree

2 files changed

+53
-39
lines changed

2 files changed

+53
-39
lines changed

pydexcom/dexcom.py

Lines changed: 41 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
ArgumentError,
2525
ArgumentErrorEnum,
2626
DexcomError,
27+
ServerError,
28+
ServerErrorEnum,
2729
SessionError,
2830
SessionErrorEnum,
2931
)
@@ -93,50 +95,50 @@ def _post(
9395
)
9496

9597
try:
98+
response_json = response.json()
9699
response.raise_for_status()
97-
return response.json()
98100
except requests.HTTPError as http_error:
99-
error = self._handle_response(response)
100-
if error:
101-
raise error from http_error
102-
_LOGGER.exception("%s", response.text)
103-
raise
104-
105-
def _handle_response(self, response: requests.Response) -> DexcomError | None: # noqa: C901
106-
error: DexcomError | None = None
101+
raise self._handle_error_code(response_json) from http_error
102+
except requests.JSONDecodeError as json_error:
103+
_LOGGER.exception("JSON decode error: %s", response.text)
104+
raise ServerError(ServerErrorEnum.INVALID_JSON) from json_error
105+
else:
106+
return response_json
107+
108+
def _handle_error_code(self, json: dict[str, Any]) -> DexcomError: # noqa: C901, PLR0911
107109
"""
108-
Parse `requests.Response` for `pydexcom.errors.DexcomError`.
110+
Parse `requests.Response` JSON for `pydexcom.errors.DexcomError`.
109111
110-
:param response: `requests.Response` to parse
112+
:param response: `requests.Response` JSON to parse
111113
"""
112-
if response.json():
113-
_LOGGER.debug("%s", response.json())
114-
code = response.json().get("Code", None)
115-
message = response.json().get("Message", None)
116-
if code == "SessionIdNotFound":
117-
error = SessionError(SessionErrorEnum.NOT_FOUND)
118-
elif code == "SessionNotValid":
119-
error = SessionError(SessionErrorEnum.INVALID)
120-
elif code == "AccountPasswordInvalid": # defunct
121-
error = AccountError(AccountErrorEnum.FAILED_AUTHENTICATION)
122-
elif code == "SSO_AuthenticateMaxAttemptsExceeded":
123-
error = AccountError(AccountErrorEnum.MAX_ATTEMPTS)
124-
elif code == "SSO_InternalError":
125-
if message and (
126-
"Cannot Authenticate by AccountName" in message
127-
or "Cannot Authenticate by AccountId" in message
128-
):
129-
error = AccountError(AccountErrorEnum.FAILED_AUTHENTICATION)
130-
elif code == "InvalidArgument":
131-
if message and "accountName" in message:
132-
error = ArgumentError(ArgumentErrorEnum.USERNAME_INVALID)
133-
elif message and "password" in message:
134-
error = ArgumentError(ArgumentErrorEnum.PASSWORD_INVALID)
135-
elif message and "UUID" in message:
136-
error = ArgumentError(ArgumentErrorEnum.ACCOUNT_ID_INVALID)
137-
elif code and message:
138-
_LOGGER.debug("%s: %s", code, message)
139-
return error
114+
_LOGGER.debug("%s", json)
115+
code, message = json.get("Code"), json.get("Message")
116+
if code == "SessionIdNotFound":
117+
return SessionError(SessionErrorEnum.NOT_FOUND)
118+
if code == "SessionNotValid":
119+
return SessionError(SessionErrorEnum.INVALID)
120+
if code == "AccountPasswordInvalid":
121+
return AccountError(AccountErrorEnum.FAILED_AUTHENTICATION)
122+
if code == "SSO_AuthenticateMaxAttemptsExceeded":
123+
return AccountError(AccountErrorEnum.MAX_ATTEMPTS)
124+
if code == "SSO_InternalError": # noqa: SIM102
125+
if message and (
126+
"Cannot Authenticate by AccountName" in message
127+
or "Cannot Authenticate by AccountId" in message
128+
):
129+
return AccountError(AccountErrorEnum.FAILED_AUTHENTICATION)
130+
if code == "InvalidArgument":
131+
if message and "accountName" in message:
132+
return ArgumentError(ArgumentErrorEnum.USERNAME_INVALID)
133+
if message and "password" in message:
134+
return ArgumentError(ArgumentErrorEnum.PASSWORD_INVALID)
135+
if message and "UUID" in message:
136+
return ArgumentError(ArgumentErrorEnum.ACCOUNT_ID_INVALID)
137+
if code and message:
138+
_LOGGER.error("%s: %s", code, message)
139+
return ServerError(ServerErrorEnum.UNKNOWN_CODE)
140+
_LOGGER.error("%s", json)
141+
return ServerError(ServerErrorEnum.UNEXPECTED)
140142

141143
def _validate_region(self, region: Region) -> None:
142144
if region not in list(Region):

pydexcom/errors.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,14 @@ class ArgumentErrorEnum(DexcomErrorEnum):
3838
GLUCOSE_READING_INVALID = "JSON glucose reading incorrectly formatted"
3939

4040

41+
class ServerErrorEnum(DexcomErrorEnum):
42+
"""`ServerErrorEnum` strings."""
43+
44+
INVALID_JSON = "Invalid or malformed JSON in server response"
45+
UNKNOWN_CODE = "Unknown error code in server response"
46+
UNEXPECTED = "Unexpected server response"
47+
48+
4149
class DexcomError(Exception):
4250
"""Base class for all `pydexcom` errors."""
4351

@@ -70,3 +78,7 @@ class SessionError(DexcomError):
7078

7179
class ArgumentError(DexcomError):
7280
"""Errors involving `pydexcom` arguments."""
81+
82+
83+
class ServerError(DexcomError):
84+
"""Errors involving unexpected or malformed server responses."""

0 commit comments

Comments
 (0)