fix(rest): zodIssuesToFields 展开 invalid_union,联合分支里的处方到达调用方 (#5014) - #5362
Merged
Conversation
zod 把失配的 z.union 折叠成一条顶层 invalid_union issue,message 是裸的 "Invalid input",各分支真正的拒绝理由(含 #4001 那批 strictObject 处方) 挂在 issue.errors 里。zodIssuesToFields 只映射顶层 issue,所以 POST /api/v1/data/:object/query 对 {"search":{"fields":["name"]}} 只回 {field:"query.search", code:"invalid_shape", message:"Invalid input"} —— 「缺 query 这个键」那句话被生产出来又被丢掉。 现在联合条目之后追加解释它的分支条目:field 用分支相对路径拼上联合自身路径, code 仍走 ADR-0114 D3 的目录映射(缺键 → required,这需要绝对路径去读入参)。 分支选择策略沿用 #4971 给 spec 侧 formatZodError 落的那一套(丢弃只报根部 KIND 不匹配的分支;报得最少的分支胜出——这条就是防止一个未知键被 N 个分支各 报一遍的机制;unrecognized_keys 破平局;声明顺序破其余;并列全出,上限 3; 跨分支重复结论只出现一次;嵌套联合按绝对路径递归,深度上限 3),两侧判定必须 一致,否则同一个错误从终端和从 API 会得到两套说法。 对 wire 是纯追加:原有条目的 field/code/message 与相对次序均不变。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FTszibd6C8sUCCZnM4VcrL
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
Contributor
📓 Docs Drift CheckThis PR changes 1 package(s): 11 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
联合失配现在除了联合自身那条,还会追加解释它的分支条目,所以 `fields.length` 要读作「字段错误条数」而不是「zod issue 条数」。 这段示例正是对外推荐 `zodIssuesToFields` 的地方。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FTszibd6C8sUCCZnM4VcrL
os-zhuang
marked this pull request as ready for review
August 5, 2026 01:24
This was referenced Aug 5, 2026
This was referenced Aug 5, 2026
Closed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #5014
机制
zod 把一个失配的
z.union([...])折叠成一条顶层invalid_unionissue,它自己的message是裸的"Invalid input";每个分支真正的抱怨——包括 #4001 那批strictObject写下的处方文案——躺在issue.errors里(每分支一个数组)。packages/rest/src/rest-server.ts的zodIssuesToFields过去只映射顶层 issue,分支错误整体丢失。前提核验(issue body 是线索,不是规格)
机制成立,但 issue 里那个具体 witness 已经过期,两点分别记录:
过期的部分:
DashboardWidgetSchema的compareTo现在不再是 union。dashboard widgetcompareTo:三个声明分支在 ADR-0021 dataset 路径上全部无效(两个静默丢弃,一个抛错) #5011 把它收敛到分析执行器自己的契约{ kind, dimension? }(一个普通 strict object),所以 issue 里compareTo:{offset:'7d', granularity:'month'}那条实测在origin/main上已经不复现——处方(Unrecognized key(s)... granularity、offset的退役说明)现在直接出现在顶层 issue 上。这一点仓库内已有记录:packages/spec/src/ui/strictness-batch14.test.ts:194与dashboard-compareto.test.ts:199都写了「dashboard widgetcompareTo:三个声明分支在 ADR-0021 dataset 路径上全部无效(两个静默丢弃,一个抛错) #5011 DISSOLVED that limit for this slot」。仍然成立的部分(本 PR 修的):缺陷本身在 REST 数据路由的 ingress schema 上活着,而且是真实可达的。
POST /api/v1/data/:object/query用FindDataRequestSchema校验请求体(请求体从不与声明它的 schema 对照(#3877 的请求侧对偶):7 个 schema 定义了从未启用,而 API 目录已宣称生效 #3899),QuerySchema里有两处 union:实测(修复前):
fields[]{"search": {"fields": ["name"]}}[{field:"query.search", code:"invalid_shape", message:"Invalid input"}]{"groupBy": [{"field":"closed_at","dateGranularity":"decade"}]}[{field:"query.groupBy.0", code:"invalid_shape", message:"Invalid input"}]真正说清问题的两句话——「缺的是
query这个键」和「可选值是 day/week/month/quarter/year」——被生产出来然后丢掉。另需说明:元数据发布路径(
DashboardWidgetSchema这类)并不经过zodIssuesToFields,它走sendError的status直通并原样透传issues;那是另一个面,不在本单文件面内。修法:照抄 #4971 的分支选择策略,不重新发明
selectUnionBranches/renderIssue已于546ab3c49(PR #5342)为 spec/CLI 侧的formatZodError落地。本 PR 复用同一套策略,逐条对齐:expected string, received object)整支丢弃;全部如此则不展开,输出与从前逐字一致。unrecognized_keys破平局(策展文案在那儿)。复用方式说明(三消费者必须同判定):代码是改写(adapt)而非 import。spec 只导出字符串渲染器(
formatZodIssue/formatZodError),而 wire 需要的是结构化的{field, code, message}条目;把选择逻辑单独导出需要改packages/spec,本单明确不碰。所以策略在rest-server.ts里以同名函数、同顺序、同上限重写,并在注释里指回 spec 的出处。CLI 侧的formatZodErrors(#5341)还未做——三处必须给出同一个判定,否则同一个错误从终端发布和从 API 提交会得到两套说法。一处有意的分歧(只在渲染,不在判定):spec 侧在并列分支超限时打印一行
… and N more branches rejected this value;fields[]不能有这种条目——一条 field error 必须指向真实字段并带目录码,这行两样都没有,故不输出。wire 契约(ADR-0114)
改动是纯追加:
fields[]条目(包括联合自身那条invalid_shape/"Invalid input")的field、code、message与相对次序完全不变;新条目插在它解释的那条之后。mapDataError的VALIDATION_FAILED同形({field, code, message});数组长度从来不是契约的一部分,ADR-0114 约束的是code取值必须落在FieldErrorCode目录内——新增条目同样走zodIssueToFieldCode,并有测试逐条断言其为目录成员。path是相对于联合的,而 ADR-0114 D3 用「把 path 走进入参」来区分required与invalid_type。所以zodIssueToFieldCode改成接收绝对路径;沿用相对路径会读到错误的槽位(通常是undefined),把每个分支的类型不符都误报成required。验证
先红后绿 + 反向验证,三步都在报告里留了真实输出:
10 failed | 3 passed,失败信息就是[{"field":"query.search","code":"invalid_shape","message":"Invalid input"}]: expected undefined to be defined。通过的那 3 条正是不变量(全 KIND 不匹配不展开 / 非 union 一对一 / junk 容错)。packages/rest全量41 test files | 621 tests passed。selectUnionBranches临时改成「展开所有分支」,预期是防噪测试转红——实测一个typo从 2 条变成 7 条条目、其中unknown_field出现 2 次,同时 KIND 不匹配不变量、上限、平局三条测试一起转红。随后已还原。Generated by Claude Code