文档先行:AGC 认证错误的 TS 形状改为每个变体一个具名载荷类型

- ADR §1 增加"每个变体一个具名载荷结构体 + ts-rs 生成 `{ type } & X`"的决策与代价
- ADR §3 说明 switch 分支里错误已窄化成具名类型
- 技术方案同步命令错误形状描述
- decision-log / pitfalls 记录形状约定:不手写这层类型、不再包派生分类与提示文案函数
This commit is contained in:
2026-10-01 18:08:00 +08:00
parent b561db9249
commit 2f51e043b7
4 changed files with 12 additions and 3 deletions
@@ -33,6 +33,12 @@ DirectProject 已经解决过同一类问题([`【ADR】DirectProject命令接
- Rust 侧定义具体变体枚举(auth 首个落地:`ClientAuthError`),`#[derive(Serialize, TS)]` +
`#[serde(tag = "type", rename_all = "camelCase")]`,用 ts-rs 导出到
`apps/ai-game-creator-shell/src/services/generated/`;生成物不手改。
- **每个变体一个具名载荷结构体**,枚举用 newtype 变体持有它(`PhoneNumberInvalid(PhoneNumberInvalid)`)。
ts-rs 因此生成 `{ type: 'phoneNumberInvalid' } & PhoneNumberInvalid`,以及每个变体一个
`generated/<变体名>.ts`:前端 `switch (error.type)` 的每个分支都落到一个有名字的类型,等价于 Java 的
`catch (PhoneNumberInvalid e)`,不需要 `as` 断言,载荷类型自己带 JSDoc。载荷必须能序列化成 map
(serde 的 internally tagged 表示只接受 struct / map),所以没有无字段变体;代价是 Rust 构造点统一写成
`ClientAuthError::X(X { message })`。这个形状在本仓已有先例(`DirectCodexUserContentPart`)。
- `#[tauri::command]` 的 `Err` 直接携带该枚举(Tauri 2 的 `InvokeError(pub serde_json::Value)` 支持结构化错误)。
这是 DirectProject 已有的做法(`enqueue_direct_codex_turn -> Result<(), DirectTurnEnqueueFailure>`),不是新约定。
- 变体按**可判定的事实**命名。服务端 400 只提供 `status + message`(`AppError.code` 仍是通用
@@ -57,7 +63,8 @@ DirectProject 已经解决过同一类问题([`【ADR】DirectProject命令接
- `isClientAuthError` 只做形状读取(`type` 是稳定判别键),**不做分类、不产出派生值**。
- 调用方自己 `switch (error.type)`,逐个列出具体变体:业务输入 / 会话变体给提示,`default`(系统变体
与 Rust 新增而界面没接的变体)带上文交池。判据是具体变体名,不是聚合出来的类别,也不是文案匹配。
与 Rust 新增而界面没接的变体)带上文交池。判据是具体变体名,不是聚合出来的类别,也不是文案匹配;
每个 `case` 里 `error` 已窄化成 §1 的具名载荷类型(如 `PhoneNumberInvalid`)。
- 认不出、系统类、非结构化拒绝 → 调用方包成 `ClientActionError(message, context, cause)` 交给
`captureClientError`:`instanceof` 解包 `context` 与 `cause`,指纹/展示字段与今天一致。
**不把 rejection 留在没人接手的 Promise 上**:`onSubmit` / `onClick` 这类 `void` 掉的 handler 抛错