Files
chenxin 3fbcd0437c 🎉 feat: 建立阶段 0 工程骨架与 OpenAPI 契约机器化(WP-01 起步)
- 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 启动)
2026-09-09 21:45:04 +08:00

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}
),
)