File size: 8,320 Bytes
2ed8996
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
"""
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
        }
    }