fix(validation): retain unmatched union correction details

Preserve actionable field paths and schema-declared variants for unknown or missing discriminators.

Closes #1421. Refs #1065.
This commit is contained in:
Ysopwnable
2026-10-10 12:15:11 +08:00
committed by GitHub
parent 28595276fa
commit 4d95f2b2e7
2 changed files with 63 additions and 1 deletions
+18 -1
View File
@@ -35,7 +35,23 @@ const projectIssue = (
typeof part === "string" || typeof part === "number" ? [part] : [],
);
// Branch issue paths are relative to the union; branches often agree.
if (issue.code === "invalid_union")
if (issue.code === "invalid_union") {
// Unmatched discriminators and ambiguous unions have no branch issues.
// Keep their outer failure instead of flattening it into an empty list.
if (issue.errors.length === 0) {
const expected =
"options" in issue ? issue.options?.filter(isSafeExpected) : undefined;
return [
{
path,
reason:
valueAtPath(input, path) === undefined
? "missing_argument"
: "invalid_value",
...(expected === undefined ? {} : { expected }),
},
];
}
return uniqueIssues(
issue.errors.flatMap((branch) =>
branch.flatMap((branchIssue) =>
@@ -46,6 +62,7 @@ const projectIssue = (
),
),
);
}
if (issue.code === "invalid_key")
return issue.issues.flatMap((keyIssue) =>
projectIssue(
@@ -80,3 +80,48 @@ cliTest("rejects a request that omits source", async ({ cli }) => {
details: { operation: "inspect-analysis-view" },
});
});
for (const [view, reason] of [
[{ kind: "unknown-view" }, "invalid_value"],
[{}, "missing_argument"],
] as const) {
cliTest(
`reports correction details for ${reason} view kind`,
async ({ cli }) => {
const root = await createTestTempDirectory("rea-view-variant-");
const input = join(root, "view.json");
await writeFile(
input,
JSON.stringify({
source: {
kind: "inline",
evidence: analysisViewLayoutEvidence(),
},
view,
}),
);
const result = await cli.run({
arguments: ["inspect-analysis-view", input, "--json"],
environment: {
HOME: root,
XDG_CONFIG_HOME: root,
XDG_CACHE_HOME: root,
},
});
expect(result.exitCode).toBe(1);
expect(result.json).toMatchObject({
code: "invalid_request",
details: {
issues: [
{
path: ["view", "kind"],
reason,
expected: expect.arrayContaining(["summary", "page"]),
},
],
},
});
expect(JSON.stringify(result.json)).not.toContain("unknown-view");
},
);
}