Skip to content

Commit 554d573

Browse files
committed
Avoid hiding error info in non-compliant error responses
1 parent 52c7158 commit 554d573

File tree

4 files changed

+56
-23
lines changed

4 files changed

+56
-23
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1818
Although removed from openeo-processes 2.0.0-rc1, support for `load_result`, `predict_random_forest` and `load_ml_model`
1919
is preserved but deprecated. ([#424](https://github.com/Open-EO/openeo-python-client/issues/424))
2020
- Show more informative error message on `403 Forbidden` errors from CDSE firewall ([#512](https://github.com/Open-EO/openeo-python-client/issues/512))
21+
- Handle API error responses more strict and avoid hiding possibly important information in JSON-formatted but non-compliant error responses.
2122

2223
### Removed
2324

openeo/rest/__init__.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -74,9 +74,10 @@ class OpenEoApiError(OpenEoApiPlainError):
7474

7575
def __init__(
7676
self,
77-
http_status_code: Optional[int] = None,
78-
code: str = "unknown",
79-
message: str = "unknown error",
77+
*,
78+
http_status_code: int,
79+
code: str,
80+
message: str,
8081
id: Optional[str] = None,
8182
url: Optional[str] = None,
8283
):

openeo/rest/connection.py

Lines changed: 28 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -176,28 +176,37 @@ def _raise_api_error(self, response: requests.Response):
176176
"""Convert API error response to Python exception"""
177177
status_code = response.status_code
178178
try:
179-
# Try parsing the error info according to spec and wrap it in an exception.
180179
info = response.json()
181-
exception = OpenEoApiError(
182-
http_status_code=status_code,
183-
code=info.get("code", "unknown"),
184-
message=info.get("message", f"unknown error ({repr_truncate(info, width=200)})"),
185-
id=info.get("id"),
186-
url=info.get("url"),
187-
)
188180
except Exception:
189-
# Parsing of error info went wrong: let's see if we can still extract some helpful information.
190-
text = response.text
191-
error_message = None
192-
_log.warning(f"Failed to parse API error response: [{status_code}] {text!r} (headers: {response.headers})")
193-
if status_code == 502 and "Proxy Error" in text:
194-
error_message = (
195-
"Received 502 Proxy Error."
196-
" This typically happens when a synchronous openEO processing request takes too long and is aborted."
197-
" Consider using a batch job instead."
181+
info = None
182+
183+
# Valid JSON object with "code" and "message" fields indicates a proper openEO API error.
184+
if isinstance(info, dict):
185+
error_code = info.get("code")
186+
error_message = info.get("message")
187+
if error_code and isinstance(error_code, str) and error_message and isinstance(error_message, str):
188+
raise OpenEoApiError(
189+
http_status_code=status_code,
190+
code=error_code,
191+
message=error_message,
192+
id=info.get("id"),
193+
url=info.get("url"),
198194
)
199-
exception = OpenEoApiPlainError(message=text, http_status_code=status_code, error_message=error_message)
200-
raise exception
195+
196+
# Failed to parse it as a compliant openEO API error: show body as-is in the exception.
197+
text = response.text
198+
error_message = None
199+
_log.warning(f"Failed to parse API error response: [{status_code}] {text!r} (headers: {response.headers})")
200+
201+
# TODO: eliminate this VITO-backend specific error massaging?
202+
if status_code == 502 and "Proxy Error" in text:
203+
error_message = (
204+
"Received 502 Proxy Error."
205+
" This typically happens when a synchronous openEO processing request takes too long and is aborted."
206+
" Consider using a batch job instead."
207+
)
208+
209+
raise OpenEoApiPlainError(message=text, http_status_code=status_code, error_message=error_message)
201210

202211
def get(self, path: str, stream: bool = False, auth: Optional[AuthBase] = None, **kwargs) -> Response:
203212
"""

tests/rest/test_connection.py

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,28 @@ def test_rest_api_expected_status_with_error(requests_mock):
130130
conn.get("/bar", check_error=False, expected_status=[302, 303])
131131

132132

133+
@pytest.mark.parametrize(
134+
["status_code", "text", "expected"],
135+
[
136+
(400, "Nope, no JSON here", "[400] Nope, no JSON here"),
137+
(400, '{"code": "InvalidJSON", "message": "oops}}}}}}', '[400] {"code": "InvalidJSON", "message": "oops}}}}}}'),
138+
(400, '{"status": "error", "msg": "Nope"}', '[400] {"status": "error", "msg": "Nope"}'),
139+
(500, '{"status": "error", "msg": "Nope"}', '[500] {"status": "error", "msg": "Nope"}'),
140+
(400, '{"status": "error", "code": "WrongColor"}', '[400] {"status": "error", "code": "WrongColor"}'),
141+
(400, '{"code": "WrongColor", "message": "Nope"}', "[400] WrongColor: Nope"),
142+
(400, '{"code": "WrongColor", "message": "Nope", "id": "r-123"}', "[400] WrongColor: Nope (ref: r-123)"),
143+
],
144+
)
145+
def test_error_response_format(requests_mock, status_code, text, expected):
146+
requests_mock.get("https://oeo.test/bar", status_code=status_code, text=text)
147+
conn = RestApiConnection(API_URL)
148+
with pytest.raises(
149+
OpenEoApiPlainError,
150+
match=re.escape(expected) if isinstance(expected, str) else expected,
151+
):
152+
conn.get("/bar")
153+
154+
133155
def test_502_proxy_error(requests_mock):
134156
"""EP-3387"""
135157
requests_mock.get("https://oeo.test/bar", status_code=502, text="""<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
@@ -157,7 +179,7 @@ def test_cdse_firewall_error(requests_mock):
157179
conn = RestApiConnection(API_URL)
158180
with pytest.raises(
159181
OpenEoApiPlainError,
160-
match=r"\[403\] unknown: unknown error.*This request was rejected.*provide reference ID 128968",
182+
match=r"\[403\].*This request was rejected.*provide reference ID 128968",
161183
):
162184
conn.get("/bar")
163185

0 commit comments

Comments
 (0)