""" 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 )