import { toNestErrors, validateFieldsNatively } from '@hookform/resolvers'; import type { FieldError, FieldErrors, FieldValues, Resolver, } from 'react-hook-form'; import * as t from 'typanion'; function parseErrors(errors: string[], parsedErrors: FieldErrors = {}) { return errors.reduce((acc, error) => { const fieldIndex = error.indexOf(':'); const field = error.slice(1, fieldIndex); const message = error.slice(fieldIndex + 1).trim(); acc[field] = { message, } as FieldError; return acc; }, parsedErrors); } export function typanionResolver( schema: t.StrictValidator, schemaOptions?: Pick, resolverOptions?: { mode?: 'async' | 'sync'; raw?: false; }, ): Resolver>; export function typanionResolver( schema: t.StrictValidator, schemaOptions: Pick | undefined, resolverOptions: { mode?: 'async' | 'sync'; raw: true; }, ): Resolver; /** * Creates a resolver for react-hook-form using Typanion schema validation * @param {t.StrictValidator} schema - The Typanion schema to validate against * @param {Pick} [schemaOptions] - Optional Typanion validation options * @returns {Resolver>} A resolver function compatible with react-hook-form * @example * const schema = t.isObject({ * name: t.isString(), * age: t.isInteger() * }); * * useForm({ * resolver: typanionResolver(schema) * }); */ export function typanionResolver( schema: t.StrictValidator, schemaOptions: Pick = {}, ): Resolver { return (values: Input, _, options) => { const rawErrors: string[] = []; const isValid = schema( values, Object.assign( {}, { errors: rawErrors, }, schemaOptions, ), ); const parsedErrors = parseErrors(rawErrors); if (isValid) { options.shouldUseNativeValidation && validateFieldsNatively(parsedErrors, options); return { values, errors: {} }; } return { values: {}, errors: toNestErrors(parsedErrors, options) }; }; }