74 lines
2.7 KiB
Python
74 lines
2.7 KiB
Python
import logging
|
|
from typing import Union
|
|
|
|
from flask import Flask, Response, jsonify
|
|
from werkzeug.exceptions import HTTPException
|
|
from werkzeug.http import HTTP_STATUS_CODES
|
|
|
|
from tofu_api.common import string_utils
|
|
from tofu_api.common.exceptions import AppException
|
|
from .exceptions import InternalServerError
|
|
|
|
T_Response = Union[Response, tuple[Response, int]]
|
|
|
|
|
|
class RestApiErrorHandler:
|
|
"""
|
|
Error handler class for REST API errors.
|
|
"""
|
|
|
|
# Dependencies
|
|
logger: logging.Logger
|
|
|
|
# Options
|
|
debug_mode: bool = False
|
|
|
|
# Lookup table for HTTP status codes to API error codes
|
|
_http_to_api_error_codes: dict[int, str]
|
|
|
|
def __init__(self, *, debug_mode: bool = False):
|
|
self.logger = logging.getLogger(type(self).__name__)
|
|
self.debug_mode = debug_mode
|
|
|
|
# Generate lookup table for HTTP status codes
|
|
self._http_to_api_error_codes = {
|
|
http_status: string_utils.str_to_snake_case(name)
|
|
for http_status, name in HTTP_STATUS_CODES.items()
|
|
}
|
|
|
|
def register_error_handlers(self, app: Flask) -> None:
|
|
"""
|
|
Registers error handlers for different types of exceptions to the app.
|
|
"""
|
|
app.register_error_handler(AppException, self.handle_app_exception)
|
|
app.register_error_handler(HTTPException, self.handle_http_exception)
|
|
app.register_error_handler(Exception, self.handle_generic_exception)
|
|
|
|
@staticmethod
|
|
def handle_app_exception(exception: AppException) -> T_Response:
|
|
"""
|
|
Handles exceptions of type `AppException` that were not handled by any more specific handler.
|
|
"""
|
|
return jsonify(exception.to_dict()), exception.status_code
|
|
|
|
def handle_http_exception(self, exception: HTTPException) -> T_Response:
|
|
"""
|
|
Handles exceptions of type `HTTPException`, i.e. any werkzeug HTTP exceptions.
|
|
"""
|
|
if exception.code >= 500:
|
|
self.logger.exception('HTTP exception with status code %s: %s', exception.code, type(exception).__name__)
|
|
|
|
return jsonify({
|
|
'code': self._http_to_api_error_codes.get(exception.code, 'unknown_http_error'),
|
|
'message': exception.description,
|
|
}), exception.code
|
|
|
|
def handle_generic_exception(self, exception: Exception) -> T_Response:
|
|
"""
|
|
Fallback handler for any exceptions not handled by any other handler.
|
|
"""
|
|
self.logger.exception('Uncaught exception: %s', type(exception).__name__)
|
|
|
|
wrapped_exception = InternalServerError('There was an uncaught error on the server.', inner_exception=exception)
|
|
return jsonify(wrapped_exception.to_dict(debug=self.debug_mode)), wrapped_exception.status_code
|