""" Standardized API response helpers for ThrillWiki. This module provides consistent response formatting across all API endpoints: Success responses: - Action completed: {"detail": "Success message"} - With data: {"detail": "...", "data": {...}} Error responses: - Validation: {"field": ["error"]} (DRF default) - Application: {"detail": "Error message", "code": "ERROR_CODE"} Usage: from apps.api.v1.responses import success_response, error_response # Success return success_response("Avatar saved successfully") # Error return error_response("User not found", code="NOT_FOUND", status_code=404) """ from rest_framework import status from rest_framework.response import Response # Standard error codes for machine-readable error handling class ErrorCodes: """Standard error codes for API responses.""" # Authentication / Authorization UNAUTHORIZED = "UNAUTHORIZED" FORBIDDEN = "FORBIDDEN" INVALID_CREDENTIALS = "INVALID_CREDENTIALS" TOKEN_EXPIRED = "TOKEN_EXPIRED" TOKEN_INVALID = "TOKEN_INVALID" # Resource errors NOT_FOUND = "NOT_FOUND" ALREADY_EXISTS = "ALREADY_EXISTS" CONFLICT = "CONFLICT" # Validation errors VALIDATION_ERROR = "VALIDATION_ERROR" INVALID_INPUT = "INVALID_INPUT" MISSING_FIELD = "MISSING_FIELD" # Operation errors OPERATION_FAILED = "OPERATION_FAILED" PERMISSION_DENIED = "PERMISSION_DENIED" RATE_LIMITED = "RATE_LIMITED" # User-specific errors USER_NOT_FOUND = "USER_NOT_FOUND" USER_INACTIVE = "USER_INACTIVE" USER_BANNED = "USER_BANNED" CANNOT_DELETE_SUPERUSER = "CANNOT_DELETE_SUPERUSER" CANNOT_DELETE_SELF = "CANNOT_DELETE_SELF" # Verification errors VERIFICATION_EXPIRED = "VERIFICATION_EXPIRED" VERIFICATION_INVALID = "VERIFICATION_INVALID" ALREADY_VERIFIED = "ALREADY_VERIFIED" # External service errors EXTERNAL_SERVICE_ERROR = "EXTERNAL_SERVICE_ERROR" CLOUDFLARE_ERROR = "CLOUDFLARE_ERROR" def success_response( detail: str, data: dict | None = None, status_code: int = status.HTTP_200_OK, ) -> Response: """ Create a standardized success response. Args: detail: Human-readable success message data: Optional additional data to include status_code: HTTP status code (default 200) Returns: DRF Response object Example: return success_response("Avatar saved successfully") return success_response("User created", data={"id": user.id}, status_code=201) """ response_data = {"detail": detail} if data: response_data.update(data) return Response(response_data, status=status_code) def error_response( detail: str, code: str | None = None, status_code: int = status.HTTP_400_BAD_REQUEST, extra: dict | None = None, ) -> Response: """ Create a standardized error response. Args: detail: Human-readable error message code: Machine-readable error code from ErrorCodes status_code: HTTP status code (default 400) extra: Optional additional data to include Returns: DRF Response object Example: return error_response("User not found", code=ErrorCodes.NOT_FOUND, status_code=404) return error_response("Invalid input", code=ErrorCodes.VALIDATION_ERROR) """ response_data = {"detail": detail} if code: response_data["code"] = code if extra: response_data.update(extra) return Response(response_data, status=status_code) def created_response(detail: str, data: dict | None = None) -> Response: """Convenience wrapper for 201 Created responses.""" return success_response(detail, data=data, status_code=status.HTTP_201_CREATED) def not_found_response(detail: str = "Resource not found") -> Response: """Convenience wrapper for 404 Not Found responses.""" return error_response( detail, code=ErrorCodes.NOT_FOUND, status_code=status.HTTP_404_NOT_FOUND, ) def forbidden_response(detail: str = "Permission denied") -> Response: """Convenience wrapper for 403 Forbidden responses.""" return error_response( detail, code=ErrorCodes.FORBIDDEN, status_code=status.HTTP_403_FORBIDDEN, ) def unauthorized_response(detail: str = "Authentication required") -> Response: """Convenience wrapper for 401 Unauthorized responses.""" return error_response( detail, code=ErrorCodes.UNAUTHORIZED, status_code=status.HTTP_401_UNAUTHORIZED, ) __all__ = [ "ErrorCodes", "success_response", "error_response", "created_response", "not_found_response", "forbidden_response", "unauthorized_response", ]