From 77a762d0963d8994f416d4a3358a054fe484b965 Mon Sep 17 00:00:00 2001 From: Jesse Kelly Date: Sat, 25 May 2024 14:47:08 +0200 Subject: [PATCH] updates --- .../src/tests/FieldApi.spec.ts | 40 ++++++++++++++++--- .../effect-form-adapter/src/tests/utils.ts | 40 +++++++++++++++---- packages/effect-form-adapter/src/validator.ts | 17 ++++---- 3 files changed, 76 insertions(+), 21 deletions(-) diff --git a/packages/effect-form-adapter/src/tests/FieldApi.spec.ts b/packages/effect-form-adapter/src/tests/FieldApi.spec.ts index 335eaf58b..fb84fe017 100644 --- a/packages/effect-form-adapter/src/tests/FieldApi.spec.ts +++ b/packages/effect-form-adapter/src/tests/FieldApi.spec.ts @@ -1,8 +1,14 @@ import { describe, expect, it } from 'vitest' import { FieldApi, FormApi } from '@tanstack/form-core' -import { effectValidator } from '../validator' -import { asyncSchema, schema, sleep } from './utils' +import { createValidator, effectValidator } from '../validator' +import { + asyncSchema, + ctxLayer, + schema, + schemaWithContext, + sleep, +} from './utils' describe('field api', () => { it('should run an onChange with Schema.minLength validation', () => { @@ -79,14 +85,38 @@ describe('field api', () => { expect(field.getMeta().errors).toEqual([]) field.setValue('a', { touch: true }) await sleep(30) - expect(field.getMeta().errors).toEqual([ - 'You must have a length of at least 3', - ]) + expect(field.getMeta().errors).toEqual(['async schema error']) field.setValue('asdf', { touch: true }) await sleep(30) expect(field.getMeta().errors).toEqual([]) }) + it('Effect error message w/ context', async () => { + const customValidator = createValidator(ctxLayer) + + const form = new FormApi({ + defaultValues: { + name: '', + }, + }) + + const field = new FieldApi({ + form, + validatorAdapter: customValidator, + name: 'name', + validators: { + onChange: schemaWithContext, + }, + }) + + field.mount() + + expect(field.getMeta().errors).toEqual([]) + field.setValue('a', { touch: true }) + await sleep(30) + expect(field.getMeta().errors).toEqual(['ctx-123']) + }) + it('should run an onChangeAsyc fn with validation option enabled', async () => { const form = new FormApi({ defaultValues: { diff --git a/packages/effect-form-adapter/src/tests/utils.ts b/packages/effect-form-adapter/src/tests/utils.ts index 28d67ca1d..70ac3bce8 100644 --- a/packages/effect-form-adapter/src/tests/utils.ts +++ b/packages/effect-form-adapter/src/tests/utils.ts @@ -1,8 +1,9 @@ -import { Context, Effect, Layer } from 'effect' +import { Context, Effect, Layer, Option } from 'effect' import { Schema } from '@effect/schema' +import * as ParseResult from '@effect/schema/ParseResult' export class Ctx extends Context.Tag('Ctx')() {} -export const ctx = Layer.succeed(Ctx, Ctx.of('ctx-123')) +export const ctxLayer = Layer.succeed(Ctx, Ctx.of('ctx-123')) export const schema = Schema.String.pipe( Schema.minLength(3, { @@ -10,14 +11,37 @@ export const schema = Schema.String.pipe( }), ) -export const schemaWithContext = Schema.transformOrFail(schema, schema, { - decode: () => Effect.flatMap(Ctx, (c) => Effect.succeed(c)), - encode: (value) => Effect.succeed(value), +const delay = Effect.delay('10 millis') +export const asyncSchema = Schema.transformOrFail( + Schema.String, + Schema.String, + { + decode: (value, _, ast) => + delay( + value.length >= 3 + ? Effect.succeed(value) + : Effect.fail(new ParseResult.Type(ast, value, 'inner msg')), + ), + encode: (value) => Effect.succeed(value).pipe(delay), + }, +).annotations({ + message: () => delay(Effect.succeed('async schema error')), }) -export const asyncSchema = Schema.transformOrFail(schema, schema, { - decode: (value) => Effect.succeed(value).pipe(Effect.delay('10 millis')), - encode: (value) => Effect.succeed(value).pipe(Effect.delay('10 millis')), +export const schemaWithContext = Schema.transformOrFail( + Schema.String, + Schema.String, + { + decode: (_, value, ast) => + Effect.fail(new ParseResult.Type(ast, value, '')), + encode: (value) => Effect.succeed(value), + }, +).annotations({ + message: () => + Effect.map( + Effect.serviceOption(Ctx), + Option.getOrElse(() => 'no context'), + ), }) export function sleep(timeout: number): Promise { diff --git a/packages/effect-form-adapter/src/validator.ts b/packages/effect-form-adapter/src/validator.ts index e11477ccd..4a10d7bd8 100644 --- a/packages/effect-form-adapter/src/validator.ts +++ b/packages/effect-form-adapter/src/validator.ts @@ -13,15 +13,16 @@ export const createValidator = (layer: Layer.Layer) => { const validator: Validator> = () => ({ validate( { value }: { value: unknown }, - schema: Schema.Schema, + schema: Schema.Schema, ): ValidationError { - const result = Schema.decodeUnknownEither(schema)(value) - if (Either.isLeft(result)) { - return ArrayFormatter.formatErrorSync(result.left) - .map((e) => e.message) - .join(', ') // must be joined into 1 string - } - return + const exit = runtime.runSyncExit( + Schema.decodeUnknown(schema)(value).pipe( + Effect.flip, + Effect.flatMap(ArrayFormatter.formatError), + Effect.map((es) => es.map((e) => e.message).join(', ')), + ), + ) + return Exit.getOrElse(exit, () => undefined) }, async validateAsync( { value }: { value: unknown },