diff --git a/docs/framework/angular/guides/basic-concepts.md b/docs/framework/angular/guides/basic-concepts.md index 09de629d9..d7d3a146b 100644 --- a/docs/framework/angular/guides/basic-concepts.md +++ b/docs/framework/angular/guides/basic-concepts.md @@ -132,7 +132,7 @@ import { z } from 'zod' state.canSubmit) diff --git a/examples/angular/yup/src/app/app.component.ts b/examples/angular/yup/src/app/app.component.ts index d73cb0e83..192faefe5 100644 --- a/examples/angular/yup/src/app/app.component.ts +++ b/examples/angular/yup/src/app/app.component.ts @@ -76,7 +76,7 @@ export class AppComponent { console.log(value) }, // Add a validator to support Zod usage in Form and Field - validatorAdapter: yupValidator, + validatorAdapter: yupValidator(), }) yup = yup diff --git a/examples/angular/zod/src/app/app.component.ts b/examples/angular/zod/src/app/app.component.ts index 605fd33b3..05fa8f456 100644 --- a/examples/angular/zod/src/app/app.component.ts +++ b/examples/angular/zod/src/app/app.component.ts @@ -79,7 +79,7 @@ export class AppComponent { console.log(value) }, // Add a validator to support Zod usage in Form and Field - validatorAdapter: zodValidator, + validatorAdapter: zodValidator(), }) z = z diff --git a/examples/react/valibot/src/index.tsx b/examples/react/valibot/src/index.tsx index e5da26039..608b948e7 100644 --- a/examples/react/valibot/src/index.tsx +++ b/examples/react/valibot/src/index.tsx @@ -27,7 +27,7 @@ export default function App() { console.log(value) }, // Add a validator to support Valibot usage in Form and Field - validatorAdapter: valibotValidator, + validatorAdapter: valibotValidator(), }) return ( diff --git a/examples/react/yup/src/index.tsx b/examples/react/yup/src/index.tsx index 66cd6b1f2..21d86fb90 100644 --- a/examples/react/yup/src/index.tsx +++ b/examples/react/yup/src/index.tsx @@ -27,7 +27,7 @@ export default function App() { console.log(value) }, // Add a validator to support Yup usage in Form and Field - validatorAdapter: yupValidator, + validatorAdapter: yupValidator(), }) return ( diff --git a/examples/react/zod/src/index.tsx b/examples/react/zod/src/index.tsx index 8a301db7e..5c3e8df02 100644 --- a/examples/react/zod/src/index.tsx +++ b/examples/react/zod/src/index.tsx @@ -27,7 +27,7 @@ export default function App() { console.log(value) }, // Add a validator to support Zod usage in Form and Field - validatorAdapter: zodValidator, + validatorAdapter: zodValidator(), }) return ( diff --git a/examples/solid/valibot/src/index.tsx b/examples/solid/valibot/src/index.tsx index 53b3be48c..95aca1be3 100644 --- a/examples/solid/valibot/src/index.tsx +++ b/examples/solid/valibot/src/index.tsx @@ -32,7 +32,7 @@ function App() { console.log(value) }, // Add a validator to support Valibot usage in Form and Field - validatorAdapter: valibotValidator, + validatorAdapter: valibotValidator(), })) return ( diff --git a/examples/solid/yup/src/index.tsx b/examples/solid/yup/src/index.tsx index b9a0516bf..e6a3ecc68 100644 --- a/examples/solid/yup/src/index.tsx +++ b/examples/solid/yup/src/index.tsx @@ -32,7 +32,7 @@ function App() { console.log(value) }, // Add a validator to support Yup usage in Form and Field - validatorAdapter: yupValidator, + validatorAdapter: yupValidator(), })) return ( diff --git a/examples/solid/zod/src/index.tsx b/examples/solid/zod/src/index.tsx index 33a6a887d..4b5472e4b 100644 --- a/examples/solid/zod/src/index.tsx +++ b/examples/solid/zod/src/index.tsx @@ -32,7 +32,7 @@ function App() { console.log(value) }, // Add a validator to support Zod usage in Form and Field - validatorAdapter: zodValidator, + validatorAdapter: zodValidator(), })) return ( diff --git a/examples/vue/valibot/src/App.vue b/examples/vue/valibot/src/App.vue index 0c0c31e09..b94b70ade 100644 --- a/examples/vue/valibot/src/App.vue +++ b/examples/vue/valibot/src/App.vue @@ -14,7 +14,7 @@ const form = useForm({ alert(JSON.stringify(value)) }, // Add a validator to support Valibot usage in Form and Field - validatorAdapter: valibotValidator, + validatorAdapter: valibotValidator(), }) const onChangeFirstName = v.pipeAsync( diff --git a/examples/vue/yup/src/App.vue b/examples/vue/yup/src/App.vue index b9a5e24f4..32d3c59e0 100644 --- a/examples/vue/yup/src/App.vue +++ b/examples/vue/yup/src/App.vue @@ -14,7 +14,7 @@ const form = useForm({ alert(JSON.stringify(value)) }, // Add a validator to support Yup usage in Form and Field - validatorAdapter: yupValidator, + validatorAdapter: yupValidator(), }) const onChangeFirstName = yup diff --git a/examples/vue/zod/src/App.vue b/examples/vue/zod/src/App.vue index 1e07e6eae..04d1a0f73 100644 --- a/examples/vue/zod/src/App.vue +++ b/examples/vue/zod/src/App.vue @@ -14,7 +14,7 @@ const form = useForm({ alert(JSON.stringify(value)) }, // Add a validator to support Zod usage in Form and Field - validatorAdapter: zodValidator, + validatorAdapter: zodValidator(), }) const onChangeFirstName = z.string().refine( diff --git a/packages/valibot-form-adapter/src/tests/FieldApi.spec.ts b/packages/valibot-form-adapter/src/tests/FieldApi.spec.ts index 7e5a452fb..695b33b29 100644 --- a/packages/valibot-form-adapter/src/tests/FieldApi.spec.ts +++ b/packages/valibot-form-adapter/src/tests/FieldApi.spec.ts @@ -14,7 +14,7 @@ describe('valibot field api', () => { const field = new FieldApi({ form, - validatorAdapter: valibotValidator, + validatorAdapter: valibotValidator(), name: 'name', validators: { onChange: v.pipe( @@ -44,7 +44,7 @@ describe('valibot field api', () => { const field = new FieldApi({ form, - validatorAdapter: valibotValidator, + validatorAdapter: valibotValidator(), name: 'name', validators: { onChange: ({ value }) => (value === 'a' ? 'Test' : undefined), @@ -69,7 +69,7 @@ describe('valibot field api', () => { const field = new FieldApi({ form, - validatorAdapter: valibotValidator, + validatorAdapter: valibotValidator(), name: 'name', validators: { onChangeAsync: v.pipeAsync( @@ -100,7 +100,7 @@ describe('valibot field api', () => { const field = new FieldApi({ form, - validatorAdapter: valibotValidator, + validatorAdapter: valibotValidator(), name: 'name', validators: { onChangeAsync: async ({ value }) => @@ -119,4 +119,37 @@ describe('valibot field api', () => { await sleep(10) expect(field.getMeta().errors).toEqual([]) }) + + it('should transform errors to display only the first error message', () => { + const form = new FormApi({ + defaultValues: { + name: '', + }, + }) + + const field = new FieldApi({ + form, + validatorAdapter: valibotValidator({ + transformErrors: (errors) => errors[0]?.message, + }), + name: 'name', + validators: { + onChange: v.pipe( + v.string(), + v.minLength(3, 'You must have a length of at least 3'), + v.uuid('UUID'), + ), + }, + }) + + field.mount() + + expect(field.getMeta().errors).toEqual([]) + field.setValue('aa', { touch: true }) + expect(field.getMeta().errors).toEqual([ + 'You must have a length of at least 3', + ]) + field.setValue('aaa', { touch: true }) + expect(field.getMeta().errors).toEqual(['UUID']) + }) }) diff --git a/packages/valibot-form-adapter/src/tests/FieldApi.test-d.ts b/packages/valibot-form-adapter/src/tests/FieldApi.test-d.ts index 19c6b0014..5ab61bc91 100644 --- a/packages/valibot-form-adapter/src/tests/FieldApi.test-d.ts +++ b/packages/valibot-form-adapter/src/tests/FieldApi.test-d.ts @@ -13,7 +13,7 @@ it('should allow a Valibot validator to be passed in', () => { const field = new FieldApi({ form, name: 'name', - validatorAdapter: valibotValidator, + validatorAdapter: valibotValidator(), } as const) }) @@ -27,7 +27,7 @@ it('should allow a Valibot validator to handle the correct Valibot type', () => const field = new FieldApi({ form, name: 'name', - validatorAdapter: valibotValidator, + validatorAdapter: valibotValidator(), validators: { onChange: v.string(), }, @@ -44,7 +44,7 @@ it('should allow a Valibot validator to handle the correct Valibot type for an a const field = new FieldApi({ form, name: 'name', - validatorAdapter: valibotValidator, + validatorAdapter: valibotValidator(), validators: { onChangeAsync: v.string(), }, @@ -61,7 +61,7 @@ it('should allow a functional onChange to be passed when using a validator', () const field = new FieldApi({ form, name: 'name', - validatorAdapter: valibotValidator, + validatorAdapter: valibotValidator(), validators: { onChange: ({ value }) => { assertType<'test'>(value) @@ -97,7 +97,7 @@ it.skip('should allow not a Valibot validator with the wrong Valibot type', () = const field = new FieldApi({ form, name: 'name', - validatorAdapter: valibotValidator, + validatorAdapter: valibotValidator(), validators: { onChange: v.object({}), }, diff --git a/packages/valibot-form-adapter/src/tests/FormApi.spec.ts b/packages/valibot-form-adapter/src/tests/FormApi.spec.ts index 251e326f2..ed20c0cbe 100644 --- a/packages/valibot-form-adapter/src/tests/FormApi.spec.ts +++ b/packages/valibot-form-adapter/src/tests/FormApi.spec.ts @@ -9,7 +9,7 @@ describe('valibot form api', () => { defaultValues: { name: '', }, - validatorAdapter: valibotValidator, + validatorAdapter: valibotValidator(), }) const field = new FieldApi({ @@ -39,7 +39,7 @@ describe('valibot form api', () => { defaultValues: { name: '', }, - validatorAdapter: valibotValidator, + validatorAdapter: valibotValidator(), }) const field = new FieldApi({ diff --git a/packages/valibot-form-adapter/src/tests/FormApi.test-d.ts b/packages/valibot-form-adapter/src/tests/FormApi.test-d.ts index 29c900587..dd13ce856 100644 --- a/packages/valibot-form-adapter/src/tests/FormApi.test-d.ts +++ b/packages/valibot-form-adapter/src/tests/FormApi.test-d.ts @@ -8,7 +8,7 @@ it('should allow a Valibot validator to be passed in', () => { defaultValues: { name: 'test', }, - validatorAdapter: valibotValidator, + validatorAdapter: valibotValidator(), } as const) }) @@ -17,7 +17,7 @@ it('should allow a Valibot validator to handle the correct Valibot type', () => defaultValues: { name: 'test', }, - validatorAdapter: valibotValidator, + validatorAdapter: valibotValidator(), } as const) const field = new FieldApi({ @@ -34,7 +34,7 @@ it('should allow a Valibot validator to handle the correct Valibot type on async defaultValues: { name: 'test', }, - validatorAdapter: valibotValidator, + validatorAdapter: valibotValidator(), } as const) const field = new FieldApi({ @@ -51,7 +51,7 @@ it('should allow a functional onChange to be passed when using a validator', () defaultValues: { name: 'test', }, - validatorAdapter: valibotValidator, + validatorAdapter: valibotValidator(), } as const) const field = new FieldApi({ @@ -92,7 +92,7 @@ it.skip('should allow not a Valibot validator with the wrong Valibot type', () = const field = new FieldApi({ form, name: 'name', - validatorAdapter: valibotValidator, + validatorAdapter: valibotValidator(), validators: { onChange: v.object({}), }, diff --git a/packages/valibot-form-adapter/src/validator.ts b/packages/valibot-form-adapter/src/validator.ts index b6ee6be3d..d89ca3188 100644 --- a/packages/valibot-form-adapter/src/validator.ts +++ b/packages/valibot-form-adapter/src/validator.ts @@ -1,23 +1,34 @@ import { safeParse, safeParseAsync } from 'valibot' import type { BaseIssue, BaseSchema, BaseSchemaAsync } from 'valibot' -import type { Validator } from '@tanstack/form-core' +import type { ValidationError, Validator } from '@tanstack/form-core' -export const valibotValidator = (() => { - return { - validate({ value }, fn) { - if (fn.async) return - const result = safeParse(fn, value) - if (result.success) return - return result.issues.map((i) => i.message).join(', ') - }, - async validateAsync({ value }, fn) { - const result = await safeParseAsync(fn, value) - if (result.success) return - return result.issues.map((i) => i.message).join(', ') - }, - } -}) as Validator< - unknown, - | BaseSchema> - | BaseSchemaAsync> -> +type Params = { + transformErrors?: (errors: BaseIssue[]) => ValidationError +} + +export const valibotValidator = (params: Params = {}) => + (() => { + return { + validate({ value }, fn) { + if (fn.async) return + const result = safeParse(fn, value) + if (result.success) return + if (params.transformErrors) { + return params.transformErrors(result.issues) + } + return result.issues.map((i) => i.message).join(', ') + }, + async validateAsync({ value }, fn) { + const result = await safeParseAsync(fn, value) + if (result.success) return + if (params.transformErrors) { + return params.transformErrors(result.issues) + } + return result.issues.map((i) => i.message).join(', ') + }, + } + }) as Validator< + unknown, + | BaseSchema> + | BaseSchemaAsync> + > diff --git a/packages/yup-form-adapter/src/tests/FieldApi.spec.ts b/packages/yup-form-adapter/src/tests/FieldApi.spec.ts index bb38eed9e..c9246c638 100644 --- a/packages/yup-form-adapter/src/tests/FieldApi.spec.ts +++ b/packages/yup-form-adapter/src/tests/FieldApi.spec.ts @@ -14,7 +14,7 @@ describe('yup field api', () => { const field = new FieldApi({ form, - validatorAdapter: yupValidator, + validatorAdapter: yupValidator(), name: 'name', validators: { onChange: yup.string().min(3, 'You must have a length of at least 3'), @@ -41,7 +41,7 @@ describe('yup field api', () => { const field = new FieldApi({ form, - validatorAdapter: yupValidator, + validatorAdapter: yupValidator(), name: 'name', validators: { onChange: ({ value }) => (value === 'a' ? 'Test' : undefined), @@ -66,7 +66,7 @@ describe('yup field api', () => { const field = new FieldApi({ form, - validatorAdapter: yupValidator, + validatorAdapter: yupValidator(), name: 'name', validators: { onChangeAsync: yup @@ -98,7 +98,7 @@ describe('yup field api', () => { const field = new FieldApi({ form, - validatorAdapter: yupValidator, + validatorAdapter: yupValidator(), name: 'name', validators: { onChangeAsync: async ({ value }) => @@ -117,4 +117,36 @@ describe('yup field api', () => { await sleep(10) expect(field.getMeta().errors).toEqual([]) }) + + it('should transform errors to display only the first error message', () => { + const form = new FormApi({ + defaultValues: { + name: '', + }, + }) + + const field = new FieldApi({ + form, + validatorAdapter: yupValidator({ + transformErrors: (errors) => errors[0], + }), + name: 'name', + validators: { + onChange: yup + .string() + .min(3, 'You must have a length of at least 3') + .uuid('UUID'), + }, + }) + + field.mount() + + expect(field.getMeta().errors).toEqual([]) + field.setValue('aa', { touch: true }) + expect(field.getMeta().errors).toEqual([ + 'You must have a length of at least 3', + ]) + field.setValue('aaa', { touch: true }) + expect(field.getMeta().errors).toEqual(['UUID']) + }) }) diff --git a/packages/yup-form-adapter/src/tests/FieldApi.test-d.ts b/packages/yup-form-adapter/src/tests/FieldApi.test-d.ts index f938d2b21..89686e92e 100644 --- a/packages/yup-form-adapter/src/tests/FieldApi.test-d.ts +++ b/packages/yup-form-adapter/src/tests/FieldApi.test-d.ts @@ -13,7 +13,7 @@ it('should allow a Zod validator to be passed in', () => { const field = new FieldApi({ form, name: 'name', - validatorAdapter: yupValidator, + validatorAdapter: yupValidator(), } as const) }) @@ -27,7 +27,7 @@ it('should allow a Zod validator to handle the correct Zod type', () => { const field = new FieldApi({ form, name: 'name', - validatorAdapter: yupValidator, + validatorAdapter: yupValidator(), validators: { onChange: yup.string(), }, @@ -44,7 +44,7 @@ it('should allow a functional onChange to be passed when using a validator', () const field = new FieldApi({ form, name: 'name', - validatorAdapter: yupValidator, + validatorAdapter: yupValidator(), validators: { onChange: ({ value }) => { assertType<'test'>(value) @@ -80,7 +80,7 @@ it.skip('should allow not a Zod validator with the wrong Zod type', () => { const field = new FieldApi({ form, name: 'name', - validatorAdapter: yupValidator, + validatorAdapter: yupValidator(), validators: { onChange: yup.object({}), }, diff --git a/packages/yup-form-adapter/src/tests/FormApi.spec.ts b/packages/yup-form-adapter/src/tests/FormApi.spec.ts index 3e3e17fa8..b632eed9e 100644 --- a/packages/yup-form-adapter/src/tests/FormApi.spec.ts +++ b/packages/yup-form-adapter/src/tests/FormApi.spec.ts @@ -10,7 +10,7 @@ describe('yup form api', () => { defaultValues: { name: '', }, - validatorAdapter: yupValidator, + validatorAdapter: yupValidator(), }) const field = new FieldApi({ @@ -37,7 +37,7 @@ describe('yup form api', () => { defaultValues: { name: '', }, - validatorAdapter: yupValidator, + validatorAdapter: yupValidator(), }) const field = new FieldApi({ diff --git a/packages/yup-form-adapter/src/tests/FormApi.test-d.ts b/packages/yup-form-adapter/src/tests/FormApi.test-d.ts index 42a1748fe..ecfa19671 100644 --- a/packages/yup-form-adapter/src/tests/FormApi.test-d.ts +++ b/packages/yup-form-adapter/src/tests/FormApi.test-d.ts @@ -8,7 +8,7 @@ it('should allow a Zod validator to be passed in', () => { defaultValues: { name: 'test', }, - validatorAdapter: yupValidator, + validatorAdapter: yupValidator(), } as const) }) @@ -17,7 +17,7 @@ it('should allow a Zod validator to handle the correct Zod type', () => { defaultValues: { name: 'test', }, - validatorAdapter: yupValidator, + validatorAdapter: yupValidator(), } as const) const field = new FieldApi({ @@ -34,7 +34,7 @@ it('should allow a functional onChange to be passed when using a validator', () defaultValues: { name: 'test', }, - validatorAdapter: yupValidator, + validatorAdapter: yupValidator(), } as const) const field = new FieldApi({ @@ -75,7 +75,7 @@ it.skip('should allow not a Zod validator with the wrong Zod type', () => { const field = new FieldApi({ form, name: 'name', - validatorAdapter: yupValidator, + validatorAdapter: yupValidator(), validators: { onChange: yup.object({}), }, diff --git a/packages/yup-form-adapter/src/validator.ts b/packages/yup-form-adapter/src/validator.ts index 0884b76aa..e151c8885 100644 --- a/packages/yup-form-adapter/src/validator.ts +++ b/packages/yup-form-adapter/src/validator.ts @@ -1,28 +1,40 @@ import type { AnySchema, ValidationError as YupError } from 'yup' -import type { ValidationError, Validator } from '@tanstack/form-core' +import type { ValidationError } from '@tanstack/form-core' -export const yupValidator = () => { - return { - validate({ value }: { value: unknown }, fn: AnySchema): ValidationError { - try { - fn.validateSync(value) - return - } catch (_e) { - const e = _e as YupError - return e.errors.join(', ') - } - }, - async validateAsync( - { value }: { value: unknown }, - fn: AnySchema, - ): Promise { - try { - await fn.validate(value) - return - } catch (_e) { - const e = _e as YupError - return e.errors.join(', ') - } - }, - } +type Params = { + transformErrors?: (errors: string[]) => ValidationError } + +export const yupValidator = + (params: Params = {}) => + () => { + return { + validate({ value }: { value: unknown }, fn: AnySchema): ValidationError { + try { + fn.validateSync(value) + return + } catch (_e) { + const e = _e as YupError + if (params.transformErrors) { + return params.transformErrors(e.errors) + } + return e.errors.join(', ') + } + }, + async validateAsync( + { value }: { value: unknown }, + fn: AnySchema, + ): Promise { + try { + await fn.validate(value) + return + } catch (_e) { + const e = _e as YupError + if (params.transformErrors) { + return params.transformErrors(e.errors) + } + return e.errors.join(', ') + } + }, + } + } diff --git a/packages/zod-form-adapter/src/tests/FieldApi.spec.ts b/packages/zod-form-adapter/src/tests/FieldApi.spec.ts index 2393523ae..af166133a 100644 --- a/packages/zod-form-adapter/src/tests/FieldApi.spec.ts +++ b/packages/zod-form-adapter/src/tests/FieldApi.spec.ts @@ -15,7 +15,7 @@ describe('zod field api', () => { const field = new FieldApi({ form, - validatorAdapter: zodValidator, + validatorAdapter: zodValidator(), name: 'name', validators: { onChange: z.string().min(3, 'You must have a length of at least 3'), @@ -42,7 +42,7 @@ describe('zod field api', () => { const field = new FieldApi({ form, - validatorAdapter: zodValidator, + validatorAdapter: zodValidator(), name: 'name', validators: { onChange: ({ value }) => (value === 'a' ? 'Test' : undefined), @@ -67,7 +67,7 @@ describe('zod field api', () => { const field = new FieldApi({ form, - validatorAdapter: zodValidator, + validatorAdapter: zodValidator(), name: 'name', validators: { onChangeAsync: z.string().refine(async (val) => val.length > 3, { @@ -97,7 +97,7 @@ describe('zod field api', () => { const field = new FieldApi({ form, - validatorAdapter: zodValidator, + validatorAdapter: zodValidator(), name: 'name', validators: { onChangeAsync: async ({ value }) => @@ -116,4 +116,36 @@ describe('zod field api', () => { await sleep(10) expect(field.getMeta().errors).toEqual([]) }) + + it('should transform errors to display only the first error message', () => { + const form = new FormApi({ + defaultValues: { + name: '', + }, + }) + + const field = new FieldApi({ + form, + validatorAdapter: zodValidator({ + transformErrors: (errors) => errors[0]?.message, + }), + name: 'name', + validators: { + onChange: z + .string() + .min(3, 'You must have a length of at least 3') + .uuid('UUID'), + }, + }) + + field.mount() + + expect(field.getMeta().errors).toEqual([]) + field.setValue('aa', { touch: true }) + expect(field.getMeta().errors).toEqual([ + 'You must have a length of at least 3', + ]) + field.setValue('aaa', { touch: true }) + expect(field.getMeta().errors).toEqual(['UUID']) + }) }) diff --git a/packages/zod-form-adapter/src/tests/FieldApi.test-d.ts b/packages/zod-form-adapter/src/tests/FieldApi.test-d.ts index efa077993..ebd2b3dcb 100644 --- a/packages/zod-form-adapter/src/tests/FieldApi.test-d.ts +++ b/packages/zod-form-adapter/src/tests/FieldApi.test-d.ts @@ -13,7 +13,7 @@ it('should allow a Zod validator to be passed in', () => { const field = new FieldApi({ form, name: 'name', - validatorAdapter: zodValidator, + validatorAdapter: zodValidator(), }) }) @@ -27,7 +27,7 @@ it('should allow a Zod validator to handle the correct Zod type', () => { const field = new FieldApi({ form, name: 'name', - validatorAdapter: zodValidator, + validatorAdapter: zodValidator(), validators: { onChange: z.string(), }, @@ -44,7 +44,7 @@ it('should allow a Zod validator to handle the correct Zod type for an async met const field = new FieldApi({ form, name: 'name', - validatorAdapter: zodValidator, + validatorAdapter: zodValidator(), validators: { onChangeAsync: z.string(), }, @@ -61,7 +61,7 @@ it('should allow a functional onChange to be passed when using a validator', () const field = new FieldApi({ form, name: 'name', - validatorAdapter: zodValidator, + validatorAdapter: zodValidator(), validators: { onChange: ({ value }) => { assertType<'test'>(value) @@ -97,7 +97,7 @@ it.skip('should allow not a Zod validator with the wrong Zod type', () => { const field = new FieldApi({ form, name: 'name', - validatorAdapter: zodValidator, + validatorAdapter: zodValidator(), validators: { onChange: z.object({}), }, diff --git a/packages/zod-form-adapter/src/tests/FormApi.spec.ts b/packages/zod-form-adapter/src/tests/FormApi.spec.ts index b189485cd..69817571f 100644 --- a/packages/zod-form-adapter/src/tests/FormApi.spec.ts +++ b/packages/zod-form-adapter/src/tests/FormApi.spec.ts @@ -10,7 +10,7 @@ describe('zod form api', () => { defaultValues: { name: '', }, - validatorAdapter: zodValidator, + validatorAdapter: zodValidator(), }) const field = new FieldApi({ @@ -37,7 +37,7 @@ describe('zod form api', () => { defaultValues: { name: '', }, - validatorAdapter: zodValidator, + validatorAdapter: zodValidator(), }) const field = new FieldApi({ diff --git a/packages/zod-form-adapter/src/tests/FormApi.test-d.ts b/packages/zod-form-adapter/src/tests/FormApi.test-d.ts index 8d320f959..330dae086 100644 --- a/packages/zod-form-adapter/src/tests/FormApi.test-d.ts +++ b/packages/zod-form-adapter/src/tests/FormApi.test-d.ts @@ -8,7 +8,7 @@ it('should allow a Zod validator to be passed in', () => { defaultValues: { name: 'test', }, - validatorAdapter: zodValidator, + validatorAdapter: zodValidator(), } as const) }) @@ -17,7 +17,7 @@ it('should allow a Zod validator to handle the correct Zod type', () => { defaultValues: { name: 'test', }, - validatorAdapter: zodValidator, + validatorAdapter: zodValidator(), } as const) const field = new FieldApi({ @@ -34,7 +34,7 @@ it('should allow a Zod validator to handle the correct Zod type on async methods defaultValues: { name: 'test', }, - validatorAdapter: zodValidator, + validatorAdapter: zodValidator(), } as const) const field = new FieldApi({ @@ -51,7 +51,7 @@ it('should allow a functional onChange to be passed when using a validator', () defaultValues: { name: 'test', }, - validatorAdapter: zodValidator, + validatorAdapter: zodValidator(), } as const) const field = new FieldApi({ @@ -92,7 +92,7 @@ it.skip('should allow not a Zod validator with the wrong Zod type', () => { const field = new FieldApi({ form, name: 'name', - validatorAdapter: zodValidator, + validatorAdapter: zodValidator(), validators: { onChange: z.object({}), }, diff --git a/packages/zod-form-adapter/src/validator.ts b/packages/zod-form-adapter/src/validator.ts index 1bb0aa08c..a6d31563e 100644 --- a/packages/zod-form-adapter/src/validator.ts +++ b/packages/zod-form-adapter/src/validator.ts @@ -1,30 +1,46 @@ -import type { SafeParseError, ZodType, ZodTypeAny } from 'zod' -import type { ValidationError, Validator } from '@tanstack/form-core' +import type { ValidationError } from '@tanstack/form-core' +import type { SafeParseError, ZodIssue, ZodType, ZodTypeAny } from 'zod' -export const zodValidator = () => { - return { - validate({ value }: { value: unknown }, fn: ZodType): ValidationError { - // Call Zod on the value here and return the error message - const result = (fn as ZodTypeAny).safeParse(value) - if (!result.success) { - return (result as SafeParseError).error.issues - .map((issue) => issue.message) - .join(', ') - } - return - }, - async validateAsync( - { value }: { value: unknown }, - fn: ZodType, - ): Promise { - // Call Zod on the value here and return the error message - const result = await (fn as ZodTypeAny).safeParseAsync(value) - if (!result.success) { - return (result as SafeParseError).error.issues - .map((issue) => issue.message) - .join(', ') - } - return - }, - } +type Params = { + transformErrors?: (errors: ZodIssue[]) => ValidationError } + +export const zodValidator = + (params: Params = {}) => + () => { + return { + validate({ value }: { value: unknown }, fn: ZodType): ValidationError { + // Call Zod on the value here and return the error message + const result = (fn as ZodTypeAny).safeParse(value) + if (!result.success) { + if (params.transformErrors) { + return params.transformErrors( + (result as SafeParseError).error.issues, + ) + } + return (result as SafeParseError).error.issues + .map((issue) => issue.message) + .join(', ') + } + return + }, + async validateAsync( + { value }: { value: unknown }, + fn: ZodType, + ): Promise { + // Call Zod on the value here and return the error message + const result = await (fn as ZodTypeAny).safeParseAsync(value) + if (!result.success) { + if (params.transformErrors) { + return params.transformErrors( + (result as SafeParseError).error.issues, + ) + } + return (result as SafeParseError).error.issues + .map((issue) => issue.message) + .join(', ') + } + return + }, + } + }