Skip to content

Commit

Permalink
feat: implement typed Input/Output interface for resolvers
Browse files Browse the repository at this point in the history
  • Loading branch information
jorisre committed Feb 26, 2025
1 parent ded1746 commit cf569f3
Show file tree
Hide file tree
Showing 2 changed files with 98 additions and 5 deletions.
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

0 comments on commit cf569f3

Please # to comment.