From 5030a59b4dee8f02d469d2ebeda46822e27b2d88 Mon Sep 17 00:00:00 2001 From: Kemal Tunador Date: Mon, 27 Jan 2025 08:07:58 -0800 Subject: [PATCH] feat(ajv): Keep original validation type while using `errorMessage` (#728) BREAKING CHANGE: The AJV Resolver now unwraps the `errorMessage` object to return the original error types. This update may introduce breaking changes to your projects. --- ajv/src/__tests__/__fixtures__/data-errors.ts | 216 ++++++++ .../__snapshots__/ajv-errors.ts.snap | 462 ++++++++++++++++++ ajv/src/__tests__/__snapshots__/ajv.ts.snap | 12 +- ajv/src/__tests__/ajv-errors.ts | 227 +++++++++ ajv/src/ajv.ts | 41 +- ajv/src/types.ts | 8 + 6 files changed, 946 insertions(+), 20 deletions(-) create mode 100644 ajv/src/__tests__/__fixtures__/data-errors.ts create mode 100644 ajv/src/__tests__/__snapshots__/ajv-errors.ts.snap create mode 100644 ajv/src/__tests__/ajv-errors.ts diff --git a/ajv/src/__tests__/__fixtures__/data-errors.ts b/ajv/src/__tests__/__fixtures__/data-errors.ts new file mode 100644 index 00000000..a5b3e577 --- /dev/null +++ b/ajv/src/__tests__/__fixtures__/data-errors.ts @@ -0,0 +1,216 @@ +import { JSONSchemaType } from 'ajv'; +import { Field, InternalFieldName } from 'react-hook-form'; + +interface DataA { + username: string; + password: string; +} + +export const schemaA: JSONSchemaType = { + type: 'object', + properties: { + username: { + type: 'string', + minLength: 3, + errorMessage: { + minLength: 'username should be at least three characters long', + }, + }, + password: { + type: 'string', + pattern: '.*[A-Z].*', + minLength: 8, + errorMessage: { + pattern: 'One uppercase character', + minLength: 'passwords should be at least eight characters long', + }, + }, + }, + required: ['username', 'password'], + additionalProperties: false, + errorMessage: { + required: { + username: 'username field is required', + password: 'password field is required', + }, + }, +}; + +export const validDataA: DataA = { + username: 'kt666', + password: 'validPassword', +}; + +export const invalidDataA = { + username: 'kt', + password: 'invalid', +}; + +export const undefinedDataA = { + username: undefined, + password: undefined, +}; + +export const fieldsA: Record = { + username: { + ref: { name: 'username' }, + name: 'username', + }, + password: { + ref: { name: 'password' }, + name: 'password', + }, + email: { + ref: { name: 'email' }, + name: 'email', + }, + birthday: { + ref: { name: 'birthday' }, + name: 'birthday', + }, +}; + +// examples from [ajv-errors](https://github.com/ajv-validator/ajv-errors) + +interface DataB { + foo: number; +} + +export const schemaB: JSONSchemaType = { + type: 'object', + required: ['foo'], + properties: { + foo: { type: 'integer' }, + }, + additionalProperties: false, + errorMessage: 'should be an object with an integer property foo only', +}; + +export const validDataB: DataB = { foo: 666 }; +export const invalidDataB = { foo: 'kt', bar: 6 }; +export const undefinedDataB = { foo: undefined }; + +interface DataC { + foo: number; +} + +export const schemaC: JSONSchemaType = { + type: 'object', + required: ['foo'], + properties: { + foo: { type: 'integer' }, + }, + additionalProperties: false, + errorMessage: { + type: 'should be an object', + required: 'should have property foo', + additionalProperties: 'should not have properties other than foo', + }, +}; + +export const validDataC: DataC = { foo: 666 }; +export const invalidDataC = { foo: 'kt', bar: 6 }; +export const undefinedDataC = { foo: undefined }; +export const invalidTypeDataC = 'something'; + +interface DataD { + foo: number; + bar: string; +} + +export const schemaD: JSONSchemaType = { + type: 'object', + required: ['foo', 'bar'], + properties: { + foo: { type: 'integer' }, + bar: { type: 'string' }, + }, + errorMessage: { + type: 'should be an object', // will not replace internal "type" error for the property "foo" + required: { + foo: 'should have an integer property "foo"', + bar: 'should have a string property "bar"', + }, + }, +}; + +export const validDataD: DataD = { foo: 666, bar: 'kt' }; +export const invalidDataD = { foo: 'kt', bar: 6 }; +export const undefinedDataD = { foo: undefined, bar: undefined }; +export const invalidTypeDataD = 'something'; + +interface DataE { + foo: number; + bar: string; +} + +export const schemaE: JSONSchemaType = { + type: 'object', + required: ['foo', 'bar'], + allOf: [ + { + properties: { + foo: { type: 'integer', minimum: 2 }, + bar: { type: 'string', minLength: 2 }, + }, + additionalProperties: false, + }, + ], + errorMessage: { + properties: { + foo: 'data.foo should be integer >= 2', + bar: 'data.bar should be string with length >= 2', + }, + }, +}; + +export const validDataE: DataE = { foo: 666, bar: 'kt' }; +export const invalidDataE = { foo: 1, bar: 'k' }; +export const undefinedDataE = { foo: undefined, bar: undefined }; + +interface DataF { + foo: number; + bar: string; +} + +export const schemaF: JSONSchemaType = { + type: 'object', + required: ['foo', 'bar'], + allOf: [ + { + properties: { + foo: { type: 'integer', minimum: 2 }, + bar: { type: 'string', minLength: 2 }, + }, + additionalProperties: false, + }, + ], + errorMessage: { + type: 'data should be an object', + properties: { + foo: 'data.foo should be integer >= 2', + bar: 'data.bar should be string with length >= 2', + }, + _: 'data should have properties "foo" and "bar" only', + }, +}; + +export const validDataF: DataF = { foo: 666, bar: 'kt' }; +export const invalidDataF = {}; +export const undefinedDataF = { foo: 1, bar: undefined }; +export const invalidTypeDataF = 'something'; + +export const fieldsRest: Record = { + foo: { + ref: { name: 'foo' }, + name: 'foo', + }, + bar: { + ref: { name: 'bar' }, + name: 'bar', + }, + lorem: { + ref: { name: 'lorem' }, + name: 'lorem', + }, +}; diff --git a/ajv/src/__tests__/__snapshots__/ajv-errors.ts.snap b/ajv/src/__tests__/__snapshots__/ajv-errors.ts.snap new file mode 100644 index 00000000..56062b23 --- /dev/null +++ b/ajv/src/__tests__/__snapshots__/ajv-errors.ts.snap @@ -0,0 +1,462 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`ajvResolver with errorMessage > should return a default message if there is no specific message for the error when requirement fails 1`] = ` +{ + "errors": { + "bar": { + "message": "data should have properties "foo" and "bar" only", + "ref": { + "name": "bar", + }, + "type": "required", + "types": { + "required": "data should have properties "foo" and "bar" only", + }, + }, + "foo": { + "message": "data should have properties "foo" and "bar" only", + "ref": { + "name": "foo", + }, + "type": "required", + "types": { + "required": "data should have properties "foo" and "bar" only", + }, + }, + }, + "values": {}, +} +`; + +exports[`ajvResolver with errorMessage > should return a default message if there is no specific message for the error when some properties are undefined 1`] = ` +{ + "errors": { + "bar": { + "message": "data should have properties "foo" and "bar" only", + "ref": { + "name": "bar", + }, + "type": "required", + "types": { + "required": "data should have properties "foo" and "bar" only", + }, + }, + "foo": { + "message": "data.foo should be integer >= 2", + "ref": { + "name": "foo", + }, + "type": "minimum", + "types": { + "minimum": "data.foo should be integer >= 2", + }, + }, + }, + "values": {}, +} +`; + +exports[`ajvResolver with errorMessage > should return a default message if there is no specific message for the error when walidation fails 1`] = ` +{ + "errors": { + "bar": { + "message": "data should have properties "foo" and "bar" only", + "ref": { + "name": "bar", + }, + "type": "required", + "types": { + "required": "data should have properties "foo" and "bar" only", + }, + }, + "foo": { + "message": "data should have properties "foo" and "bar" only", + "ref": { + "name": "foo", + }, + "type": "required", + "types": { + "required": "data should have properties "foo" and "bar" only", + }, + }, + }, + "values": {}, +} +`; + +exports[`ajvResolver with errorMessage > should return customized error messages for certain keywords when requirement fails 1`] = ` +{ + "errors": { + "foo": { + "message": "should have property foo", + "ref": { + "name": "foo", + }, + "type": "required", + "types": { + "required": "should have property foo", + }, + }, + }, + "values": {}, +} +`; + +exports[`ajvResolver with errorMessage > should return customized error messages for certain keywords when some properties are undefined 1`] = ` +{ + "errors": { + "foo": { + "message": "should have property foo", + "ref": { + "name": "foo", + }, + "type": "required", + "types": { + "required": "should have property foo", + }, + }, + }, + "values": {}, +} +`; + +exports[`ajvResolver with errorMessage > should return customized error messages for certain keywords when walidation fails 1`] = ` +{ + "errors": { + "": { + "message": "should not have properties other than foo", + "ref": undefined, + "type": "additionalProperties", + "types": { + "additionalProperties": "should not have properties other than foo", + }, + }, + "foo": { + "message": "must be integer", + "ref": { + "name": "foo", + }, + "type": "type", + "types": { + "type": "must be integer", + }, + }, + }, + "values": {}, +} +`; + +exports[`ajvResolver with errorMessage > should return customized error messages when requirement fails 1`] = ` +{ + "errors": { + "password": { + "message": "password field is required", + "ref": { + "name": "password", + }, + "type": "required", + "types": { + "required": "password field is required", + }, + }, + "username": { + "message": "username field is required", + "ref": { + "name": "username", + }, + "type": "required", + "types": { + "required": "username field is required", + }, + }, + }, + "values": {}, +} +`; + +exports[`ajvResolver with errorMessage > should return customized error messages when some properties are undefined 1`] = ` +{ + "errors": { + "password": { + "message": "password field is required", + "ref": { + "name": "password", + }, + "type": "required", + "types": { + "required": "password field is required", + }, + }, + "username": { + "message": "username field is required", + "ref": { + "name": "username", + }, + "type": "required", + "types": { + "required": "username field is required", + }, + }, + }, + "values": {}, +} +`; + +exports[`ajvResolver with errorMessage > should return customized error messages when validation fails 1`] = ` +{ + "errors": { + "password": { + "message": "One uppercase character", + "ref": { + "name": "password", + }, + "type": "pattern", + "types": { + "minLength": "passwords should be at least eight characters long", + "pattern": "One uppercase character", + }, + }, + "username": { + "message": "username should be at least three characters long", + "ref": { + "name": "username", + }, + "type": "minLength", + "types": { + "minLength": "username should be at least three characters long", + }, + }, + }, + "values": {}, +} +`; + +exports[`ajvResolver with errorMessage > should return customized errors for properties/items when requirement fails 1`] = ` +{ + "errors": { + "bar": { + "message": "must have required property 'bar'", + "ref": { + "name": "bar", + }, + "type": "required", + "types": { + "required": "must have required property 'bar'", + }, + }, + "foo": { + "message": "must have required property 'foo'", + "ref": { + "name": "foo", + }, + "type": "required", + "types": { + "required": "must have required property 'foo'", + }, + }, + }, + "values": {}, +} +`; + +exports[`ajvResolver with errorMessage > should return customized errors for properties/items when some properties are undefined 1`] = ` +{ + "errors": { + "bar": { + "message": "must have required property 'bar'", + "ref": { + "name": "bar", + }, + "type": "required", + "types": { + "required": "must have required property 'bar'", + }, + }, + "foo": { + "message": "must have required property 'foo'", + "ref": { + "name": "foo", + }, + "type": "required", + "types": { + "required": "must have required property 'foo'", + }, + }, + }, + "values": {}, +} +`; + +exports[`ajvResolver with errorMessage > should return customized errors for properties/items when walidation fails 1`] = ` +{ + "errors": { + "bar": { + "message": "data.bar should be string with length >= 2", + "ref": { + "name": "bar", + }, + "type": "minLength", + "types": { + "minLength": "data.bar should be string with length >= 2", + }, + }, + "foo": { + "message": "data.foo should be integer >= 2", + "ref": { + "name": "foo", + }, + "type": "minimum", + "types": { + "minimum": "data.foo should be integer >= 2", + }, + }, + }, + "values": {}, +} +`; + +exports[`ajvResolver with errorMessage > should return different messages for different properties when requirement fails 1`] = ` +{ + "errors": { + "bar": { + "message": "should have a string property "bar"", + "ref": { + "name": "bar", + }, + "type": "required", + "types": { + "required": "should have a string property "bar"", + }, + }, + "foo": { + "message": "should have an integer property "foo"", + "ref": { + "name": "foo", + }, + "type": "required", + "types": { + "required": "should have an integer property "foo"", + }, + }, + }, + "values": {}, +} +`; + +exports[`ajvResolver with errorMessage > should return different messages for different properties when some properties are undefined 1`] = ` +{ + "errors": { + "bar": { + "message": "should have a string property "bar"", + "ref": { + "name": "bar", + }, + "type": "required", + "types": { + "required": "should have a string property "bar"", + }, + }, + "foo": { + "message": "should have an integer property "foo"", + "ref": { + "name": "foo", + }, + "type": "required", + "types": { + "required": "should have an integer property "foo"", + }, + }, + }, + "values": {}, +} +`; + +exports[`ajvResolver with errorMessage > should return different messages for different properties when walidation fails 1`] = ` +{ + "errors": { + "bar": { + "message": "must be string", + "ref": { + "name": "bar", + }, + "type": "type", + "types": { + "type": "must be string", + }, + }, + "foo": { + "message": "must be integer", + "ref": { + "name": "foo", + }, + "type": "type", + "types": { + "type": "must be integer", + }, + }, + }, + "values": {}, +} +`; + +exports[`ajvResolver with errorMessage > should return the same customized error message when requirement fails 1`] = ` +{ + "errors": { + "foo": { + "message": "should be an object with an integer property foo only", + "ref": { + "name": "foo", + }, + "type": "required", + "types": { + "required": "should be an object with an integer property foo only", + }, + }, + }, + "values": {}, +} +`; + +exports[`ajvResolver with errorMessage > should return the same customized message for all validation failures 1`] = ` +{ + "errors": { + "": { + "message": "should be an object with an integer property foo only", + "ref": undefined, + "type": "additionalProperties", + "types": { + "additionalProperties": "should be an object with an integer property foo only", + }, + }, + "foo": { + "message": "should be an object with an integer property foo only", + "ref": { + "name": "foo", + }, + "type": "type", + "types": { + "type": "should be an object with an integer property foo only", + }, + }, + }, + "values": {}, +} +`; + +exports[`ajvResolver with errorMessage > should return the same customized message when some properties are undefined 1`] = ` +{ + "errors": { + "foo": { + "message": "should be an object with an integer property foo only", + "ref": { + "name": "foo", + }, + "type": "required", + "types": { + "required": "should be an object with an integer property foo only", + }, + }, + }, + "values": {}, +} +`; diff --git a/ajv/src/__tests__/__snapshots__/ajv.ts.snap b/ajv/src/__tests__/__snapshots__/ajv.ts.snap index d96b1b52..a113c0e8 100644 --- a/ajv/src/__tests__/__snapshots__/ajv.ts.snap +++ b/ajv/src/__tests__/__snapshots__/ajv.ts.snap @@ -104,9 +104,9 @@ exports[`ajvResolver > should return all the error messages from ajvResolver whe "ref": { "name": "password", }, - "type": "errorMessage", + "type": "pattern", "types": { - "errorMessage": "One uppercase character", + "pattern": "One uppercase character", }, }, "username": { @@ -152,9 +152,9 @@ exports[`ajvResolver > should return all the error messages from ajvResolver whe "ref": { "name": "password", }, - "type": "errorMessage", + "type": "pattern", "types": { - "errorMessage": "One uppercase character", + "pattern": "One uppercase character", }, }, "username": { @@ -194,7 +194,7 @@ exports[`ajvResolver > should return single error message from ajvResolver when "ref": { "name": "password", }, - "type": "errorMessage", + "type": "pattern", }, "username": { "message": "must NOT have fewer than 3 characters", @@ -230,7 +230,7 @@ exports[`ajvResolver > should return single error message from ajvResolver when "ref": { "name": "password", }, - "type": "errorMessage", + "type": "pattern", }, "username": { "message": "must NOT have fewer than 3 characters", diff --git a/ajv/src/__tests__/ajv-errors.ts b/ajv/src/__tests__/ajv-errors.ts new file mode 100644 index 00000000..63726ce2 --- /dev/null +++ b/ajv/src/__tests__/ajv-errors.ts @@ -0,0 +1,227 @@ +import { ajvResolver } from '..'; +import * as fixture from './__fixtures__/data-errors'; + +const shouldUseNativeValidation = false; + +describe('ajvResolver with errorMessage', () => { + it('should return values when validation pass', async () => { + expect( + await ajvResolver(fixture.schemaA)(fixture.validDataA, undefined, { + fields: fixture.fieldsA, + criteriaMode: 'all', + shouldUseNativeValidation, + }), + ).toEqual({ + values: fixture.validDataA, + errors: {}, + }); + }); + + it('should return customized error messages when validation fails', async () => { + expect( + await ajvResolver(fixture.schemaA)( + fixture.invalidDataA, + {}, + { + fields: fixture.fieldsA, + criteriaMode: 'all', + shouldUseNativeValidation, + }, + ), + ).toMatchSnapshot(); + }); + + it('should return customized error messages when requirement fails', async () => { + expect( + await ajvResolver(fixture.schemaA)({}, undefined, { + fields: fixture.fieldsA, + criteriaMode: 'all', + shouldUseNativeValidation, + }), + ).toMatchSnapshot(); + }); + + it('should return customized error messages when some properties are undefined', async () => { + expect( + await ajvResolver(fixture.schemaA, undefined, { mode: 'sync' })( + fixture.undefinedDataA, + undefined, + { + fields: fixture.fieldsA, + criteriaMode: 'all', + shouldUseNativeValidation, + }, + ), + ).toMatchSnapshot(); + }); + + it('should return the same customized message for all validation failures', async () => { + expect( + await ajvResolver(fixture.schemaB)( + fixture.invalidDataB, + {}, + { + fields: fixture.fieldsRest, + criteriaMode: 'all', + shouldUseNativeValidation, + }, + ), + ).toMatchSnapshot(); + }); + + it('should return the same customized error message when requirement fails', async () => { + expect( + await ajvResolver(fixture.schemaB)({}, undefined, { + fields: fixture.fieldsRest, + criteriaMode: 'all', + shouldUseNativeValidation, + }), + ).toMatchSnapshot(); + }); + + it('should return the same customized message when some properties are undefined', async () => { + expect( + await ajvResolver(fixture.schemaB)(fixture.undefinedDataB, undefined, { + fields: fixture.fieldsRest, + criteriaMode: 'all', + shouldUseNativeValidation, + }), + ).toMatchSnapshot(); + }); + + it('should return customized error messages for certain keywords when walidation fails', async () => { + expect( + await ajvResolver(fixture.schemaC)( + fixture.invalidDataC, + {}, + { + fields: fixture.fieldsRest, + criteriaMode: 'all', + shouldUseNativeValidation, + }, + ), + ).toMatchSnapshot(); + }); + + it('should return customized error messages for certain keywords when requirement fails', async () => { + expect( + await ajvResolver(fixture.schemaC)({}, undefined, { + fields: fixture.fieldsRest, + criteriaMode: 'all', + shouldUseNativeValidation, + }), + ).toMatchSnapshot(); + }); + + it('should return customized error messages for certain keywords when some properties are undefined', async () => { + expect( + await ajvResolver(fixture.schemaC)(fixture.undefinedDataC, undefined, { + fields: fixture.fieldsRest, + criteriaMode: 'all', + shouldUseNativeValidation, + }), + ).toMatchSnapshot(); + }); + + it('should return different messages for different properties when walidation fails', async () => { + expect( + await ajvResolver(fixture.schemaD)( + fixture.invalidDataD, + {}, + { + fields: fixture.fieldsRest, + criteriaMode: 'all', + shouldUseNativeValidation, + }, + ), + ).toMatchSnapshot(); + }); + + it('should return different messages for different properties when requirement fails', async () => { + expect( + await ajvResolver(fixture.schemaD)({}, undefined, { + fields: fixture.fieldsRest, + criteriaMode: 'all', + shouldUseNativeValidation, + }), + ).toMatchSnapshot(); + }); + + it('should return different messages for different properties when some properties are undefined', async () => { + expect( + await ajvResolver(fixture.schemaD)(fixture.undefinedDataD, undefined, { + fields: fixture.fieldsRest, + criteriaMode: 'all', + shouldUseNativeValidation, + }), + ).toMatchSnapshot(); + }); + + it('should return customized errors for properties/items when walidation fails', async () => { + expect( + await ajvResolver(fixture.schemaE)( + fixture.invalidDataE, + {}, + { + fields: fixture.fieldsRest, + criteriaMode: 'all', + shouldUseNativeValidation, + }, + ), + ).toMatchSnapshot(); + }); + + it('should return customized errors for properties/items when requirement fails', async () => { + expect( + await ajvResolver(fixture.schemaE)({}, undefined, { + fields: fixture.fieldsRest, + criteriaMode: 'all', + shouldUseNativeValidation, + }), + ).toMatchSnapshot(); + }); + + it('should return customized errors for properties/items when some properties are undefined', async () => { + expect( + await ajvResolver(fixture.schemaE)(fixture.undefinedDataE, undefined, { + fields: fixture.fieldsRest, + criteriaMode: 'all', + shouldUseNativeValidation, + }), + ).toMatchSnapshot(); + }); + + it('should return a default message if there is no specific message for the error when walidation fails', async () => { + expect( + await ajvResolver(fixture.schemaF)( + fixture.invalidDataF, + {}, + { + fields: fixture.fieldsRest, + criteriaMode: 'all', + shouldUseNativeValidation, + }, + ), + ).toMatchSnapshot(); + }); + + it('should return a default message if there is no specific message for the error when requirement fails', async () => { + expect( + await ajvResolver(fixture.schemaF)({}, undefined, { + fields: fixture.fieldsRest, + criteriaMode: 'all', + shouldUseNativeValidation, + }), + ).toMatchSnapshot(); + }); + + it('should return a default message if there is no specific message for the error when some properties are undefined', async () => { + expect( + await ajvResolver(fixture.schemaF)(fixture.undefinedDataF, undefined, { + fields: fixture.fieldsRest, + criteriaMode: 'all', + shouldUseNativeValidation, + }), + ).toMatchSnapshot(); + }); +}); diff --git a/ajv/src/ajv.ts b/ajv/src/ajv.ts index 19146a13..ba55d5ad 100644 --- a/ajv/src/ajv.ts +++ b/ajv/src/ajv.ts @@ -2,47 +2,60 @@ import { toNestErrors, validateFieldsNatively } from '@hookform/resolvers'; import Ajv, { DefinedError } from 'ajv'; import ajvErrors from 'ajv-errors'; import { FieldError, appendErrors } from 'react-hook-form'; -import { Resolver } from './types'; +import { AjvError, Resolver } from './types'; const parseErrorSchema = ( - ajvErrors: DefinedError[], + ajvErrors: AjvError[], validateAllFieldCriteria: boolean, ) => { - // Ajv will return empty instancePath when require error - ajvErrors.forEach((error) => { + const parsedErrors: Record = {}; + + const reduceError = (error: AjvError) => { + // Ajv will return empty instancePath when require error if (error.keyword === 'required') { - error.instancePath += '/' + error.params.missingProperty; + error.instancePath += `/${error.params.missingProperty}`; } - }); - return ajvErrors.reduce>((previous, error) => { // `/deepObject/data` -> `deepObject.data` const path = error.instancePath.substring(1).replace(/\//g, '.'); - if (!previous[path]) { - previous[path] = { + if (!parsedErrors[path]) { + parsedErrors[path] = { message: error.message, type: error.keyword, }; } if (validateAllFieldCriteria) { - const types = previous[path].types; + const types = parsedErrors[path].types; const messages = types && types[error.keyword]; - previous[path] = appendErrors( + parsedErrors[path] = appendErrors( path, validateAllFieldCriteria, - previous, + parsedErrors, error.keyword, messages ? ([] as string[]).concat(messages as string[], error.message || '') : error.message, ) as FieldError; } + }; + + for (let index = 0; index < ajvErrors.length; index += 1) { + const error = ajvErrors[index]; + + if (error.keyword === 'errorMessage') { + error.params.errors.forEach((originalError) => { + originalError.message = error.message; + reduceError(originalError); + }); + } else { + reduceError(error); + } + } - return previous; - }, {}); + return parsedErrors; }; export const ajvResolver: Resolver = diff --git a/ajv/src/types.ts b/ajv/src/types.ts index 2defc4d9..6726ccda 100644 --- a/ajv/src/types.ts +++ b/ajv/src/types.ts @@ -1,4 +1,5 @@ import * as Ajv from 'ajv'; +import type { DefinedError, ErrorObject } from 'ajv'; import { FieldValues, ResolverOptions, ResolverResult } from 'react-hook-form'; export type Resolver = ( @@ -10,3 +11,10 @@ export type Resolver = ( context: TContext | undefined, options: ResolverOptions, ) => Promise>; + +// ajv doesn't export any types for errors with `keyword='errorMessage'` +type ErrorMessage = ErrorObject< + 'errorMessage', + { errors: (DefinedError & { emUsed: boolean })[] } +>; +export type AjvError = ErrorMessage | DefinedError;