Source code for search.routes.api.exceptions

"""
Exception handlers for API endpoints.

.. todo:: This module belongs in :mod:`arxiv.base`.

"""

from typing import Callable, List, Tuple

from werkzeug.exceptions import NotFound, Forbidden, Unauthorized, \
    MethodNotAllowed, RequestEntityTooLarge, BadRequest, InternalServerError, \
    HTTPException
from flask import make_response, Response, jsonify

from arxiv import status
from arxiv.base import logging

logger = logging.getLogger(__name__)

_handlers = []


[docs]def handler(exception: type) -> Callable: """Generate a decorator to register a handler for an exception.""" def deco(func: Callable) -> Callable: """Register a function as an exception handler.""" _handlers.append((exception, func)) return func return deco
[docs]def get_handlers() -> List[Tuple[type, Callable]]: """ Get a list of registered exception handlers. Returns ------- list List of (:class:`.HTTPException`, callable) tuples. """ return _handlers
[docs]@handler(NotFound) def handle_not_found(error: NotFound) -> Response: """Render the base 404 error page.""" rendered = jsonify({'code': error.code, 'error': error.description}) response = make_response(rendered) response.status_code = status.HTTP_404_NOT_FOUND return response
[docs]@handler(Forbidden) def handle_forbidden(error: Forbidden) -> Response: """Render the base 403 error page.""" rendered = jsonify({'code': error.code, 'error': error.description}) response = make_response(rendered) response.status_code = status.HTTP_403_FORBIDDEN return response
[docs]@handler(Unauthorized) def handle_unauthorized(error: Unauthorized) -> Response: """Render the base 401 error page.""" rendered = jsonify({'code': error.code, 'error': error.description}) response = make_response(rendered) response.status_code = status.HTTP_401_UNAUTHORIZED return response
[docs]@handler(MethodNotAllowed) def handle_method_not_allowed(error: MethodNotAllowed) -> Response: """Render the base 405 error page.""" rendered = jsonify({'code': error.code, 'error': error.description}) response = make_response(rendered) response.status_code = status.HTTP_405_METHOD_NOT_ALLOWED return response
[docs]@handler(RequestEntityTooLarge) def handle_request_entity_too_large(error: RequestEntityTooLarge) -> Response: """Render the base 413 error page.""" rendered = jsonify({'code': error.code, 'error': error.description}) response = make_response(rendered) response.status_code = status.HTTP_413_REQUEST_ENTITY_TOO_LARGE return response
[docs]@handler(BadRequest) def handle_bad_request(error: BadRequest) -> Response: """Render the base 400 error page.""" rendered = jsonify({'code': error.code, 'error': error.description}) response = make_response(rendered) response.status_code = status.HTTP_400_BAD_REQUEST return response
[docs]@handler(InternalServerError) def handle_internal_server_error(error: InternalServerError) -> Response: """Render the base 500 error page.""" if isinstance(error, HTTPException): rendered = jsonify({'code': error.code, 'error': error.description}) else: logger.error('Caught unhandled exception: %s', error) rendered = jsonify({'code': status.HTTP_500_INTERNAL_SERVER_ERROR, 'error': 'Unexpected error'}) response = make_response(rendered) response.status_code = status.HTTP_500_INTERNAL_SERVER_ERROR return response