first commit
This commit is contained in:
337
api/app/utils/response.py
Normal file
337
api/app/utils/response.py
Normal file
@@ -0,0 +1,337 @@
|
||||
"""
|
||||
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
|
||||
)
|
||||
Reference in New Issue
Block a user