Skip to content

Commit 2f5315d

Browse files
committed
fix: handle JSONDecodeError in _handle_response to prevent crashes
- Add ServerResponseError and ServerResponseErrorEnum for invalid JSON responses - Wrap response.json() in try/except to catch JSONDecodeError - Return ServerResponseError instead of crashing when API returns empty/malformed JSON - Fixes #62: erratic JSONDecodeError crashes during API polling This prevents application crashes when Dexcom Share API returns empty or invalid JSON responses, allowing graceful error handling and retry logic
1 parent 343a06e commit 2f5315d

2 files changed

Lines changed: 42 additions & 26 deletions

File tree

pydexcom/dexcom.py

Lines changed: 32 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from typing import Any
66

77
import requests
8+
import json
89

910
from .const import (
1011
DEFAULT_UUID,
@@ -26,6 +27,8 @@
2627
DexcomError,
2728
SessionError,
2829
SessionErrorEnum,
30+
ServerResponseError,
31+
ServerResponseErrorEnum,
2932
)
3033
from .glucose_reading import GlucoseReading
3134
from .util import _LOGGER, valid_uuid
@@ -109,33 +112,36 @@ def _handle_response(self, response: requests.Response) -> DexcomError | None:
109112
110113
:param response: `requests.Response` to parse
111114
"""
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-
):
115+
try:
116+
if response.json():
117+
_LOGGER.debug("%s", response.json())
118+
code = response.json().get("Code", None)
119+
message = response.json().get("Message", None)
120+
if code == "SessionIdNotFound":
121+
error = SessionError(SessionErrorEnum.NOT_FOUND)
122+
elif code == "SessionNotValid":
123+
error = SessionError(SessionErrorEnum.INVALID)
124+
elif code == "AccountPasswordInvalid": # defunct
129125
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)
126+
elif code == "SSO_AuthenticateMaxAttemptsExceeded":
127+
error = AccountError(AccountErrorEnum.MAX_ATTEMPTS)
128+
elif code == "SSO_InternalError":
129+
if message and (
130+
"Cannot Authenticate by AccountName" in message
131+
or "Cannot Authenticate by AccountId" in message
132+
):
133+
error = AccountError(AccountErrorEnum.FAILED_AUTHENTICATION)
134+
elif code == "InvalidArgument":
135+
if message and "accountName" in message:
136+
error = ArgumentError(ArgumentErrorEnum.USERNAME_INVALID)
137+
elif message and "password" in message:
138+
error = ArgumentError(ArgumentErrorEnum.PASSWORD_INVALID)
139+
elif message and "UUID" in message:
140+
error = ArgumentError(ArgumentErrorEnum.ACCOUNT_ID_INVALID)
141+
elif code and message:
142+
_LOGGER.debug("%s: %s", code, message)
143+
except json.JSONDecodeError:
144+
error = ServerResponseError(ServerResponseErrorEnum.INVALID_JSON)
139145
return error
140146

141147
def _validate_region(self, region: Region) -> None:

pydexcom/errors.py

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

4040

41+
class ServerResponseErrorEnum(DexcomErrorEnum):
42+
"""`ServerResponseError` strings."""
43+
44+
INVALID_JSON = "Invalid or malformed JSON in server response"
45+
46+
4147
class DexcomError(Exception):
4248
"""Base class for all `pydexcom` errors."""
4349

@@ -70,3 +76,7 @@ class SessionError(DexcomError):
7076

7177
class ArgumentError(DexcomError):
7278
"""Errors involving `pydexcom` arguments."""
79+
80+
81+
class ServerResponseError(DexcomError):
82+
"""Errors involving unexpected or malformed server responses (e.g., JSONDecodeError)."""

0 commit comments

Comments
 (0)