59 lines
2.1 KiB
Python
59 lines
2.1 KiB
Python
"""Shared HTTP request and error contracts."""
|
|
|
|
from typing import Any, Optional
|
|
|
|
from pydantic import BaseModel, ConfigDict, Field
|
|
|
|
|
|
class StrictRequest(BaseModel):
|
|
model_config = ConfigDict(extra="forbid")
|
|
|
|
|
|
class ErrorResponse(BaseModel):
|
|
detail: str
|
|
|
|
|
|
COMMON_ERROR_RESPONSES: dict[int, dict[str, Any]] = {
|
|
400: {"model": ErrorResponse, "description": "Invalid request"},
|
|
401: {"model": ErrorResponse, "description": "Authentication required"},
|
|
403: {"model": ErrorResponse, "description": "Permission denied"},
|
|
404: {"model": ErrorResponse, "description": "Resource not found"},
|
|
409: {"model": ErrorResponse, "description": "Request conflict"},
|
|
429: {"model": ErrorResponse, "description": "Rate limit exceeded"},
|
|
500: {"model": ErrorResponse, "description": "Unexpected server error"},
|
|
502: {"model": ErrorResponse, "description": "Upstream service error"},
|
|
503: {"model": ErrorResponse, "description": "Service unavailable"},
|
|
}
|
|
|
|
|
|
class SignupRequest(StrictRequest):
|
|
invite_code: str = Field(min_length=1, max_length=256)
|
|
username: str = Field(min_length=1, max_length=100)
|
|
password: str = Field(min_length=1, max_length=1024)
|
|
email: Optional[str] = Field(default=None, max_length=320)
|
|
|
|
|
|
class ForgotPasswordRequest(StrictRequest):
|
|
identifier: Optional[str] = Field(default=None, max_length=320)
|
|
username: Optional[str] = Field(default=None, max_length=100)
|
|
email: Optional[str] = Field(default=None, max_length=320)
|
|
|
|
|
|
class PasswordResetRequest(StrictRequest):
|
|
token: str = Field(min_length=1, max_length=512)
|
|
new_password: str = Field(min_length=1, max_length=1024)
|
|
|
|
|
|
class ProfileEmailUpdateRequest(StrictRequest):
|
|
email: Optional[str] = Field(default=None, max_length=320)
|
|
|
|
|
|
class ChangePasswordRequest(StrictRequest):
|
|
current_password: str = Field(min_length=1, max_length=1024)
|
|
new_password: str = Field(min_length=1, max_length=1024)
|
|
|
|
|
|
def request_data(payload: BaseModel | dict[str, Any]) -> dict[str, Any]:
|
|
"""Keep direct service-level tests compatible while FastAPI validates HTTP input."""
|
|
return payload if isinstance(payload, dict) else payload.model_dump()
|