90 lines
2.7 KiB
Python
90 lines
2.7 KiB
Python
"""Exception handling middleware."""
|
|
|
|
from datetime import UTC, datetime
|
|
|
|
from fastapi import FastAPI, Request
|
|
from fastapi.responses import JSONResponse
|
|
from starlette.exceptions import HTTPException as StarletteHTTPException
|
|
|
|
from app.domain.exceptions import (
|
|
AlreadyExistsException,
|
|
DomainException,
|
|
ForbiddenException,
|
|
NotFoundException,
|
|
UnauthorizedException,
|
|
ValidationException,
|
|
)
|
|
|
|
|
|
def get_status_code(exc: DomainException) -> int:
|
|
"""Map domain exceptions to HTTP status codes."""
|
|
match exc:
|
|
case ValidationException():
|
|
return 400
|
|
case UnauthorizedException():
|
|
return 401
|
|
case ForbiddenException():
|
|
return 403
|
|
case NotFoundException():
|
|
return 404
|
|
case AlreadyExistsException():
|
|
return 409
|
|
case _:
|
|
return 500
|
|
|
|
|
|
async def domain_exception_handler(request: Request, exc: DomainException) -> JSONResponse:
|
|
"""Handle domain exceptions."""
|
|
status_code = get_status_code(exc)
|
|
return JSONResponse(
|
|
status_code=status_code,
|
|
content={
|
|
"error": exc.__class__.__name__,
|
|
"message": exc.message,
|
|
"timestamp": datetime.now(UTC).isoformat(),
|
|
"path": str(request.url.path),
|
|
},
|
|
)
|
|
|
|
|
|
async def http_exception_handler(request: Request, exc: StarletteHTTPException) -> JSONResponse:
|
|
"""Handle HTTP exceptions."""
|
|
return JSONResponse(
|
|
status_code=exc.status_code,
|
|
content={
|
|
"error": "HTTPException",
|
|
"message": str(exc.detail),
|
|
"timestamp": datetime.now(UTC).isoformat(),
|
|
"path": str(request.url.path),
|
|
},
|
|
)
|
|
|
|
|
|
async def generic_exception_handler(request: Request, exc: Exception) -> JSONResponse:
|
|
"""Handle generic exceptions."""
|
|
return JSONResponse(
|
|
status_code=500,
|
|
content={
|
|
"error": "InternalServerError",
|
|
"message": "An unexpected error occurred",
|
|
"timestamp": datetime.now(UTC).isoformat(),
|
|
"path": str(request.url.path),
|
|
},
|
|
)
|
|
|
|
|
|
def register_exception_handlers(app: FastAPI) -> None:
|
|
"""Register all exception handlers with FastAPI app."""
|
|
if not isinstance(app, FastAPI):
|
|
raise TypeError("app must be a FastAPI instance")
|
|
|
|
# Domain exceptions
|
|
app.add_exception_handler(DomainException, domain_exception_handler) # type: ignore[arg-type]
|
|
|
|
# HTTP exceptions
|
|
app.add_exception_handler(StarletteHTTPException, http_exception_handler) # type: ignore[arg-type]
|
|
|
|
# Generic exceptions (only in production)
|
|
# In development, let FastAPI show detailed traceback
|
|
# app.add_exception_handler(Exception, generic_exception_handler)
|