UNPKG

@hookform/resolvers

Version:

React Hook Form validation resolvers: Yup, Joi, Superstruct, Zod, Vest, Class Validator, io-ts, Nope, computed-types, TypeBox, arktype, Typanion, Effect-TS and VineJS

106 lines (96 loc) 3.26 kB
import { toNestErrors, validateFieldsNatively } from '@hookform/resolvers'; import { Effect, Schema } from 'effect'; import { ArrayFormatter, decodeUnknown } from 'effect/ParseResult'; import { ParseOptions } from 'effect/SchemaAST'; import { type FieldError, FieldValues, Resolver, appendErrors, } from 'react-hook-form'; export function effectTsResolver<Input extends FieldValues, Context, Output>( schema: Schema.Schema<Output, Input>, schemaOptions?: ParseOptions, resolverOptions?: { mode?: 'async' | 'sync'; raw?: false; }, ): Resolver<Input, Context, Output>; export function effectTsResolver<Input extends FieldValues, Context, Output>( schema: Schema.Schema<Output, Input>, schemaOptions: ParseOptions | undefined, resolverOptions: { mode?: 'async' | 'sync'; raw: true; }, ): Resolver<Input, Context, Input>; /** * Creates a resolver for react-hook-form using Effect.ts schema validation * @param {Schema.Schema<TFieldValues, I>} schema - The Effect.ts schema to validate against * @param {ParseOptions} [schemaOptions] - Optional Effect.ts validation options * @returns {Resolver<Schema.Schema.Type<typeof schema>>} A resolver function compatible with react-hook-form * @example * const schema = Schema.Struct({ * name: Schema.String, * age: Schema.Number * }); * * useForm({ * resolver: effectTsResolver(schema) * }); */ export function effectTsResolver<Input extends FieldValues, Context, Output>( schema: Schema.Schema<Output, Input>, schemaOptions: ParseOptions = { errors: 'all', onExcessProperty: 'ignore' }, ): Resolver<Input, Context, Output | Input> { return (values, _, options) => { return decodeUnknown( schema, schemaOptions, )(values).pipe( Effect.catchAll((parseIssue) => Effect.flip(ArrayFormatter.formatIssue(parseIssue)), ), Effect.mapError((issues) => { 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); }), Effect.tap(() => Effect.sync( () => options.shouldUseNativeValidation && validateFieldsNatively({}, options), ), ), Effect.match({ onFailure: (errors) => ({ errors, values: {} }), onSuccess: (result) => ({ errors: {}, values: result }), }), Effect.runPromise, ); }; }