""" infra.api.errors — the API error model. One exception type, ``ApiError``, carrying a machine-readable ``code``, a human ``message`` and the HTTP ``status`` it maps to. Handlers raise it; the FastAPI shell (app.py) catches it and renders a consistent JSON body: {"error": {"code": "...", "message": "..."}} Pure module — no fastapi dependency, so handlers stay testable without a web server. """ from __future__ import annotations class ApiError(Exception): """A handler-level error with a stable code and an HTTP status.""" def __init__(self, code: str, message: str, status: int = 400): super().__init__(message) self.code = code self.message = message self.status = status def to_dict(self) -> dict: return {"error": {"code": self.code, "message": self.message}} # --- common errors, as small factories so codes/statuses stay consistent --- def bad_request(message: str) -> ApiError: return ApiError("bad_request", message, status=400) def not_found(message: str) -> ApiError: return ApiError("not_found", message, status=404) def conflict(message: str) -> ApiError: return ApiError("conflict", message, status=409) def unprocessable(message: str) -> ApiError: return ApiError("unprocessable", message, status=422)