""" Unified API Response Helpers Provides consistent response format across all endpoints """ from rest_framework.response import Response from rest_framework import status import uuid from typing import Any from django.http import HttpRequest 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, } # Backwards compatibility: some callers used positional args in the order # (error, status_code, request) which maps to (error, errors, status_code=request) # causing `status_code` to be a Request object and raising TypeError. # Detect this misuse and normalize arguments: try: if request is None and status_code is not None: # If status_code appears to be a Request object, shift arguments if isinstance(status_code, HttpRequest) or hasattr(status_code, 'META'): # original call looked like: error_response(msg, status.HTTP_400_BAD_REQUEST, request) # which resulted in: errors = status.HTTP_400..., status_code = request request = status_code # If `errors` holds an int-like HTTP status, use it as status_code if isinstance(errors, int): status_code = errors errors = None else: # fallback to default 400 status_code = status.HTTP_400_BAD_REQUEST except Exception: # Defensive: if introspection fails, continue with provided args pass 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)