From e6ef5c9094ce39bafcff2641db6b5cf319372a4c Mon Sep 17 00:00:00 2001 From: acazares Date: Mon, 16 Feb 2026 13:18:42 -0600 Subject: [PATCH] feat: enhance error handling with custom HTTP exception responses and middleware improvements --- .../v1/modules/a76/audit_log/middleware.py | 7 +++- backend/core/error_handlers.py | 20 ++++++++- backend/core/middleware.py | 41 ++++++++++++++----- backend/main.py | 12 ------ 4 files changed, 56 insertions(+), 24 deletions(-) diff --git a/backend/api/v1/modules/a76/audit_log/middleware.py b/backend/api/v1/modules/a76/audit_log/middleware.py index fe2c111a..0d16a79b 100644 --- a/backend/api/v1/modules/a76/audit_log/middleware.py +++ b/backend/api/v1/modules/a76/audit_log/middleware.py @@ -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 diff --git a/backend/core/error_handlers.py b/backend/core/error_handlers.py index e1f7783a..833d54b8 100644 --- a/backend/core/error_handlers.py +++ b/backend/core/error_handlers.py @@ -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) diff --git a/backend/core/middleware.py b/backend/core/middleware.py index d7da611b..d4c9c594 100644 --- a/backend/core/middleware.py +++ b/backend/core/middleware.py @@ -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() diff --git a/backend/main.py b/backend/main.py index ae673fbf..ba49f3c3 100644 --- a/backend/main.py +++ b/backend/main.py @@ -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)