Exception handling enhancement

This commit is contained in:
2025-09-23 22:42:26 +02:00
parent b1a76cdb57
commit 98285aa8aa
35 changed files with 3283 additions and 1743 deletions

View File

@@ -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)