Skip to content

Commit 0a16428

Browse files
authored
🐛web-api: Fixes missing supportID on default 5XX responses (#7414)
1 parent 81c58df commit 0a16428

File tree

3 files changed

+48
-25
lines changed

3 files changed

+48
-25
lines changed

packages/service-library/src/servicelib/aiohttp/rest_middlewares.py

+2-3
Original file line numberDiff line numberDiff line change
@@ -51,14 +51,13 @@ def _process_and_raise_unexpected_error(request: web.BaseRequest, err: Exception
5151
"request.path": f"{request.path}",
5252
}
5353

54-
user_error_msg = _FMSG_INTERNAL_ERROR_USER_FRIENDLY.format(
55-
error_code=error_code
56-
)
54+
user_error_msg = _FMSG_INTERNAL_ERROR_USER_FRIENDLY
5755
http_error = create_http_error(
5856
err,
5957
user_error_msg,
6058
web.HTTPInternalServerError,
6159
skip_internal_error_details=_is_prod,
60+
error_code=error_code,
6261
)
6362
_logger.exception(
6463
**create_troubleshotting_log_kwargs(

packages/service-library/src/servicelib/aiohttp/rest_responses.py

+22-14
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,10 @@
55

66
from aiohttp import web, web_exceptions
77
from aiohttp.web_exceptions import HTTPError, HTTPException
8+
from common_library.error_codes import ErrorCodeStr
89
from common_library.json_serialization import json_dumps
910
from models_library.rest_error import ErrorGet, ErrorItemType
11+
from servicelib.rest_constants import RESPONSE_MODEL_POLICY
1012

1113
from ..aiohttp.status import HTTP_200_OK
1214
from ..mimetype_constants import MIMETYPE_APPLICATION_JSON
@@ -52,6 +54,7 @@ def create_http_error(
5254
http_error_cls: type[HTTPError] = web.HTTPInternalServerError,
5355
*,
5456
skip_internal_error_details: bool = False,
57+
error_code: ErrorCodeStr | None = None
5558
) -> HTTPError:
5659
"""
5760
- Response body conforms OAS schema model
@@ -61,33 +64,38 @@ def create_http_error(
6164
if not isinstance(errors, list):
6265
errors = [errors]
6366

64-
# TODO: guarantee no throw!
65-
6667
is_internal_error: bool = http_error_cls == web.HTTPInternalServerError
6768
default_message = reason or get_code_description(http_error_cls.status_code)
6869

6970
if is_internal_error and skip_internal_error_details:
70-
error = ErrorGet(
71-
errors=[],
72-
logs=[],
73-
status=http_error_cls.status_code,
74-
message=default_message,
71+
error = ErrorGet.model_validate(
72+
{
73+
"status": http_error_cls.status_code,
74+
"message": default_message,
75+
"support_id": error_code,
76+
}
7577
)
7678
else:
7779
items = [ErrorItemType.from_error(err) for err in errors]
78-
error = ErrorGet(
79-
errors=items,
80-
logs=[],
81-
status=http_error_cls.status_code,
82-
message=default_message,
80+
error = ErrorGet.model_validate(
81+
{
82+
"errors": items, # NOTE: deprecated!
83+
"status": http_error_cls.status_code,
84+
"message": default_message,
85+
"support_id": error_code,
86+
}
8387
)
8488

8589
assert not http_error_cls.empty_body # nosec
86-
payload = wrap_as_envelope(error=error)
90+
payload = wrap_as_envelope(
91+
error=error.model_dump(mode="json", **RESPONSE_MODEL_POLICY)
92+
)
8793

8894
return http_error_cls(
8995
reason=reason,
90-
text=json_dumps(payload),
96+
text=json_dumps(
97+
payload,
98+
),
9199
content_type=MIMETYPE_APPLICATION_JSON,
92100
)
93101

packages/service-library/tests/aiohttp/test_rest_responses.py

+24-8
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
# pylint: disable=unused-variable
44

55
import itertools
6+
import json
67

78
import pytest
89
from aiohttp import web
@@ -15,6 +16,7 @@
1516
HTTPNotModified,
1617
HTTPOk,
1718
)
19+
from common_library.error_codes import ErrorCodeStr, create_error_code
1820
from servicelib.aiohttp import status
1921
from servicelib.aiohttp.rest_responses import create_http_error, exception_to_response
2022
from servicelib.aiohttp.web_exceptions_extension import (
@@ -58,26 +60,40 @@ def test_collected_http_errors_map(status_code: int, http_error_cls: type[HTTPEr
5860

5961

6062
@pytest.mark.parametrize("skip_details", [True, False])
61-
def tests_exception_to_response(skip_details: bool):
62-
exception = create_http_error(
63-
errors=[RuntimeError("foo")],
64-
reason="Something whent wrong",
63+
@pytest.mark.parametrize("error_code", [None, create_error_code(Exception("fake"))])
64+
def tests_exception_to_response(skip_details: bool, error_code: ErrorCodeStr | None):
65+
66+
expected_reason = "Something whent wrong !"
67+
expected_exceptions: list[Exception] = [RuntimeError("foo")]
68+
69+
http_error = create_http_error(
70+
errors=expected_exceptions,
71+
reason=expected_reason,
6572
http_error_cls=web.HTTPInternalServerError,
6673
skip_internal_error_details=skip_details,
74+
error_code=error_code,
6775
)
6876

6977
# For now until deprecated SEE https://github.com/aio-libs/aiohttp/issues/2415
70-
assert isinstance(exception, Exception)
71-
assert isinstance(exception, web.Response)
72-
assert hasattr(exception, "__http_exception__")
78+
assert isinstance(http_error, Exception)
79+
assert isinstance(http_error, web.Response)
80+
assert hasattr(http_error, "__http_exception__")
7381

7482
# until they have exception.make_response(), we user
75-
response = exception_to_response(exception)
83+
response = exception_to_response(http_error)
7684
assert isinstance(response, web.Response)
7785
assert not isinstance(response, Exception)
7886
assert not hasattr(response, "__http_exception__")
7987

88+
# checks response components
8089
assert response.content_type == MIMETYPE_APPLICATION_JSON
8190
assert response.status == status.HTTP_500_INTERNAL_SERVER_ERROR
8291
assert response.text
8392
assert response.body
93+
94+
# checks response model
95+
response_json = json.loads(response.text)
96+
assert response_json["data"] is None
97+
assert response_json["error"]["message"] == expected_reason
98+
assert response_json["error"]["supportId"] == error_code
99+
assert response_json["error"]["status"] == response.status

0 commit comments

Comments
 (0)