From 81dec88ab3654d52125626e7db79973d2821b903 Mon Sep 17 00:00:00 2001 From: Dylan Lundy <4567380+diesal11@users.noreply.github.com> Date: Wed, 12 Mar 2025 17:26:01 +1030 Subject: [PATCH 1/2] fix(JSON): @default not working for JSON type & Typed JSON lists generating invalid Zod schemas --- .../attribute-application-validator.ts | 7 ++++ .../schema/src/plugins/zod/transformer.ts | 4 +- tests/regression/tests/issue-2039.test.ts | 42 +++++++++++++++++++ 3 files changed, 52 insertions(+), 1 deletion(-) create mode 100644 tests/regression/tests/issue-2039.test.ts diff --git a/packages/schema/src/language-server/validator/attribute-application-validator.ts b/packages/schema/src/language-server/validator/attribute-application-validator.ts index b22a6f372..f536d4d66 100644 --- a/packages/schema/src/language-server/validator/attribute-application-validator.ts +++ b/packages/schema/src/language-server/validator/attribute-application-validator.ts @@ -267,6 +267,12 @@ function assignableToAttributeParam(arg: AttributeArg, param: AttributeParam, at if (dstType === 'ContextType') { // ContextType is inferred from the attribute's container's type if (isDataModelField(attr.$container)) { + // If the field is Typed JSON, and the param is @default, the argument must be a string + const dstIsTypedJson = attr.$container.attributes.find((attr) => attr.decl.ref?.name === '@json'); + if (dstIsTypedJson && param.default) { + return argResolvedType.decl === 'String'; + } + dstIsArray = attr.$container.type.array; } } @@ -327,6 +333,7 @@ function assignableToAttributeParam(arg: AttributeArg, param: AttributeParam, at if (!attr.$container?.type?.type) { return false; } + dstType = mapBuiltinTypeToExpressionType(attr.$container.type.type); dstIsArray = attr.$container.type.array; } else { diff --git a/packages/schema/src/plugins/zod/transformer.ts b/packages/schema/src/plugins/zod/transformer.ts index 8e7364669..899c6c473 100644 --- a/packages/schema/src/plugins/zod/transformer.ts +++ b/packages/schema/src/plugins/zod/transformer.ts @@ -197,7 +197,9 @@ export default class Transformer { if (replaceJsonWithTypeDef) { const dmField = contextDataModel?.fields.find((f) => f.name === field.name); if (isTypeDef(dmField?.type.reference?.ref)) { - alternatives = [`z.lazy(() => ${upperCaseFirst(dmField?.type.reference!.$refText)}Schema)`]; + const isList = dmField.type.array; + const lazyStr = `z.lazy(() => ${upperCaseFirst(dmField.type.reference!.$refText)}Schema)`; + alternatives = [isList ? `${lazyStr}.array()` : lazyStr]; } } diff --git a/tests/regression/tests/issue-2039.test.ts b/tests/regression/tests/issue-2039.test.ts new file mode 100644 index 000000000..bddca560a --- /dev/null +++ b/tests/regression/tests/issue-2039.test.ts @@ -0,0 +1,42 @@ +import { createPostgresDb, loadSchema } from '@zenstackhq/testtools'; + +describe('issue 2039', () => { + it('regression', async () => { + const dbUrl = await createPostgresDb('issue-2039'); + const { zodSchemas, enhance } = await loadSchema( + ` +type Foo { + a String +} + +model Bar { + id String @id @default(cuid()) + foo Foo @json @default("{ \\"a\\": \\"a\\" }") + fooList Foo[] @json @default("[{ \\"a\\": \\"b\\" }]") + @@allow('all', true) +} + `, + { + fullZod: true, + provider: 'postgresql', + dbUrl, + } + ); + + // Ensure default values are correctly set + const db = enhance(); + await expect(db.bar.create({ data: {} })).resolves.toMatchObject({ + id: expect.any(String), + foo: { a: 'a' }, + fooList: [{ a: 'b' }], + }); + + // Ensure Zod Schemas are correctly generated + expect( + zodSchemas.objects.BarCreateInputObjectSchema.safeParse({ + foo: { a: 'a' }, + fooList: [{ a: 'a' }], + }).success + ).toBeTruthy(); + }); +}); From 570512fd28488f9f1aa0ac3c6ed399d7a2d3d620 Mon Sep 17 00:00:00 2001 From: ymc9 <104139426+ymc9@users.noreply.github.com> Date: Wed, 12 Mar 2025 17:52:12 -0700 Subject: [PATCH 2/2] use the `hasAttribute` helper --- .../validator/attribute-application-validator.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/schema/src/language-server/validator/attribute-application-validator.ts b/packages/schema/src/language-server/validator/attribute-application-validator.ts index f536d4d66..0efa760b8 100644 --- a/packages/schema/src/language-server/validator/attribute-application-validator.ts +++ b/packages/schema/src/language-server/validator/attribute-application-validator.ts @@ -268,7 +268,7 @@ function assignableToAttributeParam(arg: AttributeArg, param: AttributeParam, at // ContextType is inferred from the attribute's container's type if (isDataModelField(attr.$container)) { // If the field is Typed JSON, and the param is @default, the argument must be a string - const dstIsTypedJson = attr.$container.attributes.find((attr) => attr.decl.ref?.name === '@json'); + const dstIsTypedJson = hasAttribute(attr.$container, '@json'); if (dstIsTypedJson && param.default) { return argResolvedType.decl === 'String'; }