""" Manejadores globales de excepciones para FastAPI """ import logging from typing import Any, Dict from fastapi import Request, status, HTTPException from fastapi.responses import JSONResponse from fastapi.encoders import jsonable_encoder from fastapi.exceptions import RequestValidationError from sqlalchemy.exc import IntegrityError, SQLAlchemyError from .exceptions import BaseAPIException logger = logging.getLogger(__name__) async def base_exception_handler( request: Request, exc: BaseAPIException, ) -> JSONResponse: """ Manejador para todas las excepciones personalizadas de la API """ logger.warning( f"API Exception: {exc.error_code} - {exc.message}", extra={ "path": request.url.path, "method": request.method, "status_code": exc.status_code, }, ) # Log detailed errors if they exist if hasattr(exc, "errors") and exc.errors: logger.warning(f"Validation errors details: {exc.errors}") return JSONResponse( status_code=exc.status_code, content=jsonable_encoder(exc.to_dict()), ) async def validation_exception_handler( request: Request, exc: RequestValidationError, ) -> JSONResponse: """ Manejador para errores de validación de Pydantic/FastAPI """ errors = [] for error in exc.errors(): field = ".".join(str(loc) for loc in error["loc"] if loc != "body") errors.append( { "field": field, "message": error["msg"], "type": error["type"], } ) logger.warning( f"Validation Error en {request.url.path}", extra={"errors": errors}, ) return JSONResponse( status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, content={ "error": "VALIDATION_ERROR", "message": "Error de validación en los datos recibidos", "status_code": status.HTTP_422_UNPROCESSABLE_ENTITY, "errors": errors, }, ) async def integrity_error_handler( request: Request, exc: IntegrityError, ) -> JSONResponse: """ Manejador para errores de integridad de la base de datos """ logger.error( f"Database Integrity Error: {str(exc.orig)}", extra={ "path": request.url.path, "method": request.method, }, ) orig_msg = str(exc.orig).lower() # Check for unique/duplicate key violations (English and Spanish) if any(kw in orig_msg for kw in ["unique constraint", "duplicate key", "duplicada", "unicidad", "ya existe"]): error_message = "El registro ya existe. Verifica los campos únicos (Año, Aduana, Patente, Número, etc.)." # Check for foreign key violations (English and Spanish) elif any(kw in orig_msg for kw in ["foreign key", "foránea", "referencia"]): error_message = "Referencia inválida a otro registro. Verifica las categorías y catálogos seleccionados." # Check for not null violations (English and Spanish) elif any(kw in orig_msg for kw in ["not null", "no nulo", "valor nulo"]): error_message = "Falta un campo requerido. Asegúrate de llenar todos los datos obligatorios." else: error_message = "Error de integridad en la base de datos" return JSONResponse( status_code=status.HTTP_409_CONFLICT, content={ "error": "DATABASE_INTEGRITY_ERROR", "message": error_message, "status_code": status.HTTP_409_CONFLICT, }, ) async def sqlalchemy_error_handler( request: Request, exc: SQLAlchemyError, ) -> JSONResponse: """ Manejador para errores generales de SQLAlchemy """ logger.error( f"Database Error: {str(exc)}", extra={ "path": request.url.path, "method": request.method, }, exc_info=True, ) return JSONResponse( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, content={ "error": "DATABASE_ERROR", "message": "Error en la operación de base de datos", "status_code": status.HTTP_500_INTERNAL_SERVER_ERROR, }, ) 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, ) -> JSONResponse: """ Manejador para excepciones no capturadas """ logger.error( f"Unhandled Exception: {str(exc)}", extra={ "path": request.url.path, "method": request.method, }, exc_info=True, ) return JSONResponse( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, content={ "error": "INTERNAL_SERVER_ERROR", "message": "Error interno del servidor", "status_code": status.HTTP_500_INTERNAL_SERVER_ERROR, }, ) def register_exception_handlers(app) -> None: """ Registra todos los manejadores de excepciones en la aplicación FastAPI Args: 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) app.add_exception_handler(Exception, general_exception_handler)