From 873a7762e8ea592c200ba0170961b1448b2e95d1 Mon Sep 17 00:00:00 2001 From: Sassan Haradji Date: Mon, 4 Nov 2024 12:47:18 +0400 Subject: [PATCH] Add support for nested ZodEffects --- .../__tests__/ZodBridge.ts | 65 +++++++++++++++++++ packages/uniforms-bridge-zod/src/ZodBridge.ts | 20 ++++-- 2 files changed, 81 insertions(+), 4 deletions(-) diff --git a/packages/uniforms-bridge-zod/__tests__/ZodBridge.ts b/packages/uniforms-bridge-zod/__tests__/ZodBridge.ts index ae11c541d..4a39581d7 100644 --- a/packages/uniforms-bridge-zod/__tests__/ZodBridge.ts +++ b/packages/uniforms-bridge-zod/__tests__/ZodBridge.ts @@ -89,6 +89,29 @@ describe('ZodBridge', () => { expect(error?.issues?.[0]?.message).toBe(errorMessage); }); + it('works with chained refined schema', () => { + const firstErrorMessage = 'Different values'; + const secondErrorMessage = 'Different moduloes of 2 and 3'; + + const schema = object({ + a: number(), + b: number(), + }) + .refine(({ a, b }) => a === b, { + message: firstErrorMessage, + path: ['b'], + }) + .refine(({ a, b }) => a % 2 === b % 3, { + message: secondErrorMessage, + path: ['b'], + }); + + const bridge = new ZodBridge({ schema }); + const error = bridge.getValidator()({ a: 1, b: 2 }); + expect(error?.issues?.[0]?.message).toBe(firstErrorMessage); + expect(error?.issues?.[1]?.message).toBe(secondErrorMessage); + }); + it('works with super refined schema', () => { const errorMessage = 'Different values'; @@ -108,6 +131,37 @@ describe('ZodBridge', () => { const error = bridge.getValidator()({ a: 'a', b: 'b' }); expect(error?.issues?.[0]?.message).toBe(errorMessage); }); + + it('works with chained super refined schema', () => { + const firstErrorMessage = 'Different values'; + const secondErrorMessage = 'Different moduloes of 2 and 3'; + + const schema = object({ + a: number(), + b: number(), + }) + .superRefine((val, ctx) => { + if (val.a !== val.b) { + ctx.addIssue({ + code: ZodIssueCode.custom, + message: firstErrorMessage, + }); + } + }) + .superRefine((val, ctx) => { + if (val.a % 2 !== val.b % 3) { + ctx.addIssue({ + code: ZodIssueCode.custom, + message: secondErrorMessage, + }); + } + }); + + const bridge = new ZodBridge({ schema }); + const error = bridge.getValidator()({ a: 1, b: 2 }); + expect(error?.issues?.[0]?.message).toBe(firstErrorMessage); + expect(error?.issues?.[1]?.message).toBe(secondErrorMessage); + }); }); describe('#getErrorMessage', () => { @@ -244,6 +298,17 @@ describe('ZodBridge', () => { const bridge = new ZodBridge({ schema }); expect(bridge.getField('')).toBe(schema._def.schema); }); + + it('works with nested ZodEffects', () => { + const schema = object({}) + .refine(data => data) + .refine(data => data) + .refine(data => data); + const bridge = new ZodBridge({ schema }); + expect(bridge.getField('')).toBe( + schema._def.schema._def.schema._def.schema, + ); + }); }); describe('#getInitialValue', () => { diff --git a/packages/uniforms-bridge-zod/src/ZodBridge.ts b/packages/uniforms-bridge-zod/src/ZodBridge.ts index ec3da0af0..03353df94 100644 --- a/packages/uniforms-bridge-zod/src/ZodBridge.ts +++ b/packages/uniforms-bridge-zod/src/ZodBridge.ts @@ -55,15 +55,27 @@ type Option = { value: Value; }; +type BuildTuple = T['length'] extends L + ? T + : BuildTuple; + +type Decrement = + BuildTuple extends [any, ...infer R] ? R['length'] : never; + +type NestedZodEffect< + T extends ZodObject, + Depth extends number = 20, +> = Depth extends 0 ? T : ZodEffects>>; + export default class ZodBridge extends Bridge { - schema: ZodObject | ZodEffects>; + schema: ZodObject | NestedZodEffect>; provideDefaultLabelFromFieldName: boolean; constructor({ schema, provideDefaultLabelFromFieldName = true, }: { - schema: ZodObject | ZodEffects>; + schema: ZodObject | NestedZodEffect>; provideDefaultLabelFromFieldName?: boolean; }) { super(); @@ -108,8 +120,8 @@ export default class ZodBridge extends Bridge { getField(name: string) { let field: ZodType = this.schema; - if (this.schema instanceof ZodEffects) { - field = this.schema._def.schema; + while (field instanceof ZodEffects) { + field = field.innerType(); } for (const key of joinName(null, name)) {