error_responses.py 2.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596
  1. # (c) Nelen & Schuurmans
  2. import logging
  3. from typing import List
  4. from typing import Optional
  5. from typing import Union
  6. from fastapi.encoders import jsonable_encoder
  7. from fastapi.requests import Request
  8. from fastapi.responses import JSONResponse
  9. from starlette import status
  10. from clean_python import BadRequest
  11. from clean_python import Conflict
  12. from clean_python import DoesNotExist
  13. from clean_python import PermissionDenied
  14. from clean_python import Unauthorized
  15. from clean_python import ValueObject
  16. logger = logging.getLogger(__name__)
  17. __all__ = [
  18. "ValidationErrorResponse",
  19. "DefaultErrorResponse",
  20. "not_found_handler",
  21. "conflict_handler",
  22. "validation_error_handler",
  23. "permission_denied_handler",
  24. "unauthorized_handler",
  25. ]
  26. class ValidationErrorEntry(ValueObject):
  27. loc: List[Union[str, int]]
  28. msg: str
  29. type: str
  30. class ValidationErrorResponse(ValueObject):
  31. message: str
  32. detail: List[ValidationErrorEntry]
  33. class DefaultErrorResponse(ValueObject):
  34. message: str
  35. detail: Optional[str]
  36. async def not_found_handler(request: Request, exc: DoesNotExist) -> JSONResponse:
  37. return JSONResponse(
  38. status_code=status.HTTP_404_NOT_FOUND,
  39. content={
  40. "message": f"Could not find {exc.name}{' with id=' + str(exc.id) if exc.id else ''}"
  41. },
  42. )
  43. async def conflict_handler(request: Request, exc: Conflict) -> JSONResponse:
  44. return JSONResponse(
  45. status_code=status.HTTP_409_CONFLICT,
  46. content={
  47. "message": "Conflict",
  48. "detail": jsonable_encoder(exc.args[0] if exc.args else None),
  49. },
  50. )
  51. async def validation_error_handler(request: Request, exc: BadRequest) -> JSONResponse:
  52. return JSONResponse(
  53. status_code=status.HTTP_400_BAD_REQUEST,
  54. content=ValidationErrorResponse(
  55. message="Validation error", detail=exc.errors() # type: ignore
  56. ).model_dump(mode="json"),
  57. )
  58. async def unauthorized_handler(request: Request, exc: Unauthorized) -> JSONResponse:
  59. if exc.args:
  60. logger.info(f"unauthorized: {exc}")
  61. return JSONResponse(
  62. status_code=status.HTTP_401_UNAUTHORIZED,
  63. content={"message": "Unauthorized", "detail": None},
  64. headers={"WWW-Authenticate": "Bearer"},
  65. )
  66. async def permission_denied_handler(
  67. request: Request, exc: PermissionDenied
  68. ) -> JSONResponse:
  69. return JSONResponse(
  70. status_code=status.HTTP_403_FORBIDDEN,
  71. content={
  72. "message": "Permission denied",
  73. "detail": jsonable_encoder(exc.args[0] if exc.args else None),
  74. },
  75. )