"""Stable RFC 9457-style problem responses for public API failures.""" from __future__ import annotations from dataclasses import dataclass from typing import Any from fastapi import Request from fastapi.responses import JSONResponse PROBLEM_MEDIA_TYPE = "application/problem+json" PROBLEM_BASE = "https://geological-rag.local/problems" @dataclass(frozen=True, slots=True) class ApiProblem(Exception): """An intentionally public and sanitized application failure.""" status: int code: str title: str detail: str def __post_init__(self) -> None: Exception.__init__(self, self.code) def problem_payload( *, status: int, code: str, title: str, detail: str, trace_id: str, field_errors: list[dict[str, Any]] | None = None, ) -> dict[str, Any]: """Build the only public error envelope used by formal product routes.""" return { "type": f"{PROBLEM_BASE}/{code.lower().replace('_', '-')}", "title": title, "status": status, "code": code, "detail": detail, "trace_id": trace_id, "field_errors": field_errors or [], } def api_problem_handler(request: Request, exc: ApiProblem) -> JSONResponse: trace_id = str(getattr(request.state, "trace_id", "unavailable")) return JSONResponse( status_code=exc.status, media_type=PROBLEM_MEDIA_TYPE, content=problem_payload( status=exc.status, code=exc.code, title=exc.title, detail=exc.detail, trace_id=trace_id, ), )