Files

59 lines
1.7 KiB
Python

"""Safe, JSON:API-compatible exception handling."""
import logging
import uuid
from rest_framework.exceptions import APIException
from rest_framework_json_api.exceptions import exception_handler as jsonapi_exception_handler
logger = logging.getLogger(__name__)
ERROR_ID_HEADER = "X-Error-ID"
class APIConflictException(APIException):
"""A deliberately raised, non-sensitive resource conflict."""
status_code = 409
default_detail = "The request conflicts with the current resource state."
default_code = "conflict"
class InternalServerError(APIException):
"""Sanitized representation of an unexpected server exception."""
status_code = 500
default_detail = "An unexpected server error occurred."
default_code = "internal_error"
def hardened_exception_handler(exc, context):
"""Render expected errors normally and sanitize unexpected exceptions.
Unexpected exception details and tracebacks are logged server-side under a
generated error identifier. Only that opaque identifier is returned to the
client, in a response header, so operators can correlate a report safely.
"""
response = jsonapi_exception_handler(exc, context)
if response is not None:
return response
error_id = uuid.uuid4().hex
logger.error(
"Unhandled API exception; error_id=%s",
error_id,
exc_info=exc,
)
response = jsonapi_exception_handler(InternalServerError(), context)
response[ERROR_ID_HEADER] = error_id
return response
class HardenedExceptionHandlingMixin:
"""Opt a DRF view into :func:`hardened_exception_handler`."""
def get_exception_handler(self):
return hardened_exception_handler