Exception handling enhancement
This commit is contained in:
@@ -3,113 +3,24 @@
|
||||
Exception handler for FastAPI application.
|
||||
|
||||
Provides consistent error responses and logging for all custom exceptions.
|
||||
This module provides classes and functions for:
|
||||
- Unified exception handling for all application exceptions
|
||||
- Consistent error response formatting
|
||||
- Comprehensive logging with structured data
|
||||
"""
|
||||
|
||||
import logging
|
||||
from typing import Union
|
||||
|
||||
from fastapi import Request, HTTPException
|
||||
from fastapi.exceptions import RequestValidationError
|
||||
from fastapi.responses import JSONResponse
|
||||
from starlette.middleware.base import BaseHTTPMiddleware
|
||||
|
||||
from .base import LetzShopException
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ExceptionHandlerMiddleware(BaseHTTPMiddleware):
|
||||
"""Middleware to handle custom exceptions and convert them to JSON responses."""
|
||||
|
||||
async def dispatch(self, request: Request, call_next):
|
||||
try:
|
||||
response = await call_next(request)
|
||||
return response
|
||||
except LetzShopException as exc:
|
||||
return await self.handle_custom_exception(request, exc)
|
||||
except HTTPException as exc:
|
||||
return await self.handle_http_exception(request, exc)
|
||||
except Exception as exc:
|
||||
return await self.handle_generic_exception(request, exc)
|
||||
|
||||
async def handle_custom_exception(
|
||||
self,
|
||||
request: Request,
|
||||
exc: LetzShopException
|
||||
) -> JSONResponse:
|
||||
"""Handle custom LetzShop exceptions."""
|
||||
|
||||
# Log the exception
|
||||
logger.error(
|
||||
f"Custom exception in {request.method} {request.url}: "
|
||||
f"{exc.error_code} - {exc.message}",
|
||||
extra={
|
||||
"error_code": exc.error_code,
|
||||
"status_code": exc.status_code,
|
||||
"details": exc.details,
|
||||
"url": str(request.url),
|
||||
"method": request.method,
|
||||
}
|
||||
)
|
||||
|
||||
return JSONResponse(
|
||||
status_code=exc.status_code,
|
||||
content=exc.to_dict()
|
||||
)
|
||||
|
||||
async def handle_http_exception(
|
||||
self,
|
||||
request: Request,
|
||||
exc: HTTPException
|
||||
) -> JSONResponse:
|
||||
"""Handle FastAPI HTTPExceptions."""
|
||||
|
||||
logger.error(
|
||||
f"HTTP exception in {request.method} {request.url}: "
|
||||
f"{exc.status_code} - {exc.detail}",
|
||||
extra={
|
||||
"status_code": exc.status_code,
|
||||
"detail": exc.detail,
|
||||
"url": str(request.url),
|
||||
"method": request.method,
|
||||
}
|
||||
)
|
||||
|
||||
return JSONResponse(
|
||||
status_code=exc.status_code,
|
||||
content={
|
||||
"error_code": f"HTTP_{exc.status_code}",
|
||||
"message": exc.detail,
|
||||
"status_code": exc.status_code,
|
||||
}
|
||||
)
|
||||
|
||||
async def handle_generic_exception(
|
||||
self,
|
||||
request: Request,
|
||||
exc: Exception
|
||||
) -> JSONResponse:
|
||||
"""Handle unexpected exceptions."""
|
||||
|
||||
logger.error(
|
||||
f"Unexpected exception in {request.method} {request.url}: {str(exc)}",
|
||||
exc_info=True,
|
||||
extra={
|
||||
"url": str(request.url),
|
||||
"method": request.method,
|
||||
"exception_type": type(exc).__name__,
|
||||
}
|
||||
)
|
||||
|
||||
return JSONResponse(
|
||||
status_code=500,
|
||||
content={
|
||||
"error_code": "INTERNAL_SERVER_ERROR",
|
||||
"message": "Internal server error",
|
||||
"status_code": 500,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def setup_exception_handlers(app):
|
||||
"""Setup exception handlers for the FastAPI app."""
|
||||
|
||||
@@ -126,6 +37,7 @@ def setup_exception_handlers(app):
|
||||
"details": exc.details,
|
||||
"url": str(request.url),
|
||||
"method": request.method,
|
||||
"exception_type": type(exc).__name__,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -146,6 +58,7 @@ def setup_exception_handlers(app):
|
||||
"detail": exc.detail,
|
||||
"url": str(request.url),
|
||||
"method": request.method,
|
||||
"exception_type": "HTTPException",
|
||||
}
|
||||
)
|
||||
|
||||
@@ -158,6 +71,32 @@ def setup_exception_handlers(app):
|
||||
}
|
||||
)
|
||||
|
||||
@app.exception_handler(RequestValidationError)
|
||||
async def validation_exception_handler(request: Request, exc: RequestValidationError):
|
||||
"""Handle Pydantic validation errors with consistent format."""
|
||||
|
||||
logger.error(
|
||||
f"Validation error in {request.method} {request.url}: {exc.errors()}",
|
||||
extra={
|
||||
"validation_errors": exc.errors(),
|
||||
"url": str(request.url),
|
||||
"method": request.method,
|
||||
"exception_type": "RequestValidationError",
|
||||
}
|
||||
)
|
||||
|
||||
return JSONResponse(
|
||||
status_code=422,
|
||||
content={
|
||||
"error_code": "VALIDATION_ERROR",
|
||||
"message": "Request validation failed",
|
||||
"status_code": 422,
|
||||
"details": {
|
||||
"validation_errors": exc.errors()
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
@app.exception_handler(Exception)
|
||||
async def generic_exception_handler(request: Request, exc: Exception):
|
||||
"""Handle unexpected exceptions."""
|
||||
@@ -204,4 +143,4 @@ def raise_auth_error(message: str = "Authentication failed") -> None:
|
||||
def raise_permission_error(message: str = "Access denied") -> None:
|
||||
"""Convenience function to raise AuthorizationException."""
|
||||
from .base import AuthorizationException
|
||||
raise AuthorizationException(message)
|
||||
raise AuthorizationException(message)
|
||||
Reference in New Issue
Block a user