Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions zod/src/__tests__/__snapshots__/zod.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,46 @@ Object {
}
`;

exports[`zodResolver should return a single error from zodResolver with \`mode: sync\` when validation fails 1`] = `
Object {
"errors": Object {
"birthYear": Object {
"message": "Invalid input",
"type": "invalid_union",
},
"confirm": Object {
"message": "Passwords don't match",
"type": "custom_error",
},
"email": Object {
"message": "Invalid email",
"type": "invalid_string",
},
"enabled": Object {
"message": "Required",
"type": "invalid_type",
},
"password": Object {
"message": "Invalid",
"type": "invalid_string",
},
"repeatPassword": Object {
"message": "Required",
"type": "invalid_type",
},
"tags": Object {
"message": "Required",
"type": "invalid_type",
},
"username": Object {
"message": "Required",
"type": "invalid_type",
},
},
"values": Object {},
}
`;

exports[`zodResolver should return all the errors from zodResolver when validation fails with \`validateAllFieldCriteria\` set to true 1`] = `
Object {
"errors": Object {
Expand Down
28 changes: 28 additions & 0 deletions zod/src/__tests__/zod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,22 @@ describe('zodResolver', () => {
expect(result).toEqual({ errors: {}, values: data });
});

it('should return values from zodResolver with `mode: sync` when validation pass', async () => {
const data: z.infer<typeof schema> = {
username: 'Doe',
password: 'Password123',
repeatPassword: 'Password123',
birthYear: 2000,
email: 'john@doe.com',
tags: ['tag1', 'tag2'],
enabled: true,
};

const result = await zodResolver(schema, undefined, { mode: 'sync' })(data);

expect(result).toEqual({ errors: {}, values: data });
});

it('should return a single error from zodResolver when validation fails', async () => {
const data = {
password: '___',
Expand All @@ -46,6 +62,18 @@ describe('zodResolver', () => {
expect(result).toMatchSnapshot();
});

it('should return a single error from zodResolver with `mode: sync` when validation fails', async () => {
const data = {
password: '___',
email: '',
birthYear: 'birthYear',
};

const result = await zodResolver(schema, undefined, { mode: 'sync' })(data);

expect(result).toMatchSnapshot();
});

it('should return all the errors from zodResolver when validation fails with `validateAllFieldCriteria` set to true', async () => {
const data = {
password: '___',
Expand Down
3 changes: 2 additions & 1 deletion zod/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import type { ParseParams } from 'zod/lib/src/parser';

export type Resolver = <T extends z.ZodSchema<any, any>>(
schema: T,
options?: ParseParams,
schemaOptions?: ParseParams,
resolverOptions?: { mode: 'async' | 'sync' },
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

to use this feature, i guess users will have to this:

resolver: async (data, options) => await zodResolver(schema, options, 'async')

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No they have to this:

useForm({
  resolver: zodResolver(schema, options, {mode: 'sync'})
})

Or if you prefer, we can do the following:

interface Options {
  schema?: ParseParams;
  resolver?: { mode: 'async' | 'sync' }
}

export type Resolver = <T extends z.ZodSchema<any, any>>(
  schema: T,
  options?: Options
) => { ... }

So user will have to this:

useForm({
  resolver: zodResolver(schema, {
    resolver: { mode: 'sync' },
    schema: schemaZodOptions
  })
})

I think I prefer this one, what do you think ?

FYI: async is the default mode

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks great:

useForm({
  resolver: zodResolver(schema, options, {mode: 'sync'})
})

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

options is optional so it could be:

useForm({
  resolver: zodResolver(schema, undefined, { mode: 'sync' })
})

) => <TFieldValues extends FieldValues, TContext>(
values: UnpackNestedValue<TFieldValues>,
context?: TContext,
Expand Down
33 changes: 18 additions & 15 deletions zod/src/zod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,21 +47,24 @@ const parseErrorSchema = (
);
};

export const zodResolver: Resolver = (schema, options) => async (
values,
_,
validateAllFieldCriteria = false,
) => {
const result = schema.safeParse(values, options);
export const zodResolver: Resolver = (
schema,
schemaOptions,
{ mode } = { mode: 'async' },
) => async (values, _, validateAllFieldCriteria = false) => {
try {
const result =
mode === 'async'
? await schema.parseAsync(values, schemaOptions)
: schema.parse(values, schemaOptions);

if (result.success) {
return { values: result.data, errors: {} };
return { values: result, errors: {} };
} catch (error) {
return {
values: {},
errors: transformToNestObject(
parseErrorSchema(error, validateAllFieldCriteria),
),
};
}

return {
values: {},
errors: transformToNestObject(
parseErrorSchema(result.error, validateAllFieldCriteria),
),
};
};