Skip to content

Commit

Permalink
fix: resolvers fails silently (#528)
Browse files Browse the repository at this point in the history
  • Loading branch information
jorisre committed Mar 20, 2023
1 parent 2749bd9 commit 6677e0e
Show file tree
Hide file tree
Showing 11 changed files with 129 additions and 37 deletions.
16 changes: 16 additions & 0 deletions computed-types/src/__tests__/computed-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,20 @@ describe('computedTypesResolver', () => {

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

it('should throw any error unrelated to computed-types', async () => {
const schemaWithCustomError = schema.transform(() => {
throw Error('custom error');
});
const promise = computedTypesResolver(schemaWithCustomError)(
validData,
undefined,
{
fields,
shouldUseNativeValidation,
},
);

await expect(promise).rejects.toThrow('custom error');
});
});
17 changes: 12 additions & 5 deletions computed-types/src/computed-types.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import type { FieldErrors } from 'react-hook-form';
import { toNestError, validateFieldsNatively } from '@hookform/resolvers';
import type { ValidationError } from 'computed-types';
import type { Resolver } from './types';
import type { ValidationError } from 'computed-types';

const isValidationError = (error: any): error is ValidationError =>
error.errors != null;

const parseErrorSchema = (computedTypesError: ValidationError) => {
const parsedErrors: FieldErrors = {};
Expand All @@ -27,9 +30,13 @@ export const computedTypesResolver: Resolver =
values: data,
};
} catch (error: any) {
return {
values: {},
errors: toNestError(parseErrorSchema(error), options),
};
if (isValidationError(error)) {
return {
values: {},
errors: toNestError(parseErrorSchema(error), options),
};
}

throw error;
}
};
13 changes: 13 additions & 0 deletions superstruct/src/__tests__/superstruct.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,17 @@ describe('superstructResolver', () => {

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

it('should return values from superstructResolver when validation pass & raw=true', async () => {
const result = await superstructResolver(schema, undefined, { raw: true })(
validData,
undefined,
{
fields,
shouldUseNativeValidation,
},
);

expect(result).toEqual({ errors: {}, values: validData });
});
});
7 changes: 4 additions & 3 deletions superstruct/src/superstruct.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@ const parseErrorSchema = (error: StructError) =>
);

export const superstructResolver: Resolver =
(schema, resolverOptions) => (values, _, options) => {
const result = validate(values, schema, resolverOptions);
(schema, schemaOptions, resolverOptions = {}) =>
(values, _, options) => {
const result = validate(values, schema, schemaOptions);

if (result[0]) {
return {
Expand All @@ -28,7 +29,7 @@ export const superstructResolver: Resolver =
options.shouldUseNativeValidation && validateFieldsNatively({}, options);

return {
values: result[1],
values: resolverOptions.raw ? values : result[1],
errors: {},
};
};
13 changes: 8 additions & 5 deletions superstruct/src/types.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
import {
FieldValues,
ResolverOptions,
ResolverResult,
} from 'react-hook-form';
import { FieldValues, ResolverOptions, ResolverResult } from 'react-hook-form';
import { validate, Struct } from 'superstruct';

type Options = Parameters<typeof validate>[2];

export type Resolver = <T extends Struct<any, any>>(
schema: T,
options?: Options,
factoryOptions?: {
/**
* Return the raw input values rather than the parsed values.
* @default false
*/
raw?: boolean;
},
) => <TFieldValues extends FieldValues, TContext>(
values: TFieldValues,
context: TContext | undefined,
Expand Down
30 changes: 30 additions & 0 deletions yup/src/__tests__/yup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -190,4 +190,34 @@ describe('yupResolver', () => {

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

it('should throw any error unrelated to Yup', async () => {
const schemaWithCustomError = schema.transform(() => {
throw Error('custom error');
});
const promise = yupResolver(schemaWithCustomError)(validData, undefined, {
fields,
shouldUseNativeValidation,
});

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

it('should return values from yupResolver when validation pass & raw=true', async () => {
const schemaSpy = vi.spyOn(schema, 'validate');
const schemaSyncSpy = vi.spyOn(schema, 'validateSync');

const result = await yupResolver(schema, undefined, { raw: true })(
validData,
undefined,
{
fields,
shouldUseNativeValidation,
},
);

expect(schemaSpy).toHaveBeenCalledTimes(1);
expect(schemaSyncSpy).not.toHaveBeenCalled();
expect(result).toEqual({ errors: {}, values: validData });
});
});
12 changes: 11 additions & 1 deletion yup/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,17 @@ type Options<T extends Yup.ObjectSchema<any>> = Parameters<T['validate']>[1];
export type Resolver = <T extends Yup.ObjectSchema<any>>(
schema: T,
schemaOptions?: Options<T>,
factoryOptions?: { mode?: 'async' | 'sync'; rawValues?: boolean },
factoryOptions?: {
/**
* @default async
*/
mode?: 'async' | 'sync';
/**
* Return the raw input values rather than the parsed values.
* @default false
*/
raw?: boolean;
},
) => <TFieldValues extends FieldValues, TContext>(
values: TFieldValues,
context: TContext | undefined,
Expand Down
2 changes: 1 addition & 1 deletion yup/src/yup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ export const yupResolver: Resolver =
options.shouldUseNativeValidation && validateFieldsNatively({}, options);

return {
values: resolverOptions.rawValues ? values : result,
values: resolverOptions.raw ? values : result,
errors: {},
};
} catch (e: any) {
Expand Down
16 changes: 14 additions & 2 deletions zod/src/__tests__/zod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ import { schema, validData, invalidData, fields } from './__fixtures__/data';
const shouldUseNativeValidation = false;

describe('zodResolver', () => {
it('should return values from zodResolver when validation pass & rawValues=true', async () => {
it('should return values from zodResolver when validation pass & raw=true', async () => {
const parseAsyncSpy = vi.spyOn(schema, 'parseAsync');

const result = await zodResolver(schema, undefined, {
rawValues: true,
raw: true,
})(validData, undefined, {
fields,
shouldUseNativeValidation,
Expand Down Expand Up @@ -77,4 +77,16 @@ describe('zodResolver', () => {

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

it('should throw any error unrelated to Zod', async () => {
const schemaWithCustomError = schema.refine(() => {
throw Error('custom error');
});
const promise = zodResolver(schemaWithCustomError)(validData, undefined, {
fields,
shouldUseNativeValidation,
});

await expect(promise).rejects.toThrow('custom error');
});
});
8 changes: 2 additions & 6 deletions zod/src/types.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
import {
FieldValues,
ResolverResult,
ResolverOptions,
} from 'react-hook-form';
import { FieldValues, ResolverResult, ResolverOptions } from 'react-hook-form';
import { z } from 'zod';

export type Resolver = <T extends z.Schema<any, any>>(
Expand All @@ -17,7 +13,7 @@ export type Resolver = <T extends z.Schema<any, any>>(
* Return the raw input values rather than the parsed values.
* @default false
*/
rawValues?: boolean;
raw?: boolean;
},
) => <TFieldValues extends FieldValues, TContext>(
values: TFieldValues,
Expand Down
32 changes: 18 additions & 14 deletions zod/src/zod.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { appendErrors, FieldError, FieldErrors } from 'react-hook-form';
import { z } from 'zod';
import { z, ZodError } from 'zod';
import { toNestError, validateFieldsNatively } from '@hookform/resolvers';
import type { Resolver } from './types';

const isZodError = (error: any): error is ZodError => error.errors != null;

const parseErrorSchema = (
zodErrors: z.ZodIssue[],
validateAllFieldCriteria: boolean,
Expand Down Expand Up @@ -65,21 +67,23 @@ export const zodResolver: Resolver =

return {
errors: {} as FieldErrors,
values: resolverOptions.rawValues ? values : data,
values: resolverOptions.raw ? values : data,
};
} catch (error: any) {
return {
values: {},
errors: error.isEmpty
? {}
: toNestError(
parseErrorSchema(
error.errors,
!options.shouldUseNativeValidation &&
options.criteriaMode === 'all',
),
options,
if (isZodError(error)) {
return {
values: {},
errors: toNestError(
parseErrorSchema(
error.errors,
!options.shouldUseNativeValidation &&
options.criteriaMode === 'all',
),
};
options,
),
};
}

throw error;
}
};

0 comments on commit 6677e0e

Please sign in to comment.