Skip to content

Commit f734677

Browse files
claudeos-zhuang
authored andcommitted
fix(rest): ingress parse failures speak the documented fields[] envelope
The two new schema parses (#3897 / #3933) reported Zod failures as `issues[]`, copying the cross-object batch route. But the data surface's documented `VALIDATION_FAILED` envelope carries `fields[]` (`{field, code, message}` — wire-format §7), which is also what `mapDataError` emits for a validator-thrown one (#3918). Two shapes behind one wire code means a client keying on `fields` silently sees nothing on these routes. Adds `zodIssuesToFields` and uses it on both, so `code: 'VALIDATION_FAILED'` means one thing on the data surface regardless of which layer noticed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TzLE9cw4gZKNyPN2ZP4iTt
1 parent fccec22 commit f734677

3 files changed

Lines changed: 32 additions & 2 deletions

File tree

packages/rest/src/rest-bulk-path-object.test.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,11 @@ describe('updateMany ingress validation (#3933)', () => {
132132

133133
expect(res.statusCode).toBe(400);
134134
expect(res.body.code).toBe('VALIDATION_FAILED');
135+
// The documented data-surface envelope (`fields[]`, wire-format §7), not a
136+
// second per-route shape.
137+
expect(res.body.fields).toEqual([
138+
{ field: 'records.0.id', code: 'invalid_type', message: expect.any(String) },
139+
]);
135140
expect(updateManyData).not.toHaveBeenCalled();
136141
});
137142
});

packages/rest/src/rest-delete-many-ingress.test.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,12 @@ describe('POST /data/:object/deleteMany — ingress validation (#3897)', () => {
109109

110110
expect(res.statusCode).toBe(400);
111111
expect(res.body.code).toBe('VALIDATION_FAILED');
112+
// Same `fields[]` envelope a validator-thrown VALIDATION_FAILED uses
113+
// (#3918 / wire-format §7) — one shape per code on the wire.
114+
expect(res.body.fields).toEqual([
115+
{ field: 'ids.0', code: 'invalid_type', message: expect.any(String) },
116+
]);
117+
expect(res.body.object).toBe('invoice');
112118
expect(deleteManyData).not.toHaveBeenCalled();
113119
});
114120

packages/rest/src/rest-server.ts

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,25 @@ const TRANSLATABLE_META_TYPES = new Set(['view', 'action', 'object', 'app', 'das
7676
* not permitted …" — trips the `'<obj>' … not` substring check and
7777
* returns a misleading 404.
7878
*/
79+
/**
80+
* Zod issues → the data surface's `fields[]` validation envelope
81+
* (`{ field, code, message }`, docs/api/wire-format §7).
82+
*
83+
* A schema `.parse()` at a route ingress must report failures in the SAME shape
84+
* a validator-thrown `VALIDATION_FAILED` does through {@link mapDataError}
85+
* (#3918) — otherwise a client keying on `fields` has to learn a second shape
86+
* per route, and `code: 'VALIDATION_FAILED'` stops meaning one thing on the
87+
* wire.
88+
*/
89+
export function zodIssuesToFields(issues: unknown): Array<{ field: string; code: string; message: string }> {
90+
if (!Array.isArray(issues)) return [];
91+
return issues.map((i: any) => ({
92+
field: Array.isArray(i?.path) ? i.path.join('.') : String(i?.path ?? ''),
93+
code: String(i?.code ?? 'invalid'),
94+
message: String(i?.message ?? 'Invalid value'),
95+
}));
96+
}
97+
7998
export function mapDataError(error: any, object?: string): { status: number; body: Record<string, unknown> } {
8099
// Referential-integrity restrict on delete → 409 with the dependent count.
81100
// Surfaced FIRST so the structured fields survive the generic catch-alls.
@@ -6836,8 +6855,8 @@ export class RestServer {
68366855
res.status(400).json({
68376856
error: 'Invalid updateMany request',
68386857
code: 'VALIDATION_FAILED',
6858+
fields: zodIssuesToFields(parsedUpdate.error?.issues),
68396859
object: req.params?.object,
6840-
issues: parsedUpdate.error?.issues,
68416860
});
68426861
return;
68436862
}
@@ -6897,8 +6916,8 @@ export class RestServer {
68976916
res.status(400).json({
68986917
error: 'Invalid deleteMany request',
68996918
code: 'VALIDATION_FAILED',
6919+
fields: zodIssuesToFields(parsed.error?.issues),
69006920
object: req.params?.object,
6901-
issues: parsed.error?.issues,
69026921
});
69036922
return;
69046923
}

0 commit comments

Comments
 (0)