Skip to content

Commit

Permalink
feat!: add ability to transform errors from validators. To migrate, c…
Browse files Browse the repository at this point in the history
…all your validators `validator: yupValidator()`, not `validator: yupValidator` (#755)

* feat: add errorMap params to the valibot validator

* fix: update all valibotValidator calls

* refactor: rename errorMap to transformErrors

* fix: transformErrors should return a string

* feat: add transformErrors to the yup validator

* test: add yup validator transformErrors base test

* chore: remove unused import from yup validator

* feat: add transformErrors params to the zod validator

* Apply suggestions from code review

Co-authored-by: Leonardo Montini <[email protected]>

* chore: missing docs references

* chore: fix prettier

* chore: fix import

* chore: fix valibot typings and such

---------

Co-authored-by: Corbin Crutchley <[email protected]>
Co-authored-by: Leonardo Montini <[email protected]>
Co-authored-by: Corbin Crutchley <[email protected]>
  • Loading branch information
4 people authored Jun 19, 2024
1 parent 601aa22 commit b67bd8d
Show file tree
Hide file tree
Showing 31 changed files with 272 additions and 136 deletions.
2 changes: 1 addition & 1 deletion docs/framework/angular/guides/basic-concepts.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ import { z } from 'zod'
<ng-container
[tanstackField]="form"
name="firstName"
[validatorAdapter]="zodValidator"
[validatorAdapter]="zodValidator()"
[validators]="{
onChange: z.string().min(3, 'First name must be at least 3 characters'),
onChangeAsyncDebounceMs: 500,
Expand Down
2 changes: 1 addition & 1 deletion docs/framework/angular/guides/validation.md
Original file line number Diff line number Diff line change
Expand Up @@ -422,7 +422,7 @@ import { z } from 'zod'
export class AppComponent {
form = injectForm({
// Either add the validator here or on `Field`
validatorAdapter: zodValidator,
validatorAdapter: zodValidator(),
// ...
})

Expand Down
2 changes: 1 addition & 1 deletion docs/framework/react/guides/basic-concepts.md
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ import { z } from 'zod'
// ...
<form.Field
name="firstName"
validatorAdapter={zodValidator}
validatorAdapter={zodValidator()}
validators={{
onChange: z.string().min(3, 'First name must be at least 3 characters'),
onChangeAsyncDebounceMs: 500,
Expand Down
4 changes: 2 additions & 2 deletions docs/framework/react/guides/validation.md
Original file line number Diff line number Diff line change
Expand Up @@ -334,13 +334,13 @@ import { z } from 'zod'

const form = useForm({
// Either add the validator here or on `Field`
validatorAdapter: zodValidator,
validatorAdapter: zodValidator(),
// ...
})

<form.Field
name="age"
validatorAdapter={zodValidator}
validatorAdapter={zodValidator()}
validators={{
onChange: z.number().gte(13, 'You must be 13 to make an account'),
}}
Expand Down
2 changes: 1 addition & 1 deletion examples/angular/valibot/src/app/app.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ export class AppComponent {
console.log(value)
},
// Add a validator to support Zod usage in Form and Field
validatorAdapter: valibotValidator,
validatorAdapter: valibotValidator(),
})

canSubmit = injectStore(this.form, (state) => state.canSubmit)
Expand Down
2 changes: 1 addition & 1 deletion examples/angular/yup/src/app/app.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion examples/angular/zod/src/app/app.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion examples/react/valibot/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down
2 changes: 1 addition & 1 deletion examples/react/yup/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down
2 changes: 1 addition & 1 deletion examples/react/zod/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down
2 changes: 1 addition & 1 deletion examples/solid/valibot/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down
2 changes: 1 addition & 1 deletion examples/solid/yup/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down
2 changes: 1 addition & 1 deletion examples/solid/zod/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down
2 changes: 1 addition & 1 deletion examples/vue/valibot/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
2 changes: 1 addition & 1 deletion examples/vue/yup/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion examples/vue/zod/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
41 changes: 37 additions & 4 deletions packages/valibot-form-adapter/src/tests/FieldApi.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ describe('valibot field api', () => {

const field = new FieldApi({
form,
validatorAdapter: valibotValidator,
validatorAdapter: valibotValidator(),
name: 'name',
validators: {
onChange: v.pipe(
Expand Down Expand Up @@ -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),
Expand All @@ -69,7 +69,7 @@ describe('valibot field api', () => {

const field = new FieldApi({
form,
validatorAdapter: valibotValidator,
validatorAdapter: valibotValidator(),
name: 'name',
validators: {
onChangeAsync: v.pipeAsync(
Expand Down Expand Up @@ -100,7 +100,7 @@ describe('valibot field api', () => {

const field = new FieldApi({
form,
validatorAdapter: valibotValidator,
validatorAdapter: valibotValidator(),
name: 'name',
validators: {
onChangeAsync: async ({ value }) =>
Expand All @@ -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'])
})
})
10 changes: 5 additions & 5 deletions packages/valibot-form-adapter/src/tests/FieldApi.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
})

Expand All @@ -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(),
},
Expand All @@ -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(),
},
Expand All @@ -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)
Expand Down Expand Up @@ -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({}),
},
Expand Down
4 changes: 2 additions & 2 deletions packages/valibot-form-adapter/src/tests/FormApi.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ describe('valibot form api', () => {
defaultValues: {
name: '',
},
validatorAdapter: valibotValidator,
validatorAdapter: valibotValidator(),
})

const field = new FieldApi({
Expand Down Expand Up @@ -39,7 +39,7 @@ describe('valibot form api', () => {
defaultValues: {
name: '',
},
validatorAdapter: valibotValidator,
validatorAdapter: valibotValidator(),
})

const field = new FieldApi({
Expand Down
10 changes: 5 additions & 5 deletions packages/valibot-form-adapter/src/tests/FormApi.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ it('should allow a Valibot validator to be passed in', () => {
defaultValues: {
name: 'test',
},
validatorAdapter: valibotValidator,
validatorAdapter: valibotValidator(),
} as const)
})

Expand All @@ -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({
Expand All @@ -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({
Expand All @@ -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({
Expand Down Expand Up @@ -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({}),
},
Expand Down
51 changes: 31 additions & 20 deletions packages/valibot-form-adapter/src/validator.ts
Original file line number Diff line number Diff line change
@@ -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<unknown, unknown, BaseIssue<unknown>>
| BaseSchemaAsync<unknown, unknown, BaseIssue<unknown>>
>
type Params = {
transformErrors?: (errors: BaseIssue<unknown>[]) => 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<unknown, unknown, BaseIssue<unknown>>
| BaseSchemaAsync<unknown, unknown, BaseIssue<unknown>>
>
Loading

0 comments on commit b67bd8d

Please sign in to comment.