feat: enhance error handling with custom HTTP exception responses and middleware improvements
This commit is contained in:
@@ -18,5 +18,10 @@ class UserContextMiddleware(BaseHTTPMiddleware):
|
|||||||
# Log error or ignore
|
# Log error or ignore
|
||||||
pass
|
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
|
return response
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ Manejadores globales de excepciones para FastAPI
|
|||||||
import logging
|
import logging
|
||||||
from typing import Any, Dict
|
from typing import Any, Dict
|
||||||
|
|
||||||
from fastapi import Request, status
|
from fastapi import Request, status, HTTPException
|
||||||
from fastapi.responses import JSONResponse
|
from fastapi.responses import JSONResponse
|
||||||
from fastapi.encoders import jsonable_encoder
|
from fastapi.encoders import jsonable_encoder
|
||||||
from fastapi.exceptions import RequestValidationError
|
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(
|
async def general_exception_handler(
|
||||||
request: Request,
|
request: Request,
|
||||||
exc: Exception,
|
exc: Exception,
|
||||||
@@ -172,6 +189,7 @@ def register_exception_handlers(app) -> None:
|
|||||||
app: Instancia de FastAPI
|
app: Instancia de FastAPI
|
||||||
"""
|
"""
|
||||||
app.add_exception_handler(BaseAPIException, base_exception_handler)
|
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(RequestValidationError, validation_exception_handler)
|
||||||
app.add_exception_handler(IntegrityError, integrity_error_handler)
|
app.add_exception_handler(IntegrityError, integrity_error_handler)
|
||||||
app.add_exception_handler(SQLAlchemyError, sqlalchemy_error_handler)
|
app.add_exception_handler(SQLAlchemyError, sqlalchemy_error_handler)
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import logging
|
import logging
|
||||||
import time
|
import time
|
||||||
from typing import Callable
|
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 starlette.middleware.base import BaseHTTPMiddleware
|
||||||
from .config import settings
|
from .config import settings
|
||||||
from .database import CoreSessionLocal
|
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)
|
# 4. Validación estricta de Token (solo para lo que no es público ni OPTIONS)
|
||||||
auth_header = request.headers.get("Authorization")
|
auth_header = request.headers.get("Authorization")
|
||||||
if not auth_header or not auth_header.startswith("Bearer "):
|
if not auth_header or not auth_header.startswith("Bearer "):
|
||||||
raise HTTPException(
|
return JSONResponse(
|
||||||
status_code=401,
|
status_code=401,
|
||||||
detail="Missing or invalid authorization header"
|
content={
|
||||||
|
"error": "HTTP_ERROR",
|
||||||
|
"message": "Missing or invalid authorization header",
|
||||||
|
"status_code": 401,
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
token = auth_header.split(" ")[1]
|
token = auth_header.split(" ")[1]
|
||||||
@@ -48,7 +53,14 @@ class TenantMiddleware(BaseHTTPMiddleware):
|
|||||||
request.state.user_info = user_info
|
request.state.user_info = user_info
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"❌ Tenant validation error: {str(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
|
# 5. Continuar con la petición real
|
||||||
return await call_next(request)
|
return await call_next(request)
|
||||||
@@ -104,19 +116,28 @@ class LicenseValidationMiddleware(BaseHTTPMiddleware):
|
|||||||
license_info = license_service.validate_license(tenant_id)
|
license_info = license_service.validate_license(tenant_id)
|
||||||
|
|
||||||
if not license_info["is_valid"]:
|
if not license_info["is_valid"]:
|
||||||
raise HTTPException(
|
return JSONResponse(
|
||||||
status_code=402,
|
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
|
# Agregar info de licencia al request state
|
||||||
request.state.license_info = license_info
|
request.state.license_info = license_info
|
||||||
|
|
||||||
except HTTPException:
|
|
||||||
raise
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"License validation error: {str(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:
|
finally:
|
||||||
db.close()
|
db.close()
|
||||||
|
|
||||||
|
|||||||
@@ -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():
|
def run_migrations():
|
||||||
subprocess.run(["alembic", "upgrade", "head"], check=True)
|
subprocess.run(["alembic", "upgrade", "head"], check=True)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user