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:
152
backend/igny8_core/api/response.py
Normal file
152
backend/igny8_core/api/response.py
Normal file
@@ -0,0 +1,152 @@
|
||||
"""
|
||||
Unified API Response Helpers
|
||||
Provides consistent response format across all endpoints
|
||||
"""
|
||||
from rest_framework.response import Response
|
||||
from rest_framework import status
|
||||
import uuid
|
||||
|
||||
|
||||
def get_request_id(request):
|
||||
"""Get request ID from request object (set by middleware) or headers, or generate new one"""
|
||||
if not request:
|
||||
return None
|
||||
|
||||
# First check if middleware set request_id on request object
|
||||
if hasattr(request, 'request_id') and request.request_id:
|
||||
return request.request_id
|
||||
|
||||
# Fallback to headers
|
||||
if hasattr(request, 'META'):
|
||||
request_id = request.META.get('HTTP_X_REQUEST_ID') or request.META.get('X-Request-ID')
|
||||
if request_id:
|
||||
return request_id
|
||||
|
||||
# Generate new request ID if none found
|
||||
return str(uuid.uuid4())
|
||||
|
||||
|
||||
def success_response(data=None, message=None, status_code=status.HTTP_200_OK, request=None):
|
||||
"""
|
||||
Create a standardized success response
|
||||
|
||||
Args:
|
||||
data: Response data (dict, list, or None)
|
||||
message: Optional success message
|
||||
status_code: HTTP status code (default: 200)
|
||||
request: Request object (optional, for request_id)
|
||||
|
||||
Returns:
|
||||
Response object with unified format
|
||||
"""
|
||||
response_data = {
|
||||
'success': True,
|
||||
}
|
||||
|
||||
if data is not None:
|
||||
response_data['data'] = data
|
||||
|
||||
if message:
|
||||
response_data['message'] = message
|
||||
|
||||
# Add request_id if request is provided
|
||||
if request:
|
||||
response_data['request_id'] = get_request_id(request)
|
||||
|
||||
return Response(response_data, status=status_code)
|
||||
|
||||
|
||||
def error_response(error=None, errors=None, status_code=status.HTTP_400_BAD_REQUEST, request=None, debug_info=None):
|
||||
"""
|
||||
Create a standardized error response
|
||||
|
||||
Args:
|
||||
error: Top-level error message
|
||||
errors: Field-specific errors (dict of field -> list of errors)
|
||||
status_code: HTTP status code (default: 400)
|
||||
request: Request object (optional, for request_id)
|
||||
debug_info: Debug information (only in DEBUG mode)
|
||||
|
||||
Returns:
|
||||
Response object with unified error format
|
||||
"""
|
||||
response_data = {
|
||||
'success': False,
|
||||
}
|
||||
|
||||
if error:
|
||||
response_data['error'] = error
|
||||
elif status_code == status.HTTP_400_BAD_REQUEST:
|
||||
response_data['error'] = 'Bad request'
|
||||
elif status_code == status.HTTP_401_UNAUTHORIZED:
|
||||
response_data['error'] = 'Authentication required'
|
||||
elif status_code == status.HTTP_403_FORBIDDEN:
|
||||
response_data['error'] = 'Permission denied'
|
||||
elif status_code == status.HTTP_404_NOT_FOUND:
|
||||
response_data['error'] = 'Resource not found'
|
||||
elif status_code == status.HTTP_409_CONFLICT:
|
||||
response_data['error'] = 'Conflict'
|
||||
elif status_code == status.HTTP_422_UNPROCESSABLE_ENTITY:
|
||||
response_data['error'] = 'Validation failed'
|
||||
elif status_code == status.HTTP_429_TOO_MANY_REQUESTS:
|
||||
response_data['error'] = 'Rate limit exceeded'
|
||||
elif status_code >= 500:
|
||||
response_data['error'] = 'Internal server error'
|
||||
else:
|
||||
response_data['error'] = 'An error occurred'
|
||||
|
||||
if errors:
|
||||
response_data['errors'] = errors
|
||||
|
||||
# Add request_id if request is provided
|
||||
if request:
|
||||
response_data['request_id'] = get_request_id(request)
|
||||
|
||||
# Add debug info in DEBUG mode
|
||||
if debug_info:
|
||||
response_data['debug'] = debug_info
|
||||
|
||||
return Response(response_data, status=status_code)
|
||||
|
||||
|
||||
def paginated_response(paginated_data, message=None, request=None):
|
||||
"""
|
||||
Create a standardized paginated response
|
||||
|
||||
Args:
|
||||
paginated_data: Paginated data dict from DRF paginator (contains count, next, previous, results)
|
||||
message: Optional success message
|
||||
request: Request object (optional, for request_id)
|
||||
|
||||
Returns:
|
||||
Response object with unified paginated format
|
||||
"""
|
||||
response_data = {
|
||||
'success': True,
|
||||
}
|
||||
|
||||
# Copy pagination fields from DRF paginator
|
||||
if isinstance(paginated_data, dict):
|
||||
response_data.update({
|
||||
'count': paginated_data.get('count', 0),
|
||||
'next': paginated_data.get('next'),
|
||||
'previous': paginated_data.get('previous'),
|
||||
'results': paginated_data.get('results', [])
|
||||
})
|
||||
else:
|
||||
# Fallback if paginated_data is not a dict
|
||||
response_data['count'] = 0
|
||||
response_data['next'] = None
|
||||
response_data['previous'] = None
|
||||
response_data['results'] = []
|
||||
|
||||
if message:
|
||||
response_data['message'] = message
|
||||
|
||||
# Add request_id if request is provided
|
||||
if request:
|
||||
response_data['request_id'] = get_request_id(request)
|
||||
|
||||
return Response(response_data, status=status.HTTP_200_OK)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user