feat: enhance error handling with custom HTTP exception responses and middleware improvements

This commit is contained in:
2026-02-16 13:18:42 -06:00
parent ffc82ea82b
commit e6ef5c9094
4 changed files with 56 additions and 24 deletions

View File

@@ -18,5 +18,10 @@ class UserContextMiddleware(BaseHTTPMiddleware):
# Log error or ignore
pass
response = await call_next(request)
try:
response = await call_next(request)
except Exception:
# Re-raise the exception to let other middleware and handlers deal with it
raise
return response

View File

@@ -5,7 +5,7 @@ Manejadores globales de excepciones para FastAPI
import logging
from typing import Any, Dict
from fastapi import Request, status
from fastapi import Request, status, HTTPException
from fastapi.responses import JSONResponse
from fastapi.encoders import jsonable_encoder
from fastapi.exceptions import RequestValidationError
@@ -138,6 +138,23 @@ async def sqlalchemy_error_handler(
)
async def http_exception_handler(
request: Request,
exc: HTTPException,
) -> JSONResponse:
"""
Manejador para HTTPException de FastAPI
"""
return JSONResponse(
status_code=exc.status_code,
content={
"error": "HTTP_ERROR",
"message": exc.detail,
"status_code": exc.status_code,
},
)
async def general_exception_handler(
request: Request,
exc: Exception,
@@ -172,6 +189,7 @@ def register_exception_handlers(app) -> None:
app: Instancia de FastAPI
"""
app.add_exception_handler(BaseAPIException, base_exception_handler)
app.add_exception_handler(HTTPException, http_exception_handler)
app.add_exception_handler(RequestValidationError, validation_exception_handler)
app.add_exception_handler(IntegrityError, integrity_error_handler)
app.add_exception_handler(SQLAlchemyError, sqlalchemy_error_handler)

View File

@@ -1,7 +1,8 @@
import logging
import time
from typing import Callable
from fastapi import HTTPException, Request, Response
from fastapi import Request, Response
from fastapi.responses import JSONResponse
from starlette.middleware.base import BaseHTTPMiddleware
from .config import settings
from .database import CoreSessionLocal
@@ -34,9 +35,13 @@ class TenantMiddleware(BaseHTTPMiddleware):
# 4. Validación estricta de Token (solo para lo que no es público ni OPTIONS)
auth_header = request.headers.get("Authorization")
if not auth_header or not auth_header.startswith("Bearer "):
raise HTTPException(
status_code=401,
detail="Missing or invalid authorization header"
return JSONResponse(
status_code=401,
content={
"error": "HTTP_ERROR",
"message": "Missing or invalid authorization header",
"status_code": 401,
}
)
token = auth_header.split(" ")[1]
@@ -48,7 +53,14 @@ class TenantMiddleware(BaseHTTPMiddleware):
request.state.user_info = user_info
except Exception as e:
logger.error(f"❌ Tenant validation error: {str(e)}")
raise HTTPException(status_code=401, detail="Invalid authentication")
return JSONResponse(
status_code=401,
content={
"error": "HTTP_ERROR",
"message": "Invalid authentication",
"status_code": 401,
}
)
# 5. Continuar con la petición real
return await call_next(request)
@@ -104,19 +116,28 @@ class LicenseValidationMiddleware(BaseHTTPMiddleware):
license_info = license_service.validate_license(tenant_id)
if not license_info["is_valid"]:
raise HTTPException(
return JSONResponse(
status_code=402,
detail=f"License validation failed: {license_info['reason']}",
content={
"error": "HTTP_ERROR",
"message": f"License validation failed: {license_info['reason']}",
"status_code": 402,
}
)
# Agregar info de licencia al request state
request.state.license_info = license_info
except HTTPException:
raise
except Exception as e:
logger.error(f"License validation error: {str(e)}")
raise HTTPException(status_code=500, detail="License validation error")
return JSONResponse(
status_code=500,
content={
"error": "HTTP_ERROR",
"message": "License validation error",
"status_code": 500,
}
)
finally:
db.close()

View File

@@ -70,18 +70,6 @@ async def validation_exception_handler(request: Request, exc: RequestValidationE
)
# Add HTTP exception handler
@app.exception_handler(HTTPException)
async def http_exception_handler(request: Request, exc: HTTPException):
logger.error(
f"HTTP {exc.status_code} for {request.method} {request.url.path}: {exc.detail}"
)
return JSONResponse(
status_code=exc.status_code,
content={"detail": exc.detail},
)
def run_migrations():
subprocess.run(["alembic", "upgrade", "head"], check=True)