- Makefile:统一命令入口(backend/engine/docker/openapi/generate,含 make help) - backend:FastAPI + uv 最小工程,统一错误体(契约第 7 节)、ADR-015 平台 ID 生成与校验(uuid-utils);pytest 7 项、ruff、mypy strict、启动冒烟全部通过 - engine:灵台引擎 C++23/CMake 骨架,按 Lingtai C ABI 契约实现 lingtai.h(13 个 HjStatus、版本协商、句柄表防二次释放、线程局部错误上下文);doctest 5 用例通过,nm 验证仅导出 hj_* 符号 - openapi:OpenAPI 3.1 单一真源初始版(错误体/分页/Money/首批平台 ID/ContentBlock 全 21 类型/流事件 envelope),Redocly lint 0 错误、bundle 通过 - frontend:Next.js 最小骨架,openapi-typescript 生成 API 类型并通过 strict tsc - docker:hunjing-postgres(pgvector:17)compose;.env.example 与 config 示例 - ADR-025:登记测试与契约工具链(doctest/libFuzzer/Redocly/openapi-typescript/uuid-utils) - desktop:Tauri v2 占位与初始化说明(WP-10 启动)
77 lines
2.2 KiB
Python
77 lines
2.2 KiB
Python
"""统一错误体与领域异常(契约见 docs/contracts/api-conventions.md 第 7 节)。
|
|
|
|
`code` 是稳定机器值;`message` 可本地化;`details` 不稳定但必须脱敏。
|
|
"""
|
|
|
|
from typing import Any
|
|
|
|
from fastapi import FastAPI, Request
|
|
from fastapi.exceptions import RequestValidationError
|
|
from fastapi.responses import JSONResponse
|
|
from pydantic import BaseModel
|
|
|
|
|
|
class ErrorBody(BaseModel):
|
|
code: str
|
|
message: str
|
|
trace_id: str | None = None
|
|
request_id: str | None = None
|
|
details: dict[str, Any] | None = None
|
|
|
|
|
|
class ErrorEnvelope(BaseModel):
|
|
error: ErrorBody
|
|
|
|
|
|
class HunjingError(Exception):
|
|
"""领域异常基类:映射为稳定错误码的 HTTP 响应。"""
|
|
|
|
status_code: int = 500
|
|
code: str = "internal"
|
|
|
|
def __init__(self, message: str, *, details: dict[str, Any] | None = None) -> None:
|
|
super().__init__(message)
|
|
self.message = message
|
|
self.details = details
|
|
|
|
|
|
class ValidationFailed(HunjingError):
|
|
status_code = 422
|
|
code = "validation_failed"
|
|
|
|
|
|
def _envelope(
|
|
code: str, message: str, request: Request, details: dict[str, Any] | None
|
|
) -> dict[str, Any]:
|
|
return {
|
|
"error": {
|
|
"code": code,
|
|
"message": message,
|
|
"trace_id": None,
|
|
"request_id": request.headers.get("Hunjing-Request-ID"),
|
|
"details": details,
|
|
}
|
|
}
|
|
|
|
|
|
def install_error_handlers(app: FastAPI) -> None:
|
|
@app.exception_handler(HunjingError)
|
|
async def _hunjing_error(request: Request, exc: HunjingError) -> JSONResponse:
|
|
return JSONResponse(
|
|
status_code=exc.status_code,
|
|
content=_envelope(exc.code, exc.message, request, exc.details),
|
|
)
|
|
|
|
@app.exception_handler(RequestValidationError)
|
|
async def _validation_error(request: Request, exc: RequestValidationError) -> JSONResponse:
|
|
field_errors = [
|
|
{"path": ".".join(str(p) for p in err["loc"]), "reason": err["type"]}
|
|
for err in exc.errors()
|
|
]
|
|
return JSONResponse(
|
|
status_code=422,
|
|
content=_envelope(
|
|
"validation_failed", "请求字段不合法。", request, {"field_errors": field_errors}
|
|
),
|
|
)
|