diff --git a/.changeset/hip-actors-wink.md b/.changeset/hip-actors-wink.md new file mode 100644 index 00000000..842de257 --- /dev/null +++ b/.changeset/hip-actors-wink.md @@ -0,0 +1,5 @@ +--- +'@conform-to/zod': minor +--- + +fix number and bigint coercion diff --git a/packages/conform-zod/coercion.ts b/packages/conform-zod/coercion.ts index a6a8cd63..db584981 100644 --- a/packages/conform-zod/coercion.ts +++ b/packages/conform-zod/coercion.ts @@ -119,7 +119,7 @@ export function enableTypeCoercion( schema = any() .transform((value) => coerceString(value, (text) => - text.trim() === '' ? Number.NaN : Number(text), + text.trim() === '' ? text : Number(text), ), ) .pipe(type); @@ -132,14 +132,14 @@ export function enableTypeCoercion( } else if (def.typeName === 'ZodDate') { schema = any() .transform((value) => - coerceString(value, (timestamp) => { - const date = new Date(timestamp); + coerceString(value, (text) => { + const date = new Date(text); // z.date() does not expose a quick way to set invalid_date error // This gets around it by returning the original string if it's invalid // See https://github.com/colinhacks/zod/issues/1526 if (isNaN(date.getTime())) { - return timestamp; + return text; } return date; @@ -148,7 +148,18 @@ export function enableTypeCoercion( .pipe(type); } else if (def.typeName === 'ZodBigInt') { schema = any() - .transform((value) => coerceString(value, BigInt)) + .transform((value) => + coerceString(value, (text) => { + if (text.trim() === '') { + return text; + } + try { + return BigInt(text); + } catch { + return text; + } + }), + ) .pipe(type); } else if (def.typeName === 'ZodArray') { schema = any() diff --git a/tests/conform-zod.spec.ts b/tests/conform-zod.spec.ts index d642f1dc..124be2c0 100644 --- a/tests/conform-zod.spec.ts +++ b/tests/conform-zod.spec.ts @@ -405,6 +405,74 @@ describe('conform-zod', () => { }); }); + test('z.bigint', () => { + const schema = z.object({ + test: z + .bigint({ required_error: 'required', invalid_type_error: 'invalid' }) + .min(1n, 'min') + .max(10n, 'max') + .multipleOf(2n, 'step'), + }); + const file = new File([], ''); + + expect(parseWithZod(createFormData([]), { schema })).toEqual({ + status: 'error', + payload: {}, + error: { test: ['required'] }, + reply: expect.any(Function), + }); + expect( + parseWithZod(createFormData([['test', '']]), { + schema, + }), + ).toEqual({ + status: 'error', + payload: { test: '' }, + error: { test: ['required'] }, + reply: expect.any(Function), + }); + expect( + parseWithZod(createFormData([['test', 'abc']]), { + schema, + }), + ).toEqual({ + status: 'error', + payload: { test: 'abc' }, + error: { test: ['invalid'] }, + reply: expect.any(Function), + }); + expect( + parseWithZod(createFormData([['test', file]]), { + schema, + }), + ).toEqual({ + status: 'error', + payload: { test: file }, + error: { test: ['invalid'] }, + reply: expect.any(Function), + }); + expect( + parseWithZod(createFormData([['test', '5']]), { + schema, + }), + ).toEqual({ + status: 'error', + payload: { test: '5' }, + error: { test: ['step'] }, + reply: expect.any(Function), + }); + expect( + parseWithZod(createFormData([['test', ' ']]), { + schema, + }), + ).toEqual({ + status: 'error', + payload: { test: ' ' }, + error: { test: ['invalid'] }, + reply: expect.any(Function), + }); + }); + test('z.date', () => { const schema = z.object({ test: z