在做 #5014(REST zodIssuesToFields 展开 invalid_union,PR #5362)时顺手核出的第四个消费者,不在那单的文件面内,故独立记录,未在该 PR 中修改。
落点
packages/metadata-protocol/src/protocol.ts:7128(saveMetaItem 的 spec-conformance 检查):
const parsed = schema.safeParse(request.item);
if (!parsed.success) {
const issues = parsed.error.issues.map((i) => ({
path: i.path.join('.'),
message: i.message,
code: i.code,
}));
const summary = issues.slice(0, 3).map((i) => `${i.path || '(root)'}: ${i.message}`).join('; ');
...
(err as any).code = 'INVALID_METADATA';
(err as any).status = 422;
(err as any).issues = issues;
(源码里 '(root)' 那处实际写的是尖括号包住 root 的字面量。)
和 #5014 一样:只映射顶层 issue,issue.errors 里每个分支的真实拒绝理由(含 #4001 那批 strictObject 的策展处方)在 .map() 处被丢弃。差别在于这条路径才是 spec 里那些处方真正的归宿——view / dashboard / flow 这些 overlay 类型走的就是它,而 REST 的 zodIssuesToFields 只服务数据路由的 ingress schema。
实测(origin/main @ 553a47fda,用 getMetadataTypeSchema('view'),即该函数 resolveOverlaySchema 用的同一个 schema)
输入一个 list view,columns[0].summary 写了未知键:
{ name:'task_list', object:'task', type:'list', label:'Tasks',
columns: [{ field:'title', summary: { type:'sum', fieldd:'amount' } }] }
客户端/Studio 拿到的 issues 全文:
[ { "path": "", "message": "Invalid input", "code": "invalid_union" } ]
422 的 message 摘要行:root: Invalid input(root 外面是尖括号)。
而被丢掉的分支里躺着的是:
branch[1] unrecognized_keys [] Unrecognized key(s) on this view container: `type`, `columns`. Until #4001 closed these shapes …
branch[2] invalid_value ["type"] Invalid option: expected one of "grid"|"kanban"|"gallery"|…
branch[3] invalid_value ["type"] Invalid option: expected one of "simple"|"tabbed"|"wizard"|…
即:一个字段名都没有到达作者,path 是空串,Studio 表单没有任何东西可以高亮——注释里写的「so the Studio form can highlight the offending field」在顶层是 union 的类型上不成立。ViewSchema 顶层本身是 union(view.zod.ts:2332 的 z.preprocess(stripViewConsoleDecorations, z.union([...]))),所以 view 类型的每一次保存失败都退化成这一条。
与已有单的关系
同一缺陷的四个消费者,判定必须一致(否则同一个错误在终端/API/Studio 说法不同):
建议修法与前三处一致:照抄 #4971 的 selectUnionBranches 策略(丢弃只报根部 KIND 不匹配的分支;报得最少的分支胜出——这条是防止一个未知键被 N 个分支各报一遍;unrecognized_keys 破平局;并列全出且有上限;嵌套 union 按绝对路径递归)。注意 issues[] 的条数会变,属 wire 可见改动;这条路径不走 ADR-0114 的 fields[] 目录(它透传 zod 原始 code),是否顺手对齐目录需要单独裁决。
未打标签,留给 PM 分诊。
在做 #5014(REST
zodIssuesToFields展开invalid_union,PR #5362)时顺手核出的第四个消费者,不在那单的文件面内,故独立记录,未在该 PR 中修改。落点
packages/metadata-protocol/src/protocol.ts:7128(saveMetaItem的 spec-conformance 检查):(源码里
'(root)'那处实际写的是尖括号包住 root 的字面量。)和 #5014 一样:只映射顶层 issue,
issue.errors里每个分支的真实拒绝理由(含 #4001 那批strictObject的策展处方)在.map()处被丢弃。差别在于这条路径才是 spec 里那些处方真正的归宿——view / dashboard / flow 这些 overlay 类型走的就是它,而 REST 的zodIssuesToFields只服务数据路由的 ingress schema。实测(
origin/main@553a47fda,用getMetadataTypeSchema('view'),即该函数resolveOverlaySchema用的同一个 schema)输入一个 list view,
columns[0].summary写了未知键:客户端/Studio 拿到的
issues全文:422 的 message 摘要行:
root: Invalid input(root 外面是尖括号)。而被丢掉的分支里躺着的是:
即:一个字段名都没有到达作者,
path是空串,Studio 表单没有任何东西可以高亮——注释里写的「so the Studio form can highlight the offending field」在顶层是 union 的类型上不成立。ViewSchema顶层本身是 union(view.zod.ts:2332的z.preprocess(stripViewConsoleDecorations, z.union([...]))),所以 view 类型的每一次保存失败都退化成这一条。与已有单的关系
同一缺陷的四个消费者,判定必须一致(否则同一个错误在终端/API/Studio 说法不同):
packages/specformatZodError—— formatZodError 把 union 分支的拒绝信息压成 "Invalid input" —— #4001 策展的散文在 CLI 路径上到不了作者 #4971,已合入546ab3c49(PR fix(spec): formatZodError 展开 union 分支的拒绝信息 (#4971) #5342)packages/restzodIssuesToFields—— union 分支里的 unknown-key 处方永远到不了作者:zodIssuesToFields只映射顶层 issue,失败的 union 只剩Invalid input#5014,PR fix(rest): zodIssuesToFields 展开 invalid_union,联合分支里的处方到达调用方 (#5014) #5362(本单发现者)packages/cliformatZodErrors——os validate/os build用的是 CLI 自己的 formatZodErrors,它同样把 union 分支的处方裁掉 —— #4971 修的不是这条路径 #5341,未做packages/metadata-protocolsaveMetaItem的 422 —— 本单建议修法与前三处一致:照抄 #4971 的
selectUnionBranches策略(丢弃只报根部 KIND 不匹配的分支;报得最少的分支胜出——这条是防止一个未知键被 N 个分支各报一遍;unrecognized_keys破平局;并列全出且有上限;嵌套 union 按绝对路径递归)。注意issues[]的条数会变,属 wire 可见改动;这条路径不走 ADR-0114 的fields[]目录(它透传 zod 原始code),是否顺手对齐目录需要单独裁决。未打标签,留给 PM 分诊。