Source code for core_apis.decorators.response_wrapper
# -*- coding: utf-8 -*-
"""
Response wrapper decorator for standardizing API responses.
This module provides a decorator that wraps API endpoint functions to ensure
consistent response formatting across the application. All responses follow
a standardized structure with proper status codes and error handling.
"""
from functools import wraps
from typing import Any
from typing import Callable
from typing import Dict
from typing import Optional
from typing import Tuple
from typing import TypedDict
from core_https import HTTPStatus
from core_https import StatusInfo
from core_https.exceptions import ErrorInfo
from core_https.exceptions import InternalServerError
from core_https.exceptions import ServiceException
[docs]
class ApiResponse(TypedDict):
"""
Standardised API response envelope returned by ``wrap_response``.
:param code: HTTP status code (integer).
:param status: Response status string: ``"success"``, ``"error"``, or ``"failure"``.
:param result: Response payload. Present on successful responses only.
:param error: Error detail. Present on error/failure responses only.
"""
code: int
status: str
result: Optional[Dict[str, Any]]
error: Optional[ErrorInfo]
[docs]
def wrap_response(fnc: Callable[..., Tuple[HTTPStatus, Any]]) -> Callable[..., ApiResponse]:
"""
This decorator provides a generic mechanism to return the response payload
for the endpoints in the same format. This way each router or controller does not
need to take care of it. It returns the response into the below format...
.. code-block:: text
Response payload:
{
"code": 2XX | 4XX | 5XX,
"status": "success" | "error" | "failure",
"result": ... # on success
"error": ..., # on error / failure
}
Where:
code:
- 2XX -> For success responses.
- 4XX -> For service-managed errors.
- 5XX -> For unmanaged or internal server errors.
status:
Contains the text: "success", "failure", or "error". Where "failure"
is for HTTP status response values from 500 to 599, "error" is for
statuses from 400 to 499, and "success" is for everything
else (e.g. 1XX, 2XX and 3XX responses).
result:
Contains the result. Present on successful responses only.
error:
Contains the error detail. Present on error/failure responses only.
..
"""
@wraps(fnc)
def wrapper(*args, **kwargs) -> ApiResponse:
try:
code, result = fnc(*args, **kwargs)
return {
"code": code.value,
"status": StatusInfo.SUCCESS,
"result": result,
"error": None,
}
except ServiceException as error:
return {
"code": error.status_code,
"status": StatusInfo.ERROR,
"result": None,
"error": error.get_error_info(),
}
except InternalServerError as error:
return {
"code": error.status_code,
"status": StatusInfo.FAILURE,
"result": None,
"error": error.get_error_info(),
}
except Exception: # pylint: disable=broad-exception-caught
return {
"code": int(HTTPStatus.INTERNAL_SERVER_ERROR.value),
"status": StatusInfo.FAILURE,
"result": None,
"error": {
"type": InternalServerError.__name__,
"details": "An unexpected error occurred!",
},
}
return wrapper