""" Global Error Handler Middleware for AegisLM Backend. Provides centralized error handling, logging, and consistent error responses across all API endpoints. """ import logging import traceback from datetime import datetime from typing import Union from fastapi import Request, HTTPException from fastapi.responses import JSONResponse from starlette.middleware.base import BaseHTTPMiddleware from schemas.response_schema import ErrorResponse logger = logging.getLogger(__name__) class GlobalErrorHandler(BaseHTTPMiddleware): """ Global error handler middleware. Catches all exceptions and returns consistent error responses with proper logging and error tracking. """ async def dispatch(self, request: Request, call_next): try: response = await call_next(request) return response except HTTPException as http_exc: # Handle HTTP exceptions (validation, auth, etc.) return self._handle_http_exception(http_exc, request) except Exception as exc: # Handle unexpected exceptions return self._handle_unexpected_exception(exc, request) def _handle_http_exception(self, exc: HTTPException, request: Request) -> JSONResponse: """Handle known HTTP exceptions.""" logger.warning( f"HTTP Exception: {exc.status_code} - {exc.detail} - " f"Path: {request.url.path} - Method: {request.method}" ) error_response = ErrorResponse( success=False, error_code=f"HTTP_{exc.status_code}", message=str(exc.detail), error_details={ "status_code": exc.status_code, "path": request.url.path, "method": request.method } ) return JSONResponse( status_code=exc.status_code, content=error_response.dict() ) def _handle_unexpected_exception(self, exc: Exception, request: Request) -> JSONResponse: """Handle unexpected exceptions.""" error_id = f"ERR_{id(exc)}" # Log the full error for debugging logger.error( f"Unexpected Exception [{error_id}]: {str(exc)} - " f"Path: {request.url.path} - Method: {request.method}\n" f"Traceback: {traceback.format_exc()}" ) # Return generic error to client error_response = ErrorResponse( success=False, error_code="INTERNAL_SERVER_ERROR", message="An internal server error occurred. Please try again later.", error_details={ "error_id": error_id, "path": request.url.path, "method": request.method }, timestamp=datetime.utcnow().isoformat() ) return JSONResponse( status_code=500, content=error_response.dict() ) def general_exception_handler(exc: Exception, request: Request) -> JSONResponse: """Handle all other exceptions.""" logger.error(f"General Exception: {str(exc)}") error_response = ErrorResponse( success=False, error_code="INTERNAL_SERVER_ERROR", message="An internal server error occurred. Please try again later.", error_details={ "error_type": type(exc).__name__, "path": request.url.path, "method": request.method }, timestamp=datetime.utcnow().isoformat() ) return JSONResponse( status_code=500, content=error_response.dict() ) def http_exception_handler(exc: HTTPException, request: Request) -> JSONResponse: """Handle HTTP exceptions.""" logger.warning(f"HTTP Exception: {exc.status_code} - {exc.detail}") error_response = ErrorResponse( success=False, error_code="HTTP_ERROR", message=str(exc.detail), error_details={ "status_code": exc.status_code, "path": request.url.path, "method": request.method }, timestamp=datetime.utcnow().isoformat() ) return JSONResponse( status_code=exc.status_code, content=error_response.dict() ) class ValidationErrorHandler: """ Specialized handler for validation errors. """ @staticmethod def format_validation_error(validation_error) -> dict: """Format validation errors for consistent response.""" errors = [] if hasattr(validation_error, 'errors'): for error in validation_error.errors(): field = '.'.join(str(x) for x in error['loc']) errors.append({ 'field': field, 'message': error['msg'], 'value': error.get('input') }) return { 'success': False, 'error_code': 'VALIDATION_ERROR', 'message': 'Invalid input data', 'error_details': { 'validation_errors': errors } } class DatabaseErrorHandler: """ Specialized handler for database errors. """ @staticmethod def handle_database_error(exc: Exception, operation: str) -> dict: """Handle database-related errors.""" logger.error(f"Database Error during {operation}: {str(exc)}") error_type = "DATABASE_CONNECTION_ERROR" if "connection" in str(exc).lower() else "DATABASE_OPERATION_ERROR" return { 'success': False, 'error_code': error_type, 'message': 'Database operation failed. Please try again later.', 'error_details': { 'operation': operation, 'error_type': type(exc).__name__ } } class AuthenticationErrorHandler: """ Specialized handler for authentication errors. """ @staticmethod def handle_auth_error(exc: Exception) -> dict: """Handle authentication-related errors.""" logger.warning(f"Authentication Error: {str(exc)}") return { 'success': False, 'error_code': 'AUTHENTICATION_ERROR', 'message': 'Authentication failed. Please check your credentials.', 'error_details': { 'error_type': type(exc).__name__ } } class RateLimitErrorHandler: """ Specialized handler for rate limiting errors. """ @staticmethod def handle_rate_limit_error(exc: Exception, retry_after: int = None) -> dict: """Handle rate limiting errors.""" logger.info(f"Rate Limit Error: {str(exc)}") response = { 'success': False, 'error_code': 'RATE_LIMIT_EXCEEDED', 'message': 'Rate limit exceeded. Please try again later.', 'error_details': { 'error_type': type(exc).__name__ } } if retry_after: response['error_details']['retry_after'] = retry_after return response # Utility functions for common error scenarios def create_not_found_error(resource: str, identifier: Union[str, int]) -> dict: """Create a standardized not found error.""" return { 'success': False, 'error_code': 'RESOURCE_NOT_FOUND', 'message': f'{resource} not found: {identifier}', 'error_details': { 'resource': resource, 'identifier': str(identifier) } } def create_permission_error(action: str, resource: str) -> dict: """Create a standardized permission error.""" return { 'success': False, 'error_code': 'PERMISSION_DENIED', 'message': f'Permission denied for {action} on {resource}', 'error_details': { 'action': action, 'resource': resource } } def create_business_rule_error(rule: str, message: str) -> dict: """Create a standardized business rule violation error.""" return { 'success': False, 'error_code': 'BUSINESS_RULE_VIOLATION', 'message': message, 'error_details': { 'rule': rule } }