34 lines
1.5 KiB
Python
34 lines
1.5 KiB
Python
"""统一 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)))
|