Маппинг серверных ошибок
Автоматический маппинг ошибок Prisma, ZenStack и Zod на поля формы
Обзор
mapServerErrors() автоматически определяет формат ошибки и маппит её на поля формы.
import { applyServerErrors, mapServerErrors } from '@letar/forms'
;<Form
schema={UserSchema}
onSubmit={async ({ value }) => {
try {
await createUser(value)
} catch (error) {
const mapped = mapServerErrors(error, {
fieldMap: {
email: { field: 'email', message: 'Этот email уже зарегистрирован' },
},
})
applyServerErrors(form, mapped)
}
}}
>
<Form.Field.String name="email" />
<Form.Errors />
<Form.Button.Submit>Создать</Form.Button.Submit>
</Form>С декларативным <Form>
Пример выше использует низкоуровневый useAppForm — там form доступен напрямую из замыкания. Декларативная обёртка (createForm() → <Form onSubmit={(data) => ...}>) устроена иначе: её onSubmit получает только data, без инстанса формы, а обработка ошибок вынесена в middleware.onError — туда попадает то, что бросил сам onSubmit/Server Action (FormSimple перехватывает исключение и вызывает middleware.onError, см. libs/forms/src/lib/declarative/form-root/form-simple.tsx).
Чтобы применить mapServerErrors/applyServerErrors в этом случае, нужен доступ к инстансу формы снаружи onSubmit — для этого useFormRef():
import { applyServerErrors, mapServerErrors, useFormRef } from '@letar/forms'
function MaterialForm() {
const formRef = useFormRef()
async function handleSubmit(data: MaterialFormData) {
// Server Action просто бросает ошибку (Prisma/ZenStack/Error) — оборачивать вручную не нужно
await createMaterial(data)
}
return (
<Form
schema={MaterialSchema}
initialValue={initialValue}
onSubmit={handleSubmit}
formRef={formRef}
middleware={{
onError: (error) => {
const mapped = mapServerErrors(error, {
fieldMap: { sku: { field: 'sku', message: 'Такой артикул уже используется' } },
})
if (formRef.current) {
applyServerErrors(formRef.current, mapped)
}
},
}}
>
<Form.Errors />
<Form.Field.String name="sku" />
<Form.Button.Submit>Сохранить</Form.Button.Submit>
</Form>
)
}Ключевые моменты:
formRefпередаётся в<Form>и используется внутриmiddleware.onError— без негоapplyServerErrorsнекуда применять ошибки.- Server Action, вызываемый из
onSubmit, не должен ловить и оборачивать ошибку сам —mapServerErrorsумеет разбирать «сырые» Prisma/ZenStack/Zod исключения. - Не забудь
<Form.Errors />в JSX — иначеformErrors(напримерP2025/rejected-by-policy) будут применены к форме, но нигде не отрисуются. - Рабочий пример —
apps/domwellbes/src/app/(admin)/admin/materials/_components/material-form.tsx.
Поддерживаемые форматы
| Формат | Пример | Результат |
|---|---|---|
| Prisma P2002 | { code: 'P2002', meta: { target: ['email'] } } | fieldErrors: [{ field: 'email' }] |
| Prisma P2003 | { code: 'P2003', meta: { field_name: 'categoryId' } } | fieldErrors: [{ field: 'categoryId' }] |
| Prisma P2025 | { code: 'P2025' } | formErrors: ['Запись не найдена'] |
| ZenStack policy | { reason: 'rejected-by-policy' } | formErrors: ['Нет доступа'] |
| ZenStack db-query | { reason: 'db-query-error', code: 'P2002', meta } | Делегация Prisma |
| Zod flatten | { fieldErrors: { email: ['msg'] } } | Прямой маппинг |
| ActionResult | { success: false, error: 'msg' } | formErrors: ['msg'] |
API
mapServerErrors(error, config?)
const mapped = mapServerErrors(error, {
fieldMap: Record<string, { field: string; message: string }>,
format: 'auto' | 'prisma' | 'zenstack' | 'zod' | 'action-result',
locale: 'ru' | 'en',
defaultMessage: 'Произошла ошибка',
})applyServerErrors(form, mapped)
Применяет ошибки к TanStack Form через form.setFieldMeta и form.setErrorMap.
Импорт
import { applyServerErrors, mapServerErrors } from '@letar/forms'
import { mapServerErrors } from '@letar/forms/server-errors'
import { parsePrismaError, parseZenStackError } from '@letar/forms/server-errors'