""" Centralized exception handling utilities for consistent error management across the application. """ from typing import Dict, Any, Optional, Type, Union, Callable from functools import wraps import logging from fastapi import HTTPException, status from sqlalchemy.exc import SQLAlchemyError, IntegrityError, DataError from pydantic import ValidationError import traceback logger = logging.getLogger(__name__) class DatabaseError(Exception): """Custom exception for database-related errors""" pass class BusinessLogicError(Exception): """Custom exception for business logic violations""" pass class SecurityError(Exception): """Custom exception for security-related errors""" pass class APIError(HTTPException): """Enhanced HTTP exception with additional context""" def __init__( self, status_code: int, detail: str, error_code: str = None, context: Dict[str, Any] = None ): super().__init__(status_code=status_code, detail=detail) self.error_code = error_code self.context = context or {} def handle_database_errors(func: Callable) -> Callable: """ Decorator to handle common database errors with consistent responses. """ @wraps(func) def wrapper(*args, **kwargs): try: return func(*args, **kwargs) except IntegrityError as e: logger.error(f"Database integrity error in {func.__name__}: {str(e)}") raise APIError( status_code=status.HTTP_400_BAD_REQUEST, detail="Data integrity constraint violation", error_code="INTEGRITY_ERROR", context={"function": func.__name__} ) except DataError as e: logger.error(f"Database data error in {func.__name__}: {str(e)}") raise APIError( status_code=status.HTTP_400_BAD_REQUEST, detail="Invalid data format or type", error_code="DATA_ERROR", context={"function": func.__name__} ) except SQLAlchemyError as e: logger.error(f"Database error in {func.__name__}: {str(e)}") raise APIError( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Database operation failed", error_code="DATABASE_ERROR", context={"function": func.__name__} ) except Exception as e: logger.error(f"Unexpected error in {func.__name__}: {str(e)}", exc_info=True) raise APIError( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="An unexpected error occurred", error_code="INTERNAL_ERROR", context={"function": func.__name__} ) return wrapper def handle_validation_errors(func: Callable) -> Callable: """ Decorator to handle validation errors with consistent responses. """ @wraps(func) def wrapper(*args, **kwargs): try: return func(*args, **kwargs) except ValidationError as e: logger.warning(f"Validation error in {func.__name__}: {str(e)}") raise APIError( status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail="Validation failed", error_code="VALIDATION_ERROR", context={ "function": func.__name__, "validation_errors": e.errors() } ) except ValueError as e: logger.warning(f"Value error in {func.__name__}: {str(e)}") raise APIError( status_code=status.HTTP_400_BAD_REQUEST, detail=str(e), error_code="VALUE_ERROR", context={"function": func.__name__} ) return wrapper def handle_security_errors(func: Callable) -> Callable: """ Decorator to handle security-related errors. """ @wraps(func) def wrapper(*args, **kwargs): try: return func(*args, **kwargs) except SecurityError as e: logger.warning(f"Security error in {func.__name__}: {str(e)}") raise APIError( status_code=status.HTTP_403_FORBIDDEN, detail="Access denied", error_code="SECURITY_ERROR", context={"function": func.__name__} ) except PermissionError as e: logger.warning(f"Permission error in {func.__name__}: {str(e)}") raise APIError( status_code=status.HTTP_403_FORBIDDEN, detail="Insufficient permissions", error_code="PERMISSION_ERROR", context={"function": func.__name__} ) return wrapper def safe_execute( operation: Callable, default_return: Any = None, log_errors: bool = True, raise_on_error: bool = False, error_message: str = "Operation failed" ) -> Any: """ Safely execute an operation with optional error handling. Args: operation: Function to execute default_return: Value to return if operation fails log_errors: Whether to log errors raise_on_error: Whether to re-raise exceptions error_message: Custom error message for logging Returns: Result of operation or default_return on failure """ try: return operation() except Exception as e: if log_errors: logger.error(f"{error_message}: {str(e)}", exc_info=True) if raise_on_error: raise return default_return def create_error_response( error: Exception, status_code: int = status.HTTP_500_INTERNAL_SERVER_ERROR, error_code: str = "INTERNAL_ERROR", include_traceback: bool = False ) -> Dict[str, Any]: """ Create a standardized error response. Args: error: The exception that occurred status_code: HTTP status code error_code: Application-specific error code include_traceback: Whether to include traceback (dev only) Returns: Standardized error response dictionary """ response = { "error": { "code": error_code, "message": str(error), "type": type(error).__name__ } } if include_traceback: response["error"]["traceback"] = traceback.format_exc() return response class ErrorContext: """Context manager for handling errors in a specific scope.""" def __init__( self, operation_name: str, default_return: Any = None, log_errors: bool = True, suppress_errors: bool = False ): self.operation_name = operation_name self.default_return = default_return self.log_errors = log_errors self.suppress_errors = suppress_errors self.error = None def __enter__(self): return self def __exit__(self, exc_type, exc_val, exc_tb): if exc_type is not None: self.error = exc_val if self.log_errors: logger.error( f"Error in {self.operation_name}: {str(exc_val)}", exc_info=True ) return self.suppress_errors return False def get_result(self, success_value: Any = None) -> Any: """Get the result based on whether an error occurred.""" if self.error is not None: return self.default_return return success_value