Enhance API response handling and implement unified API standard across multiple modules. Added feature flags for unified exception handling and debug throttling in settings. Updated pagination and response formats in various viewsets to align with the new standard. Improved error handling and response validation in frontend components for better user feedback.
This commit is contained in:
176
backend/igny8_core/api/exception_handlers.py
Normal file
176
backend/igny8_core/api/exception_handlers.py
Normal file
@@ -0,0 +1,176 @@
|
||||
"""
|
||||
Centralized Exception Handler
|
||||
Wraps all exceptions in unified format
|
||||
"""
|
||||
import logging
|
||||
from rest_framework.views import exception_handler
|
||||
from rest_framework import status
|
||||
from django.conf import settings
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.db import IntegrityError
|
||||
from .response import get_request_id, error_response
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def custom_exception_handler(exc, context):
|
||||
"""
|
||||
Custom exception handler that wraps all errors in unified format
|
||||
|
||||
Args:
|
||||
exc: The exception that was raised
|
||||
context: Dictionary containing request, view, args, kwargs
|
||||
|
||||
Returns:
|
||||
Response object with unified error format
|
||||
"""
|
||||
# Get request from context
|
||||
request = context.get('request')
|
||||
|
||||
# Get request ID
|
||||
request_id = get_request_id(request) if request else None
|
||||
|
||||
# Call DRF's default exception handler first
|
||||
response = exception_handler(exc, context)
|
||||
|
||||
# If DRF handled it, wrap it in unified format
|
||||
if response is not None:
|
||||
# Extract error details from DRF response
|
||||
error_message = None
|
||||
errors = None
|
||||
status_code = response.status_code
|
||||
|
||||
# Try to extract error message from response data
|
||||
if hasattr(response, 'data'):
|
||||
if isinstance(response.data, dict):
|
||||
# DRF validation errors
|
||||
if 'detail' in response.data:
|
||||
error_message = str(response.data['detail'])
|
||||
elif 'non_field_errors' in response.data:
|
||||
error_message = str(response.data['non_field_errors'][0]) if response.data['non_field_errors'] else None
|
||||
errors = response.data
|
||||
else:
|
||||
# Field-specific errors
|
||||
errors = response.data
|
||||
# Create top-level error message
|
||||
if errors:
|
||||
first_error = list(errors.values())[0] if errors else None
|
||||
if first_error and isinstance(first_error, list) and len(first_error) > 0:
|
||||
error_message = str(first_error[0])
|
||||
elif first_error:
|
||||
error_message = str(first_error)
|
||||
else:
|
||||
error_message = 'Validation failed'
|
||||
elif isinstance(response.data, list):
|
||||
# List of errors
|
||||
error_message = str(response.data[0]) if response.data else 'Validation failed'
|
||||
else:
|
||||
error_message = str(response.data)
|
||||
|
||||
# Map status codes to appropriate error messages
|
||||
if not error_message:
|
||||
if status_code == status.HTTP_400_BAD_REQUEST:
|
||||
error_message = 'Bad request'
|
||||
elif status_code == status.HTTP_401_UNAUTHORIZED:
|
||||
error_message = 'Authentication required'
|
||||
elif status_code == status.HTTP_403_FORBIDDEN:
|
||||
error_message = 'Permission denied'
|
||||
elif status_code == status.HTTP_404_NOT_FOUND:
|
||||
error_message = 'Resource not found'
|
||||
elif status_code == status.HTTP_409_CONFLICT:
|
||||
error_message = 'Conflict'
|
||||
elif status_code == status.HTTP_422_UNPROCESSABLE_ENTITY:
|
||||
error_message = 'Validation failed'
|
||||
elif status_code == status.HTTP_429_TOO_MANY_REQUESTS:
|
||||
error_message = 'Rate limit exceeded'
|
||||
elif status_code >= 500:
|
||||
error_message = 'Internal server error'
|
||||
else:
|
||||
error_message = 'An error occurred'
|
||||
|
||||
# Prepare debug info (only in DEBUG mode)
|
||||
debug_info = None
|
||||
if settings.DEBUG:
|
||||
debug_info = {
|
||||
'exception_type': type(exc).__name__,
|
||||
'exception_message': str(exc),
|
||||
'view': context.get('view').__class__.__name__ if context.get('view') else None,
|
||||
'path': request.path if request else None,
|
||||
'method': request.method if request else None,
|
||||
}
|
||||
# Include traceback in debug mode
|
||||
import traceback
|
||||
debug_info['traceback'] = traceback.format_exc()
|
||||
|
||||
# Log the error
|
||||
if status_code >= 500:
|
||||
logger.error(
|
||||
f"Server error: {error_message}",
|
||||
extra={
|
||||
'request_id': request_id,
|
||||
'endpoint': request.path if request else None,
|
||||
'method': request.method if request else None,
|
||||
'user_id': request.user.id if request and request.user and request.user.is_authenticated else None,
|
||||
'account_id': request.account.id if request and hasattr(request, 'account') and request.account else None,
|
||||
'status_code': status_code,
|
||||
'exception_type': type(exc).__name__,
|
||||
},
|
||||
exc_info=True
|
||||
)
|
||||
elif status_code >= 400:
|
||||
logger.warning(
|
||||
f"Client error: {error_message}",
|
||||
extra={
|
||||
'request_id': request_id,
|
||||
'endpoint': request.path if request else None,
|
||||
'method': request.method if request else None,
|
||||
'user_id': request.user.id if request and request.user and request.user.is_authenticated else None,
|
||||
'account_id': request.account.id if request and hasattr(request, 'account') and request.account else None,
|
||||
'status_code': status_code,
|
||||
}
|
||||
)
|
||||
|
||||
# Return unified error response
|
||||
return error_response(
|
||||
error=error_message,
|
||||
errors=errors,
|
||||
status_code=status_code,
|
||||
request=request,
|
||||
debug_info=debug_info
|
||||
)
|
||||
|
||||
# If DRF didn't handle it, it's an unhandled exception
|
||||
# Log it and return unified error response
|
||||
logger.error(
|
||||
f"Unhandled exception: {type(exc).__name__}: {str(exc)}",
|
||||
extra={
|
||||
'request_id': request_id,
|
||||
'endpoint': request.path if request else None,
|
||||
'method': request.method if request else None,
|
||||
'user_id': request.user.id if request and request.user and request.user.is_authenticated else None,
|
||||
'account_id': request.account.id if request and hasattr(request, 'account') and request.account else None,
|
||||
},
|
||||
exc_info=True
|
||||
)
|
||||
|
||||
# Prepare debug info
|
||||
debug_info = None
|
||||
if settings.DEBUG:
|
||||
import traceback
|
||||
debug_info = {
|
||||
'exception_type': type(exc).__name__,
|
||||
'exception_message': str(exc),
|
||||
'view': context.get('view').__class__.__name__ if context.get('view') else None,
|
||||
'path': request.path if request else None,
|
||||
'method': request.method if request else None,
|
||||
'traceback': traceback.format_exc()
|
||||
}
|
||||
|
||||
return error_response(
|
||||
error='Internal server error',
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
request=request,
|
||||
debug_info=debug_info
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user