"""统一 API 响应外壳(B7 · 挂账④)。 成功响应:业务字段平铺不变(B5 评审 P2-2 口径「路由返回体不变」),trace_id 经 X-Trace-Id 响应头贯通(main 中间件);错误响应统一 JWT 手册 §10 结构 {error_code, message, trace_id, request_id}(request_id 沿用 trace_id, 独立请求级标识尚未引入)。main 与测试 app 共用 register_error_handlers。 """ from __future__ import annotations from fastapi import FastAPI, Request from fastapi.responses import JSONResponse from app.utils.exceptions import ApiError, PermissionDenied from app.utils.trace import current_trace, new_trace def error_body(error_code: str, message: str) -> dict[str, str]: """手册 §10 错误体(trace 缺失时兜底生成,保证响应可归因)。""" tid = current_trace() or new_trace() return {"error_code": error_code, "message": message, "trace_id": tid, "request_id": tid} def register_error_handlers(app: FastAPI) -> None: """注册统一错误体 handler(替代 FastAPI 默认 detail 结构)。""" @app.exception_handler(ApiError) async def _api_error_handler(request: Request, exc: ApiError) -> JSONResponse: return JSONResponse(status_code=exc.status_code, content=error_body(exc.error_code, exc.message)) @app.exception_handler(PermissionDenied) async def _permission_denied_handler(request: Request, exc: PermissionDenied) -> JSONResponse: return JSONResponse(status_code=403, content=error_body(exc.code, str(exc)))