From 873a7762e8ea592c200ba0170961b1448b2e95d1 Mon Sep 17 00:00:00 2001 From: Sassan Haradji Date: Mon, 4 Nov 2024 12:47:18 +0400 Subject: [PATCH 1/3] 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)) { From 712f57896b440ca4330d2d100f69c557aecd9f78 Mon Sep 17 00:00:00 2001 From: Sassan Haradji Date: Fri, 15 Nov 2024 20:58:10 +0400 Subject: [PATCH 2/3] chore: refinements based on review feedback --- packages/uniforms-bridge-zod/__tests__/ZodBridge.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/uniforms-bridge-zod/__tests__/ZodBridge.ts b/packages/uniforms-bridge-zod/__tests__/ZodBridge.ts index 4a39581d7..43c489850 100644 --- a/packages/uniforms-bridge-zod/__tests__/ZodBridge.ts +++ b/packages/uniforms-bridge-zod/__tests__/ZodBridge.ts @@ -91,7 +91,7 @@ describe('ZodBridge', () => { it('works with chained refined schema', () => { const firstErrorMessage = 'Different values'; - const secondErrorMessage = 'Different moduloes of 2 and 3'; + const secondErrorMessage = 'Different results'; const schema = object({ a: number(), @@ -134,7 +134,7 @@ describe('ZodBridge', () => { it('works with chained super refined schema', () => { const firstErrorMessage = 'Different values'; - const secondErrorMessage = 'Different moduloes of 2 and 3'; + const secondErrorMessage = 'Different results'; const schema = object({ a: number(), From b36ee3be3f28aac6cb5ec48ffe63b56737dd872e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Po=C5=9Bpiech?= <37746259+piotrpospiech@users.noreply.github.com> Date: Mon, 18 Nov 2024 15:17:29 +0100 Subject: [PATCH 3/3] updated schema type in the ZodBridge --- .../__tests__/ZodBridge.ts | 43 +++++++++++++------ packages/uniforms-bridge-zod/src/ZodBridge.ts | 19 ++------ 2 files changed, 32 insertions(+), 30 deletions(-) diff --git a/packages/uniforms-bridge-zod/__tests__/ZodBridge.ts b/packages/uniforms-bridge-zod/__tests__/ZodBridge.ts index 43c489850..9d26d18bc 100644 --- a/packages/uniforms-bridge-zod/__tests__/ZodBridge.ts +++ b/packages/uniforms-bridge-zod/__tests__/ZodBridge.ts @@ -10,6 +10,7 @@ import { enum as enum_, function as function_, instanceof as instanceof_, + infer as infer_, intersection, lazy, literal, @@ -43,7 +44,8 @@ describe('ZodBridge', () => { it('works with simple types', () => { const schema = object({ a: string(), b: number() }); - const bridge = new ZodBridge({ schema }); + type SchemaType = infer_; + const bridge = new ZodBridge({ schema }); const error = bridge.getValidator()({}); const issues = error?.issues; expect(bridge.getError('a', error)).toBe(issues?.[0]); @@ -52,7 +54,8 @@ describe('ZodBridge', () => { it('works with arrays', () => { const schema = object({ a: array(array(string())) }); - const bridge = new ZodBridge({ schema }); + type SchemaType = infer_; + const bridge = new ZodBridge({ schema }); const error = bridge.getValidator()({ a: [['x', 'y', 0], [1]] }); const issues = error?.issues; expect(bridge.getError('a', error)).toBe(null); @@ -65,7 +68,8 @@ describe('ZodBridge', () => { it('works with nested objects', () => { const schema = object({ a: object({ b: object({ c: string() }) }) }); - const bridge = new ZodBridge({ schema }); + type SchemaType = infer_; + const bridge = new ZodBridge({ schema }); const error = bridge.getValidator()({ a: { b: { c: 1 } } }); const issues = error?.issues; expect(bridge.getError('a', error)).toBe(null); @@ -84,7 +88,8 @@ describe('ZodBridge', () => { path: ['b'], }); - const bridge = new ZodBridge({ schema }); + type SchemaType = infer_; + const bridge = new ZodBridge({ schema }); const error = bridge.getValidator()({ a: 'a', b: 'b' }); expect(error?.issues?.[0]?.message).toBe(errorMessage); }); @@ -106,7 +111,8 @@ describe('ZodBridge', () => { path: ['b'], }); - const bridge = new ZodBridge({ schema }); + type SchemaType = infer_; + 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); @@ -127,7 +133,8 @@ describe('ZodBridge', () => { } }); - const bridge = new ZodBridge({ schema }); + type SchemaType = infer_; + const bridge = new ZodBridge({ schema }); const error = bridge.getValidator()({ a: 'a', b: 'b' }); expect(error?.issues?.[0]?.message).toBe(errorMessage); }); @@ -157,7 +164,8 @@ describe('ZodBridge', () => { } }); - const bridge = new ZodBridge({ schema }); + type SchemaType = infer_; + 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); @@ -174,7 +182,8 @@ describe('ZodBridge', () => { it('works with simple types', () => { const schema = object({ a: string(), b: number() }); - const bridge = new ZodBridge({ schema }); + type SchemaType = infer_; + const bridge = new ZodBridge({ schema }); const error = bridge.getValidator()({}); const issues = error?.issues; expect(bridge.getErrorMessage('a', error)).toBe(issues?.[0].message); @@ -183,7 +192,8 @@ describe('ZodBridge', () => { it('works with arrays', () => { const schema = object({ a: array(array(string())) }); - const bridge = new ZodBridge({ schema }); + type SchemaType = infer_; + const bridge = new ZodBridge({ schema }); const error = bridge.getValidator()({ a: [['x', 'y', 0], [1]] }); const issues = error?.issues; expect(bridge.getErrorMessage('a', error)).toBe(''); @@ -196,7 +206,8 @@ describe('ZodBridge', () => { it('works with nested objects', () => { const schema = object({ a: object({ b: object({ c: string() }) }) }); - const bridge = new ZodBridge({ schema }); + type SchemaType = infer_; + const bridge = new ZodBridge({ schema }); const error = bridge.getValidator()({ a: { b: { c: 1 } } }); const issues = error?.issues; expect(bridge.getErrorMessage('a', error)).toBe(''); @@ -221,7 +232,8 @@ describe('ZodBridge', () => { it('works with simple types', () => { const schema = object({ a: string(), b: number() }); - const bridge = new ZodBridge({ schema }); + type SchemaType = infer_; + const bridge = new ZodBridge({ schema }); const error = bridge.getValidator()({}); const messages = ['A: Required', 'B: Required']; expect(bridge.getErrorMessages(error)).toEqual(messages); @@ -229,7 +241,8 @@ describe('ZodBridge', () => { it('works with arrays', () => { const schema = object({ a: array(array(string())) }); - const bridge = new ZodBridge({ schema }); + type SchemaType = infer_; + const bridge = new ZodBridge({ schema }); const error = bridge.getValidator()({ a: [['x', 'y', 0], [1]] }); const messages = [ 'A (0, 2): Expected string, received number', @@ -240,7 +253,8 @@ describe('ZodBridge', () => { it('works with nested objects', () => { const schema = object({ a: object({ b: object({ c: string() }) }) }); - const bridge = new ZodBridge({ schema }); + type SchemaType = infer_; + const bridge = new ZodBridge({ schema }); const error = bridge.getValidator()({ a: { b: { c: 1 } } }); const messages = ['C: Expected string, received number']; expect(bridge.getErrorMessages(error)).toEqual(messages); @@ -751,7 +765,8 @@ describe('ZodBridge', () => { describe('#getValidator', () => { it('return a function', () => { const schema = object({}); - const bridge = new ZodBridge({ schema }); + type SchemaType = infer_; + const bridge = new ZodBridge({ schema }); const validator = bridge.getValidator(); expect(validator).toEqual(expect.any(Function)); expect(validator({})).toEqual(null); diff --git a/packages/uniforms-bridge-zod/src/ZodBridge.ts b/packages/uniforms-bridge-zod/src/ZodBridge.ts index 03353df94..ef6d14592 100644 --- a/packages/uniforms-bridge-zod/src/ZodBridge.ts +++ b/packages/uniforms-bridge-zod/src/ZodBridge.ts @@ -17,7 +17,6 @@ import { ZodNumberDef, ZodObject, ZodOptional, - ZodRawShape, ZodString, ZodType, } from 'zod'; @@ -55,27 +54,15 @@ 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 | NestedZodEffect>; +export default class ZodBridge extends Bridge { + schema: ZodType; provideDefaultLabelFromFieldName: boolean; constructor({ schema, provideDefaultLabelFromFieldName = true, }: { - schema: ZodObject | NestedZodEffect>; + schema: ZodType; provideDefaultLabelFromFieldName?: boolean; }) { super();