🎉 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 启动)
This commit is contained in:
1 parent
5da1efe921
commit
3fbcd0437c
32 files changed
+11459
No files matched your search
@@ -0,0 +1,76 @@
|
||||
"""统一错误体与领域异常(契约见 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}
|
||||
),
|
||||
)
|
||||
Reference in new issue
Block a user