diff --git a/joi/src/__tests__/__fixtures__/data.ts b/joi/src/__tests__/__fixtures__/data.ts index 3404baf0..a48f90e3 100644 --- a/joi/src/__tests__/__fixtures__/data.ts +++ b/joi/src/__tests__/__fixtures__/data.ts @@ -15,7 +15,9 @@ export const schema = Joi.object({ tags: Joi.array().items(Joi.string()).required(), enabled: Joi.boolean().required(), like: Joi.array() - .items(Joi.object({ id: Joi.number(), name: Joi.string().length(4) })) + .items( + Joi.object({ id: Joi.number(), name: Joi.string().length(4).regex(/a/) }), + ) .optional(), }); @@ -51,7 +53,7 @@ export const invalidData = { password: '___', email: '', birthYear: 'birthYear', - like: [{ id: 'z' }], + like: [{ id: 'z', name: 'r' }], }; export const fields: Record = { diff --git a/joi/src/__tests__/__snapshots__/joi.ts.snap b/joi/src/__tests__/__snapshots__/joi.ts.snap index 7277cb47..6c6d31b5 100644 --- a/joi/src/__tests__/__snapshots__/joi.ts.snap +++ b/joi/src/__tests__/__snapshots__/joi.ts.snap @@ -27,6 +27,11 @@ Object { "ref": undefined, "type": "number.base", }, + "name": Object { + "message": "\\"like[0].name\\" length must be 4 characters long", + "ref": undefined, + "type": "string.length", + }, }, ], "password": Object { @@ -80,6 +85,11 @@ Object { "ref": undefined, "type": "number.base", }, + "name": Object { + "message": "\\"like[0].name\\" length must be 4 characters long", + "ref": undefined, + "type": "string.length", + }, }, ], "password": Object { @@ -145,6 +155,15 @@ Object { "number.base": "\\"like[0].id\\" must be a number", }, }, + "name": Object { + "message": "\\"like[0].name\\" length must be 4 characters long", + "ref": undefined, + "type": "string.length", + "types": Object { + "string.length": "\\"like[0].name\\" length must be 4 characters long", + "string.pattern.base": "\\"like[0].name\\" with value \\"r\\" fails to match the required pattern: /a/", + }, + }, }, ], "password": Object { @@ -219,6 +238,15 @@ Object { "number.base": "\\"like[0].id\\" must be a number", }, }, + "name": Object { + "message": "\\"like[0].name\\" length must be 4 characters long", + "ref": undefined, + "type": "string.length", + "types": Object { + "string.length": "\\"like[0].name\\" length must be 4 characters long", + "string.pattern.base": "\\"like[0].name\\" with value \\"r\\" fails to match the required pattern: /a/", + }, + }, }, ], "password": Object { diff --git a/joi/src/joi.ts b/joi/src/joi.ts index 232944e9..1a0f9ad8 100644 --- a/joi/src/joi.ts +++ b/joi/src/joi.ts @@ -1,84 +1,63 @@ -import { appendErrors } from 'react-hook-form'; +import { appendErrors, FieldError } from 'react-hook-form'; import { toNestError } from '@hookform/resolvers'; -import * as Joi from 'joi'; -import { convertArrayToPathName } from '@hookform/resolvers'; +import type { ValidationError } from 'joi'; import { Resolver } from './types'; const parseErrorSchema = ( - error: Joi.ValidationError, + error: ValidationError, validateAllFieldCriteria: boolean, ) => - Array.isArray(error.details) - ? error.details.reduce( - (previous: Record, { path, message = '', type }) => { - const currentPath = convertArrayToPathName(path); + error.details.length + ? error.details.reduce>((previous, error) => { + const _path = error.path.join('.'); - return { - ...previous, - ...(path - ? previous[currentPath] && validateAllFieldCriteria - ? { - [currentPath]: appendErrors( - currentPath, - validateAllFieldCriteria, - previous, - type, - message, - ), - } - : { - [currentPath]: previous[currentPath] || { - message, - type, - ...(validateAllFieldCriteria - ? { - types: { [type]: message || true }, - } - : {}), - }, - } - : {}), - }; - }, - {}, - ) - : []; + if (!previous[_path]) { + previous[_path] = { message: error.message, type: error.type }; + } + + if (validateAllFieldCriteria) { + previous[_path] = appendErrors( + _path, + validateAllFieldCriteria, + previous, + error.type, + error.message, + ) as FieldError; + } + + return previous; + }, {}) + : {}; export const joiResolver: Resolver = ( schema, schemaOptions = { abortEarly: false, }, - { mode } = { mode: 'async' }, -) => async (values, context, { criteriaMode, fields }) => { - try { - let result; - if (mode === 'async') { - result = await schema.validateAsync(values, { - ...schemaOptions, - context, - }); - } else { - const { value, error } = schema.validate(values, { - ...schemaOptions, - context, - }); - - if (error) { - throw error; - } + resolverOptions = {}, +) => async (values, context, options) => { + const _schemaOptions = Object.assign(Object.assign({}, schemaOptions), { + context, + }); - result = value; + let result: Record = {}; + if (resolverOptions.mode === 'sync') { + result = schema.validate(values, _schemaOptions); + } else { + try { + result.value = await schema.validateAsync(values, _schemaOptions); + } catch (e) { + result.error = e; } - - return { - values: result, - errors: {}, - }; - } catch (e) { - return { - values: {}, - errors: toNestError(parseErrorSchema(e, criteriaMode === 'all'), fields), - }; } + + return { + values: result.error ? {} : result.value, + errors: result.error + ? toNestError( + parseErrorSchema(result.error, options.criteriaMode === 'all'), + options.fields, + ) + : {}, + }; }; diff --git a/joi/src/types.ts b/joi/src/types.ts index 11f04884..ede151c1 100644 --- a/joi/src/types.ts +++ b/joi/src/types.ts @@ -9,7 +9,7 @@ import type { AsyncValidationOptions, Schema } from 'joi'; export type Resolver = ( schema: T, schemaOptions?: AsyncValidationOptions, - factoryOptions?: { mode: 'async' | 'sync' }, + factoryOptions?: { mode?: 'async' | 'sync' }, ) => ( values: UnpackNestedValue, context: TContext | undefined,