"""Structured, localized API error contracts. The exception text is intentionally never serialized. Route handlers can raise ``ApiError`` with a stable code and translation key; Flask integration resolves the message through the current locale at the response boundary. """ from __future__ import annotations from dataclasses import dataclass, field from typing import Callable, Mapping Translator = Callable[..., str] @dataclass class ApiError(Exception): """A safe application error that can cross the HTTP boundary.""" code: str status_code: int message_key: str params: Mapping[str, object] = field(default_factory=dict) def __post_init__(self): Exception.__init__(self, self.code) def to_payload(self, translate: Translator) -> dict[str, object]: return { "success": False, "error_code": self.code, "message": translate(self.message_key, **dict(self.params)), } def internal_error_payload(translate: Translator) -> dict[str, object]: """Return a generic internal error without accepting exception details.""" return ApiError( code="internal_error", status_code=500, message_key="api.internalError", ).to_payload(translate)