from fastapi import Request, status, HTTPException from fastapi.responses import JSONResponse from fastapi.exceptions import RequestValidationError from sqlalchemy.exc import IntegrityError from jose.exceptions import JWTError import os import traceback async def validation_exception_handler(request: Request, exc: RequestValidationError): """ Handle validation errors """ 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"] }) # Get the first error message for the main message first_error = errors[0]["message"] if errors else "Validation error" return JSONResponse( status_code=status.HTTP_400_BAD_REQUEST, content={ "status": "error", "message": first_error, "errors": errors } ) async def integrity_error_handler(request: Request, exc: IntegrityError): """ Handle database integrity errors (unique constraints, etc.) """ error_msg = str(exc.orig) if hasattr(exc, 'orig') else str(exc) # Check for duplicate entry if "Duplicate entry" in error_msg or "UNIQUE constraint" in error_msg: return JSONResponse( status_code=status.HTTP_400_BAD_REQUEST, content={ "status": "error", "message": "Duplicate entry", "errors": [{"message": "This record already exists"}] } ) return JSONResponse( status_code=status.HTTP_400_BAD_REQUEST, content={ "status": "error", "message": "Database integrity error" } ) async def jwt_error_handler(request: Request, exc: JWTError): """ Handle JWT errors """ return JSONResponse( status_code=status.HTTP_401_UNAUTHORIZED, content={ "status": "error", "message": "Invalid token" } ) async def http_exception_handler(request: Request, exc: HTTPException): """ Handle HTTPException errors """ # If detail is already a dict with status/message, return it directly if isinstance(exc.detail, dict): return JSONResponse( status_code=exc.status_code, content=exc.detail ) # Otherwise format as standard error response return JSONResponse( status_code=exc.status_code, content={ "status": "error", "message": str(exc.detail) if exc.detail else "An error occurred" } ) async def general_exception_handler(request: Request, exc: Exception): """ Handle all other exceptions """ # Log error print(f"Error: {exc}") if os.getenv("NODE_ENV") == "development": traceback.print_exc() # Handle HTTPException with dict detail if isinstance(exc, Exception) and hasattr(exc, "status_code"): status_code = exc.status_code if hasattr(exc, "detail"): detail = exc.detail if isinstance(detail, dict): # If detail is already a dict with status/message, return it directly return JSONResponse(status_code=status_code, content=detail) message = str(detail) if detail else "An error occurred" else: message = str(exc) if str(exc) else "Internal server error" else: status_code = status.HTTP_500_INTERNAL_SERVER_ERROR message = str(exc) if str(exc) else "Internal server error" return JSONResponse( status_code=status_code, content={ "status": "error", "message": message, **({"stack": traceback.format_exc()} if os.getenv("NODE_ENV") == "development" else {}) } )