Files
Code_of_Conquest/api/app/utils/response.py
2025-11-24 23:10:55 -06:00

338 lines
7.5 KiB
Python

"""
API response wrapper for Code of Conquest.
Provides standardized JSON response format for all API endpoints.
"""
from datetime import datetime
from typing import Any, Dict, Optional
from flask import jsonify, Response
from app.config import get_config
def api_response(
result: Any = None,
status: int = 200,
error: Optional[Dict[str, Any]] = None,
meta: Optional[Dict[str, Any]] = None,
request_id: Optional[str] = None
) -> Response:
"""
Create a standardized API response.
Args:
result: The response data (or None if error)
status: HTTP status code
error: Error information (dict with 'code', 'message', 'details')
meta: Metadata (pagination, etc.)
request_id: Optional request ID for tracking
Returns:
Response: Flask JSON response
Example:
>>> return api_response(
... result={"user_id": "123"},
... status=200
... )
>>> return api_response(
... error={
... "code": "INVALID_INPUT",
... "message": "Email is required",
... "details": {"field": "email"}
... },
... status=400
... )
"""
config = get_config()
response_data = {
"app": config.app.name,
"version": config.app.version,
"status": status,
"timestamp": datetime.utcnow().isoformat() + "Z",
"request_id": request_id,
"result": result,
"error": error,
"meta": meta
}
return jsonify(response_data), status
def success_response(
result: Any = None,
status: int = 200,
meta: Optional[Dict[str, Any]] = None,
request_id: Optional[str] = None
) -> Response:
"""
Create a success response.
Args:
result: The response data
status: HTTP status code (default 200)
meta: Optional metadata
request_id: Optional request ID
Returns:
Response: Flask JSON response
Example:
>>> return success_response({"character_id": "123"})
"""
return api_response(
result=result,
status=status,
meta=meta,
request_id=request_id
)
def error_response(
code: str,
message: str,
status: int = 400,
details: Optional[Dict[str, Any]] = None,
request_id: Optional[str] = None
) -> Response:
"""
Create an error response.
Args:
code: Error code (e.g., "INVALID_INPUT")
message: Human-readable error message
status: HTTP status code (default 400)
details: Optional additional error details
request_id: Optional request ID
Returns:
Response: Flask JSON response
Example:
>>> return error_response(
... code="NOT_FOUND",
... message="Character not found",
... status=404
... )
"""
error = {
"code": code,
"message": message,
"details": details or {}
}
return api_response(
error=error,
status=status,
request_id=request_id
)
def created_response(
result: Any = None,
request_id: Optional[str] = None
) -> Response:
"""
Create a 201 Created response.
Args:
result: The created resource data
request_id: Optional request ID
Returns:
Response: Flask JSON response with status 201
Example:
>>> return created_response({"character_id": "123"})
"""
return success_response(
result=result,
status=201,
request_id=request_id
)
def accepted_response(
result: Any = None,
request_id: Optional[str] = None
) -> Response:
"""
Create a 202 Accepted response (for async operations).
Args:
result: Job information or status
request_id: Optional request ID
Returns:
Response: Flask JSON response with status 202
Example:
>>> return accepted_response({"job_id": "abc123"})
"""
return success_response(
result=result,
status=202,
request_id=request_id
)
def no_content_response(request_id: Optional[str] = None) -> Response:
"""
Create a 204 No Content response.
Args:
request_id: Optional request ID
Returns:
Response: Flask JSON response with status 204
Example:
>>> return no_content_response()
"""
return success_response(
result=None,
status=204,
request_id=request_id
)
def paginated_response(
items: list,
page: int,
limit: int,
total: int,
request_id: Optional[str] = None
) -> Response:
"""
Create a paginated response.
Args:
items: List of items for current page
page: Current page number
limit: Items per page
total: Total number of items
request_id: Optional request ID
Returns:
Response: Flask JSON response with pagination metadata
Example:
>>> return paginated_response(
... items=[{"id": "1"}, {"id": "2"}],
... page=1,
... limit=20,
... total=100
... )
"""
pages = (total + limit - 1) // limit # Ceiling division
meta = {
"page": page,
"limit": limit,
"total": total,
"pages": pages
}
return success_response(
result=items,
meta=meta,
request_id=request_id
)
# Common error responses
def unauthorized_response(
message: str = "Unauthorized",
request_id: Optional[str] = None
) -> Response:
"""401 Unauthorized response."""
return error_response(
code="UNAUTHORIZED",
message=message,
status=401,
request_id=request_id
)
def forbidden_response(
message: str = "Forbidden",
request_id: Optional[str] = None
) -> Response:
"""403 Forbidden response."""
return error_response(
code="FORBIDDEN",
message=message,
status=403,
request_id=request_id
)
def not_found_response(
message: str = "Resource not found",
request_id: Optional[str] = None
) -> Response:
"""404 Not Found response."""
return error_response(
code="NOT_FOUND",
message=message,
status=404,
request_id=request_id
)
def validation_error_response(
message: str,
details: Optional[Dict[str, Any]] = None,
request_id: Optional[str] = None
) -> Response:
"""400 Bad Request for validation errors."""
return error_response(
code="INVALID_INPUT",
message=message,
status=400,
details=details,
request_id=request_id
)
def rate_limit_exceeded_response(
message: str = "Rate limit exceeded",
request_id: Optional[str] = None
) -> Response:
"""429 Too Many Requests response."""
return error_response(
code="RATE_LIMIT_EXCEEDED",
message=message,
status=429,
request_id=request_id
)
def internal_error_response(
message: str = "Internal server error",
request_id: Optional[str] = None
) -> Response:
"""500 Internal Server Error response."""
return error_response(
code="INTERNAL_ERROR",
message=message,
status=500,
request_id=request_id
)
def premium_required_response(
message: str = "This feature requires a premium subscription",
request_id: Optional[str] = None
) -> Response:
"""403 Forbidden for premium-only features."""
return error_response(
code="PREMIUM_REQUIRED",
message=message,
status=403,
request_id=request_id
)