Skip to content

Commit 12d7d8e

Browse files
authored
feat(effectResolver): returns either all errors or only the first one based on criteriaMode (#737)
1 parent 9a94555 commit 12d7d8e

File tree

3 files changed

+127
-7
lines changed

3 files changed

+127
-7
lines changed

effect-ts/src/__tests__/__snapshots__/effect-ts.ts.snap

+35-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ exports[`effectTsResolver > should return a single error from effectTsResolver w
44
{
55
"errors": {
66
"animal": {
7-
"message": "Expected "snake", actual ["dog"]",
7+
"message": "Expected string, actual ["dog"]",
88
"ref": undefined,
99
"type": "Type",
1010
},
@@ -38,3 +38,37 @@ exports[`effectTsResolver > should return a single error from effectTsResolver w
3838
"values": {},
3939
}
4040
`;
41+
42+
exports[`effectTsResolver > should return all the errors from effectTsResolver when validation fails with \`validateAllFieldCriteria\` set to true 1`] = `
43+
{
44+
"errors": {
45+
"phoneNumber": {
46+
"message": "Please enter a valid phone number in international format.",
47+
"ref": {
48+
"name": "phoneNumber",
49+
},
50+
"type": "Refinement",
51+
"types": {
52+
"Refinement": "Please enter a valid phone number in international format.",
53+
"Type": "Expected undefined, actual "123"",
54+
},
55+
},
56+
},
57+
"values": {},
58+
}
59+
`;
60+
61+
exports[`effectTsResolver > should return the first error from effectTsResolver when validation fails with \`validateAllFieldCriteria\` set to firstError 1`] = `
62+
{
63+
"errors": {
64+
"phoneNumber": {
65+
"message": "Please enter a valid phone number in international format.",
66+
"ref": {
67+
"name": "phoneNumber",
68+
},
69+
"type": "Refinement",
70+
},
71+
},
72+
"values": {},
73+
}
74+
`;

effect-ts/src/__tests__/effect-ts.ts

+61
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { Schema } from 'effect';
12
import { effectTsResolver } from '..';
23
import { fields, invalidData, schema, validData } from './__fixtures__/data';
34

@@ -21,4 +22,64 @@ describe('effectTsResolver', () => {
2122

2223
expect(result).toMatchSnapshot();
2324
});
25+
26+
it('should return the first error from effectTsResolver when validation fails with `validateAllFieldCriteria` set to firstError', async () => {
27+
const SignupSchema = Schema.Struct({
28+
phoneNumber: Schema.optional(
29+
Schema.String.pipe(
30+
Schema.pattern(/^\+\d{7,15}$/, {
31+
message: () =>
32+
'Please enter a valid phone number in international format.',
33+
}),
34+
),
35+
),
36+
});
37+
38+
const result = await effectTsResolver(SignupSchema)(
39+
{ phoneNumber: '123' },
40+
undefined,
41+
{
42+
fields: {
43+
phoneNumber: {
44+
ref: { name: 'phoneNumber' },
45+
name: 'phoneNumber',
46+
},
47+
},
48+
criteriaMode: 'firstError',
49+
shouldUseNativeValidation,
50+
},
51+
);
52+
53+
expect(result).toMatchSnapshot();
54+
});
55+
56+
it('should return all the errors from effectTsResolver when validation fails with `validateAllFieldCriteria` set to true', async () => {
57+
const SignupSchema = Schema.Struct({
58+
phoneNumber: Schema.optional(
59+
Schema.String.pipe(
60+
Schema.pattern(/^\+\d{7,15}$/, {
61+
message: () =>
62+
'Please enter a valid phone number in international format.',
63+
}),
64+
),
65+
),
66+
});
67+
68+
const result = await effectTsResolver(SignupSchema)(
69+
{ phoneNumber: '123' },
70+
undefined,
71+
{
72+
fields: {
73+
phoneNumber: {
74+
ref: { name: 'phoneNumber' },
75+
name: 'phoneNumber',
76+
},
77+
},
78+
criteriaMode: 'all',
79+
shouldUseNativeValidation,
80+
},
81+
);
82+
83+
expect(result).toMatchSnapshot();
84+
});
2485
});

effect-ts/src/effect-ts.ts

+31-6
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { toNestErrors, validateFieldsNatively } from '@hookform/resolvers';
22
import { Effect } from 'effect';
33

44
import { ArrayFormatter, decodeUnknown } from 'effect/ParseResult';
5-
import type { FieldErrors } from 'react-hook-form';
5+
import { appendErrors, type FieldError } from 'react-hook-form';
66
import type { Resolver } from './types';
77

88
export const effectTsResolver: Resolver =
@@ -16,11 +16,36 @@ export const effectTsResolver: Resolver =
1616
Effect.flip(ArrayFormatter.formatIssue(parseIssue)),
1717
),
1818
Effect.mapError((issues) => {
19-
const errors = issues.reduce((acc, current) => {
20-
const key = current.path.join('.');
21-
acc[key] = { message: current.message, type: current._tag };
22-
return acc;
23-
}, {} as FieldErrors);
19+
const validateAllFieldCriteria =
20+
!options.shouldUseNativeValidation && options.criteriaMode === 'all';
21+
22+
const errors = issues.reduce(
23+
(acc, error) => {
24+
const key = error.path.join('.');
25+
26+
if (!acc[key]) {
27+
acc[key] = { message: error.message, type: error._tag };
28+
}
29+
30+
if (validateAllFieldCriteria) {
31+
const types = acc[key].types;
32+
const messages = types && types[String(error._tag)];
33+
34+
acc[key] = appendErrors(
35+
key,
36+
validateAllFieldCriteria,
37+
acc,
38+
error._tag,
39+
messages
40+
? ([] as string[]).concat(messages as string[], error.message)
41+
: error.message,
42+
) as FieldError;
43+
}
44+
45+
return acc;
46+
},
47+
{} as Record<string, FieldError>,
48+
);
2449

2550
return toNestErrors(errors, options);
2651
}),

0 commit comments

Comments
 (0)