Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: implement typed Input/Output interface for resolvers #753

Draft
wants to merge 23 commits into
base: dev
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 1 commit
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
84 changes: 84 additions & 0 deletions zod/src/__tests__/zod.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { FieldValues, Resolver, SubmitHandler, useForm } from 'react-hook-form';
import { z } from 'zod';
import { zodResolver } from '..';
import { fields, invalidData, schema, validData } from './__fixtures__/data';

Expand Down Expand Up @@ -89,4 +91,86 @@ describe('zodResolver', () => {

await expect(promise).rejects.toThrow('custom error');
});

/**
* Type inference tests
*/
it('should correctly infer the output type from a zod schema', () => {
const resolver = zodResolver(z.object({ id: z.number() }));

expectTypeOf(resolver).toEqualTypeOf<
Resolver<FieldValues, any, { id: number }>
>();
});

it('should correctly infer the output type from a zod schema when a different input type is specified', () => {
const schema = z.object({ id: z.string() });
const resolver = zodResolver<{ id: string }, any, z.output<typeof schema>>(
schema,
);

expectTypeOf(resolver).toEqualTypeOf<
Resolver<{ id: string }, any, { id: string }>
>();
});

it('should correctly infer the output type from a zod schema when a different input type is specified (only input type is specified)', () => {
const resolver = zodResolver<{ id: string }>(z.object({ id: z.string() }));

expectTypeOf(resolver).toEqualTypeOf<
Resolver<{ id: string }, any, { id: string }>
>();
});

it('should correctly infer the output type from a zod schema when different input and output types are specified', () => {
const resolver = zodResolver<
{ id: string },
{ context: any },
{ id: boolean }
>(z.object({ id: z.number() }));

expectTypeOf(resolver).toEqualTypeOf<
Resolver<{ id: string }, { context: any }, { id: boolean }>
>();
});

it('should correctly infer the output type from a Zod schema for the handleSubmit function in useForm', () => {
const { handleSubmit } = useForm({
resolver: zodResolver(z.object({ id: z.number() })),
});

expectTypeOf(handleSubmit).parameter(0).toEqualTypeOf<
SubmitHandler<{
id: number;
}>
>();
});

it('should correctly infer the output type from a Zod schema when a different input type is specified for the handleSubmit function in useForm', () => {
const { handleSubmit } = useForm({
resolver: zodResolver<{ id: number }>(z.object({ id: z.string() })),
});

expectTypeOf(handleSubmit).parameter(0).toEqualTypeOf<
SubmitHandler<{
id: string;
}>
>();
});

it('should correctly infer the output type from a Zod schema when different input and output types are specified for the handleSubmit function in useForm', () => {
const resolver = zodResolver<{ id: string }, any, { id: boolean }>(
z.object({ id: z.number() }),
);

const { handleSubmit } = useForm({
resolver,
});

expectTypeOf(handleSubmit).parameter(0).toEqualTypeOf<
SubmitHandler<{
id: boolean;
}>
>();
});
});
19 changes: 14 additions & 5 deletions zod/src/zod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,12 +63,12 @@ function parseErrorSchema(

/**
* Creates a resolver function for react-hook-form that validates form data using a Zod schema
* @param {z.ZodSchema<TFieldValues>} schema - The Zod schema used to validate the form data
* @param {z.ZodSchema<Input>} schema - The Zod schema used to validate the form data
* @param {Partial<z.ParseParams>} [schemaOptions] - Optional configuration options for Zod parsing
* @param {Object} [resolverOptions] - Optional resolver-specific configuration
* @param {('async'|'sync')} [resolverOptions.mode='async'] - Validation mode. Use 'sync' for synchronous validation
* @param {boolean} [resolverOptions.raw=false] - If true, returns the raw form values instead of the parsed data
* @returns {Resolver<z.infer<typeof schema>>} A resolver function compatible with react-hook-form
* @returns {Resolver<z.ouput<typeof schema>>} A resolver function compatible with react-hook-form
* @throws {Error} Throws if validation fails with a non-Zod error
* @example
* const schema = z.object({
Expand All @@ -80,14 +80,23 @@ function parseErrorSchema(
* resolver: zodResolver(schema)
* });
*/
export function zodResolver<TFieldValues extends FieldValues>(
schema: z.ZodSchema<TFieldValues, any, any>,
export function zodResolver<
Input extends FieldValues,
Context = any,
Output = undefined,
Schema extends z.ZodSchema<any, any, any> = z.ZodSchema<any, any, any>,
>(
schema: Schema,
schemaOptions?: Partial<z.ParseParams>,
resolverOptions: {
mode?: 'async' | 'sync';
raw?: boolean;
} = {},
): Resolver<z.infer<typeof schema>> {
): Resolver<
Input,
Context,
Output extends undefined ? z.output<Schema> : Output
> {
return async (values, _, options) => {
try {
const data = await schema[
Expand Down
Loading