""" RESTful response format with generic types. Provides unified response structure for all API endpoints. """ from typing import TypeVar, Generic, Optional, Any from datetime import datetime from pydantic import BaseModel, Field from enum import IntEnum T = TypeVar("T") class ApiResponse(BaseModel, Generic[T]): """ Unified API response structure. Both success and error responses use this structure. Attributes: code: Business status code (0 = success, >0 = error) message: Human-readable message data: Response payload (typed) or None for errors trace_id: Request trace ID for debugging timestamp: Response timestamp in ISO 8601 format """ code: int = Field(description="Business status code (0=success, >0=error)") message: str = Field(description="Human-readable message") data: Optional[T] = Field(default=None, description="Response payload") trace_id: str = Field(description="Request trace ID") timestamp: datetime = Field( default_factory=datetime.utcnow, description="Response timestamp" ) class PaginationMeta(BaseModel): """Pagination metadata.""" total: int = Field(description="Total number of records") page: int = Field(description="Current page number (1-based)") page_size: int = Field(description="Number of records per page") total_pages: int = Field(description="Total number of pages") class PaginatedData(BaseModel, Generic[T]): """ Paginated data structure. Attributes: items: List of items for current page pagination: Pagination metadata """ items: list[T] = Field(description="List of items") pagination: PaginationMeta = Field(description="Pagination metadata") def success( data: Optional[T] = None, message: str = "Success", trace_id: str = "" ) -> ApiResponse[T]: """ Create success response. Args: data: Response payload message: Success message trace_id: Request trace ID Returns: ApiResponse: Success response with code=0 Example: return success(data={"user_id": 123}, message="User created") """ return ApiResponse( code=BusinessCode.SUCCESS, message=message, data=data, trace_id=trace_id, timestamp=datetime.now(), ) def error(code: "BusinessCode", data: Any=None, message: str="", trace_id: str = "") -> ApiResponse[None]: """ Create error response. Args: code: Business error code (must be > 0) message: Error message trace_id: Request trace ID Returns: ApiResponse: Error response Example: return error(code=1001, message="Login failed: Invalid credentials") """ if code <= 0: raise ValueError("Error code must be greater than 0") if message == "": message = BusinessCode(code).name return ApiResponse( code=code, message=message, data=data, trace_id=trace_id, timestamp=datetime.now(), ) def paginated( items: list[T], total: int, page: int, page_size: int, message: str = "Success", trace_id: str = "", ) -> ApiResponse[PaginatedData[T]]: """ Create paginated response. Args: items: List of items for current page total: Total number of records page: Current page number (1-based) page_size: Number of records per page message: Success message trace_id: Request trace ID Returns: ApiResponse: Paginated response Example: return paginated( items=[user1, user2], total=100, page=1, page_size=10 ) """ total_pages = (total + page_size - 1) // page_size if page_size > 0 else 0 pagination_meta = PaginationMeta( total=total, page=page, page_size=page_size, total_pages=total_pages ) paginated_data = PaginatedData(items=items, pagination=pagination_meta) return ApiResponse( code=0, message=message, data=paginated_data, trace_id=trace_id, timestamp=datetime.utcnow(), ) # Business code constants class BusinessCode(IntEnum): """ Business status code definitions. Code ranges: 0: Success 1000-1999: Authentication & Authorization 2000-2999: Business Logic Errors 3000-3999: Validation Errors 4000-4999: Resource Errors 5000-5999: System Errors 9000-9999: Unknown Errors """ # Success SUCCESS = 0 # Authentication & Authorization (1000-1999) LOGIN_FAILED = 1001 TOKEN_EXPIRED = 1002 INSUFFICIENT_PERMISSIONS = 1003 INVALID_TOKEN = 1004 # Business Logic Errors (2000-2999) ORDER_CREATION_FAILED = 2001 PAYMENT_FAILED = 2002 INSUFFICIENT_BALANCE = 2003 OPERATION_NOT_ALLOWED = 2004 # Validation Errors (3000-3999) INVALID_INPUT = 3001 MISSING_REQUIRED_FIELD = 3002 INVALID_FORMAT = 3003 # Resource Errors (4000-4999) RESOURCE_NOT_FOUND = 4001 RESOURCE_ALREADY_EXISTS = 4002 RESOURCE_CONFLICT = 4003 # System Errors (5000-5999) DATABASE_ERROR = 5001 EXTERNAL_SERVICE_ERROR = 5002 CACHE_ERROR = 5003 INTERNAL_ERROR = 5004 # 没有实现 NOT_IMPLEMENTED = 5004 # Unknown Errors (9000-9999) UNKNOWN_ERROR = 9000 # 京东充值的错误 JD_ORDER_FACE_PRICE_ERR = 10001 JD_ORDER_NORMAL_ERR = 10002 JD_ORDER_CK_ERR = 10003 JD_ORDER_TYPE_NOT_SUPPORTED_ERR = 10004 JD_ORDER_EXPIRED_ERR = 10005 JD_ORDER_STOCK_ERR = 10006 JD_ORDER_RISK_ERR = 10007 # Common error response examples for OpenAPI documentation ERROR_RESPONSES = { 400: { "description": "Bad Request - Validation or business logic error", "content": { "application/json": { "example": { "code": 3001, "message": "Validation error: email - field required", "data": None, "trace_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "timestamp": "2024-01-15T10:30:00Z", } } }, }, 401: { "description": "Unauthorized - Authentication failed", "content": { "application/json": { "example": { "code": 1001, "message": "Login failed: Invalid credentials", "data": None, "trace_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "timestamp": "2024-01-15T10:30:00Z", } } }, }, 403: { "description": "Forbidden - Insufficient permissions", "content": { "application/json": { "example": { "code": 1003, "message": "Insufficient permissions to perform this action", "data": None, "trace_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "timestamp": "2024-01-15T10:30:00Z", } } }, }, 404: { "description": "Not Found - Resource does not exist", "content": { "application/json": { "example": { "code": 4001, "message": "User not found", "data": None, "trace_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "timestamp": "2024-01-15T10:30:00Z", } } }, }, 409: { "description": "Conflict - Resource already exists or conflicts", "content": { "application/json": { "example": { "code": 4002, "message": "User already exists", "data": None, "trace_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "timestamp": "2024-01-15T10:30:00Z", } } }, }, 422: { "description": "Unprocessable Entity - Validation error", "content": { "application/json": { "example": { "code": 3001, "message": "Validation error: body -> email - value is not a valid email address", "data": None, "trace_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "timestamp": "2024-01-15T10:30:00Z", } } }, }, 500: { "description": "Internal Server Error - Unexpected error", "content": { "application/json": { "example": { "code": 9000, "message": "An unexpected error occurred", "data": None, "trace_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "timestamp": "2024-01-15T10:30:00Z", } } }, }, 502: { "description": "Bad Gateway - External service error", "content": { "application/json": { "example": { "code": 5002, "message": "External service unavailable", "data": None, "trace_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "timestamp": "2024-01-15T10:30:00Z", } } }, }, 503: { "description": "Service Unavailable - Database or cache error", "content": { "application/json": { "example": { "code": 5001, "message": "Database error occurred", "data": None, "trace_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "timestamp": "2024-01-15T10:30:00Z", } } }, }, } # Error message templates class ErrorMessage: """Predefined error messages for common errors.""" # Authentication LOGIN_FAILED = "Login failed: Invalid credentials" TOKEN_EXPIRED = "Authentication token has expired" INSUFFICIENT_PERMISSIONS = "Insufficient permissions to perform this action" INVALID_TOKEN = "Invalid authentication token" # Business Logic ORDER_CREATION_FAILED = "Order creation failed: {reason}" PAYMENT_FAILED = "Payment processing failed: {reason}" INSUFFICIENT_BALANCE = "Insufficient account balance" OPERATION_NOT_ALLOWED = "Operation not allowed: {reason}" # Validation INVALID_INPUT = "Invalid input: {field}" MISSING_REQUIRED_FIELD = "Missing required field: {field}" INVALID_FORMAT = "Invalid format: {field}" # Resources RESOURCE_NOT_FOUND = "{resource} not found" RESOURCE_ALREADY_EXISTS = "{resource} already exists" RESOURCE_CONFLICT = "{resource} conflict: {reason}" # System DATABASE_ERROR = "Database error occurred" EXTERNAL_SERVICE_ERROR = "External service unavailable" CACHE_ERROR = "Cache service error" # Unknown UNKNOWN_ERROR = "An unexpected error occurred"