Repository: react-hook-form/resolvers (ts)

Issue:
[effect-ts] Only the last issue per field is reported
**Describe the bug**
If the filed is a union, only the last issue is reported. This can be cumbersome especially with optional fields, which are just unions with undefined.

**To Reproduce**
Create a Schema which will produce multiple `ParseIssues` for a single filed

**Codesandbox link (Required)**
https://codesandbox.io/p/sandbox/react-hook-form-validationschema-v6-forked-nwp2d2

**Expected behavior**
The error should contain the error "Please enter a valid phone number in international format." when submit is clicked.


Pull request description:
feat(effectResolver): returns either all errors or only the first one based on `criteriaMode`
Fixes #721 

_(do not merge)_

Patch:
diff --git a/effect-ts/src/effect-ts.ts b/effect-ts/src/effect-ts.ts
index f3b8ba7..b99a758 100644
--- a/effect-ts/src/effect-ts.ts
+++ b/effect-ts/src/effect-ts.ts
@@ -2,7 +2,7 @@ import { toNestErrors, validateFieldsNatively } from '@hookform/resolvers';
 import { Effect } from 'effect';
 
 import { ArrayFormatter, decodeUnknown } from 'effect/ParseResult';
-import type { FieldErrors } from 'react-hook-form';
+import { appendErrors, type FieldError } from 'react-hook-form';
 import type { Resolver } from './types';
 
 export const effectTsResolver: Resolver =
@@ -16,11 +16,36 @@ export const effectTsResolver: Resolver =
         Effect.flip(ArrayFormatter.formatIssue(parseIssue)),
       ),
       Effect.mapError((issues) => {
-        const errors = issues.reduce((acc, current) => {
-          const key = current.path.join('.');
-          acc[key] = { message: current.message, type: current._tag };
-          return acc;
-        }, {} as FieldErrors);
+        const validateAllFieldCriteria =
+          !options.shouldUseNativeValidation && options.criteriaMode === 'all';
+
+        const errors = issues.reduce(
+          (acc, error) => {
+            const key = error.path.join('.');
+
+            if (!acc[key]) {
+              acc[key] = { message: error.message, type: error._tag };
+            }
+
+            if (validateAllFieldCriteria) {
+              const types = acc[key].types;
+              const messages = types && types[String(error._tag)];
+
+              acc[key] = appendErrors(
+                key,
+                validateAllFieldCriteria,
+                acc,
+                error._tag,
+                messages
+                  ? ([] as string[]).concat(messages as string[], error.message)
+                  : error.message,
+              ) as FieldError;
+            }
+
+            return acc;
+          },
+          {} as Record<string, FieldError>,
+        );
 
         return toNestErrors(errors, options);
       }),


Test patch:
diff --git a/effect-ts/src/__tests__/__snapshots__/effect-ts.ts.snap b/effect-ts/src/__tests__/__snapshots__/effect-ts.ts.snap
index 6d97f0e..0e9e0cc 100644
--- a/effect-ts/src/__tests__/__snapshots__/effect-ts.ts.snap
+++ b/effect-ts/src/__tests__/__snapshots__/effect-ts.ts.snap
@@ -4,7 +4,7 @@ exports[`effectTsResolver > should return a single error from effectTsResolver w
 {
   "errors": {
     "animal": {
-      "message": "Expected "snake", actual ["dog"]",
+      "message": "Expected string, actual ["dog"]",
       "ref": undefined,
       "type": "Type",
     },
@@ -38,3 +38,37 @@ exports[`effectTsResolver > should return a single error from effectTsResolver w
   "values": {},
 }
 `;
+
+exports[`effectTsResolver > should return all the errors from effectTsResolver when validation fails with \`validateAllFieldCriteria\` set to true 1`] = `
+{
+  "errors": {
+    "phoneNumber": {
+      "message": "Please enter a valid phone number in international format.",
+      "ref": {
+        "name": "phoneNumber",
+      },
+      "type": "Refinement",
+      "types": {
+        "Refinement": "Please enter a valid phone number in international format.",
+        "Type": "Expected undefined, actual "123"",
+      },
+    },
+  },
+  "values": {},
+}
+`;
+
+exports[`effectTsResolver > should return the first error from effectTsResolver when validation fails with \`validateAllFieldCriteria\` set to firstError 1`] = `
+{
+  "errors": {
+    "phoneNumber": {
+      "message": "Please enter a valid phone number in international format.",
+      "ref": {
+        "name": "phoneNumber",
+      },
+      "type": "Refinement",
+    },
+  },
+  "values": {},
+}
+`;
diff --git a/effect-ts/src/__tests__/effect-ts.ts b/effect-ts/src/__tests__/effect-ts.ts
index 409adac..cfe296b 100644
--- a/effect-ts/src/__tests__/effect-ts.ts
+++ b/effect-ts/src/__tests__/effect-ts.ts
@@ -1,3 +1,4 @@
+import { Schema } from 'effect';
 import { effectTsResolver } from '..';
 import { fields, invalidData, schema, validData } from './__fixtures__/data';
 
@@ -21,4 +22,64 @@ describe('effectTsResolver', () => {
 
     expect(result).toMatchSnapshot();
   });
+
+  it('should return the first error from effectTsResolver when validation fails with `validateAllFieldCriteria` set to firstError', async () => {
+    const SignupSchema = Schema.Struct({
+      phoneNumber: Schema.optional(
+        Schema.String.pipe(
+          Schema.pattern(/^\+\d{7,15}$/, {
+            message: () =>
+              'Please enter a valid phone number in international format.',
+          }),
+        ),
+      ),
+    });
+
+    const result = await effectTsResolver(SignupSchema)(
+      { phoneNumber: '123' },
+      undefined,
+      {
+        fields: {
+          phoneNumber: {
+            ref: { name: 'phoneNumber' },
+            name: 'phoneNumber',
+          },
+        },
+        criteriaMode: 'firstError',
+        shouldUseNativeValidation,
+      },
+    );
+
+    expect(result).toMatchSnapshot();
+  });
+
+  it('should return all the errors from effectTsResolver when validation fails with `validateAllFieldCriteria` set to true', async () => {
+    const SignupSchema = Schema.Struct({
+      phoneNumber: Schema.optional(
+        Schema.String.pipe(
+          Schema.pattern(/^\+\d{7,15}$/, {
+            message: () =>
+              'Please enter a valid phone number in international format.',
+          }),
+        ),
+      ),
+    });
+
+    const result = await effectTsResolver(SignupSchema)(
+      { phoneNumber: '123' },
+      undefined,
+      {
+        fields: {
+          phoneNumber: {
+            ref: { name: 'phoneNumber' },
+            name: 'phoneNumber',
+          },
+        },
+        criteriaMode: 'all',
+        shouldUseNativeValidation,
+      },
+    );
+
+    expect(result).toMatchSnapshot();
+  });
 });