Files
thrillwiki_django_no_react/backend/apps/api/v1/responses.py

168 lines
4.6 KiB
Python

"""
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",
]