Files
group_xinghuo_jinrong/app/utils/response.py
T

57 lines
2.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""统一 API 响应外壳(B7 · 挂账④;T-02 扩展)。
成功响应:业务字段平铺不变(B5 评审 P2-2 口径「路由返回体不变」),trace_id
经 X-Trace-Id 响应头贯通;错误响应统一 JWT 手册 §10 结构
{error_code, message, trace_id, request_id}——request_id 为独立请求级标识
(T-02,B7 复审 P3-4 收口),trace_id/request_id 分别对齐响应头。
T-02 补齐(B7 复审 P2-2):422 请求校验失败、404/405 路由方法错误改用统一
错误体(原 FastAPI detail 结构);未捕获异常的 500 由 main.trace 中间件
兜底(异常穿透 exception handler,直接在中间件层生成错误体并回写 trace 头)。
main 与测试 app 共用 register_error_handlers。
"""
from __future__ import annotations
from fastapi import FastAPI, Request
from fastapi.exceptions import RequestValidationError
from fastapi.responses import JSONResponse
from starlette.exceptions import HTTPException as StarletteHTTPException
from app.utils.exceptions import ApiError, PermissionDenied
from app.utils.trace import current_request_id, current_trace, new_trace, new_request_id
# 路由级 HTTP 状态 → 错误码(手册 §10 之外的平台通用码)
_HTTP_ERROR_CODES = {404: "NOT_FOUND", 405: "METHOD_NOT_ALLOWED"}
def error_body(error_code: str, message: str) -> dict[str, str]:
"""手册 §10 错误体(trace/request 缺失时兜底生成,保证响应可归因)。"""
tid = current_trace() or new_trace()
rid = current_request_id() or new_request_id()
return {"error_code": error_code, "message": message, "trace_id": tid, "request_id": rid}
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)))
@app.exception_handler(StarletteHTTPException)
async def _http_exception_handler(request: Request, exc: StarletteHTTPException) -> JSONResponse:
code = _HTTP_ERROR_CODES.get(exc.status_code, "HTTP_ERROR")
return JSONResponse(status_code=exc.status_code, content=error_body(code, str(exc.detail)))
@app.exception_handler(RequestValidationError)
async def _validation_error_handler(request: Request, exc: RequestValidationError) -> JSONResponse:
return JSONResponse(
status_code=422,
content=error_body("REQUEST_VALIDATION_FAILED", "request validation failed"),
)