Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -51,14 +51,13 @@ def _process_and_raise_unexpected_error(request: web.BaseRequest, err: Exception
"request.path": f"{request.path}",
}

user_error_msg = _FMSG_INTERNAL_ERROR_USER_FRIENDLY.format(
error_code=error_code
)
user_error_msg = _FMSG_INTERNAL_ERROR_USER_FRIENDLY
http_error = create_http_error(
err,
user_error_msg,
web.HTTPInternalServerError,
skip_internal_error_details=_is_prod,
error_code=error_code,
)
_logger.exception(
**create_troubleshotting_log_kwargs(
Expand Down
36 changes: 22 additions & 14 deletions packages/service-library/src/servicelib/aiohttp/rest_responses.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@

from aiohttp import web, web_exceptions
from aiohttp.web_exceptions import HTTPError, HTTPException
from common_library.error_codes import ErrorCodeStr
from common_library.json_serialization import json_dumps
from models_library.rest_error import ErrorGet, ErrorItemType
from servicelib.rest_constants import RESPONSE_MODEL_POLICY

from ..aiohttp.status import HTTP_200_OK
from ..mimetype_constants import MIMETYPE_APPLICATION_JSON
Expand Down Expand Up @@ -52,6 +54,7 @@ def create_http_error(
http_error_cls: type[HTTPError] = web.HTTPInternalServerError,
*,
skip_internal_error_details: bool = False,
error_code: ErrorCodeStr | None = None
) -> HTTPError:
"""
- Response body conforms OAS schema model
Expand All @@ -61,33 +64,38 @@ def create_http_error(
if not isinstance(errors, list):
errors = [errors]

# TODO: guarantee no throw!

is_internal_error: bool = http_error_cls == web.HTTPInternalServerError
default_message = reason or get_code_description(http_error_cls.status_code)

if is_internal_error and skip_internal_error_details:
error = ErrorGet(
errors=[],
logs=[],
status=http_error_cls.status_code,
message=default_message,
error = ErrorGet.model_validate(
{
"status": http_error_cls.status_code,
"message": default_message,
"support_id": error_code,
}
)
else:
items = [ErrorItemType.from_error(err) for err in errors]
error = ErrorGet(
errors=items,
logs=[],
status=http_error_cls.status_code,
message=default_message,
error = ErrorGet.model_validate(
{
"errors": items, # NOTE: deprecated!
"status": http_error_cls.status_code,
"message": default_message,
"support_id": error_code,
}
)

assert not http_error_cls.empty_body # nosec
payload = wrap_as_envelope(error=error)
payload = wrap_as_envelope(
error=error.model_dump(mode="json", **RESPONSE_MODEL_POLICY)
)

return http_error_cls(
reason=reason,
text=json_dumps(payload),
text=json_dumps(
payload,
),
content_type=MIMETYPE_APPLICATION_JSON,
)

Expand Down
32 changes: 24 additions & 8 deletions packages/service-library/tests/aiohttp/test_rest_responses.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
# pylint: disable=unused-variable

import itertools
import json

import pytest
from aiohttp import web
Expand All @@ -15,6 +16,7 @@
HTTPNotModified,
HTTPOk,
)
from common_library.error_codes import ErrorCodeStr, create_error_code
from servicelib.aiohttp import status
from servicelib.aiohttp.rest_responses import create_http_error, exception_to_response
from servicelib.aiohttp.web_exceptions_extension import (
Expand Down Expand Up @@ -58,26 +60,40 @@ def test_collected_http_errors_map(status_code: int, http_error_cls: type[HTTPEr


@pytest.mark.parametrize("skip_details", [True, False])
def tests_exception_to_response(skip_details: bool):
exception = create_http_error(
errors=[RuntimeError("foo")],
reason="Something whent wrong",
@pytest.mark.parametrize("error_code", [None, create_error_code(Exception("fake"))])
def tests_exception_to_response(skip_details: bool, error_code: ErrorCodeStr | None):

expected_reason = "Something whent wrong !"
expected_exceptions: list[Exception] = [RuntimeError("foo")]

http_error = create_http_error(
errors=expected_exceptions,
reason=expected_reason,
http_error_cls=web.HTTPInternalServerError,
skip_internal_error_details=skip_details,
error_code=error_code,
)

# For now until deprecated SEE https://github.com/aio-libs/aiohttp/issues/2415
assert isinstance(exception, Exception)
assert isinstance(exception, web.Response)
assert hasattr(exception, "__http_exception__")
assert isinstance(http_error, Exception)
assert isinstance(http_error, web.Response)
assert hasattr(http_error, "__http_exception__")

# until they have exception.make_response(), we user
response = exception_to_response(exception)
response = exception_to_response(http_error)
assert isinstance(response, web.Response)
assert not isinstance(response, Exception)
assert not hasattr(response, "__http_exception__")

# checks response components
assert response.content_type == MIMETYPE_APPLICATION_JSON
assert response.status == status.HTTP_500_INTERNAL_SERVER_ERROR
assert response.text
assert response.body

# checks response model
response_json = json.loads(response.text)
assert response_json["data"] is None
assert response_json["error"]["message"] == expected_reason
assert response_json["error"]["supportId"] == error_code
assert response_json["error"]["status"] == response.status
Loading