feat(frontend): add Zod runtime validation at API boundary

Introduces Zod 4 schemas for response validation on the three highest-traffic
endpoints (server/status, nodes/list, setting/all) and a Zod->AntD form rule
adapter, replacing the duplicated per-file ApiMsg<T> interfaces. Validation
runs safeParse with console.warn + raw-payload fallback so backend drift never
breaks the UI for users.

Login form switches to schema-driven rules as the proof-of-life for the
adapter. Class-based models stay untouched; remaining query/mutation hooks
and form modals will migrate in follow-ups.
This commit is contained in:
MHSanaei
2026-05-25 16:02:27 +02:00
parent 20edaee8ed
commit 6846fac1cc
13 changed files with 266 additions and 58 deletions

View File

@@ -0,0 +1,15 @@
import type { Rule } from 'antd/es/form';
import type { TFunction } from 'i18next';
import type { z } from 'zod';
export function antdRule<T extends z.ZodTypeAny>(schema: T, t: TFunction): Rule {
return {
validator: async (_rule, value) => {
const result = schema.safeParse(value);
if (result.success) return;
const issue = result.error.issues[0];
const key = issue?.message ?? 'validation.invalid';
throw new Error(t(key, { defaultValue: key }));
},
};
}

View File

@@ -0,0 +1,18 @@
import type { z } from 'zod';
import { Msg } from '@/utils';
export function parseMsg<T extends z.ZodTypeAny>(
msg: Msg<unknown>,
schema: T,
context: string,
): Msg<z.infer<T>> {
if (!msg.success || msg.obj == null) {
return msg as Msg<z.infer<T>>;
}
const result = schema.safeParse(msg.obj);
if (!result.success) {
console.warn(`[zod] ${context} response failed validation`, result.error.issues);
return msg as Msg<z.infer<T>>;
}
return new Msg<z.infer<T>>(msg.success, msg.msg, result.data);
}