From 96e152694aa45a90ac30f61e077f2e49f79a3f8e Mon Sep 17 00:00:00 2001 From: Max Freedom Pollard <272618364+MaxFreedomPollard@users.noreply.github.com> Date: Sun, 6 Sep 2026 03:34:18 -0400 Subject: [PATCH] fix(zod): treat @uuid without a version as any UUID version `addStringValidation` in packages/zod/src/utils.ts mapped the `@uuid` attribute with `if (version === 7) uuidv7() else uuidv4()`, so the no-version form fell into the v4 branch and rejected every other UUID version. stdlib.zmodel declares the version argument as optional and documents the attribute as "Validates a string field value is a valid UUID", and the `isUuid()` branch in the same file already passes an undefined version to `z.uuid()`, which accepts any version. Writing a v7 or v1 UUID into a `String @uuid` field therefore failed with `Validation error: Invalid UUID`. v7 is the shape ZenStack's own `uuid(7)` generator produces, at packages/orm/src/client/crud/operations/base.ts:1119. Only the no-version branch changes, to `result.uuid()`. `@uuid(4)` and `@uuid(7)` stay pinned, and the `@uuid` check in attribute-application-validator.ts already rejects any other version literal. --- packages/zod/src/utils.ts | 6 ++++-- tests/e2e/orm/validation/toplevel.test.ts | 17 +++++++++++++++++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/packages/zod/src/utils.ts b/packages/zod/src/utils.ts index 3aeb693b7..c5a949afd 100644 --- a/packages/zod/src/utils.ts +++ b/packages/zod/src/utils.ts @@ -82,10 +82,12 @@ export function addStringValidation( } case '@uuid': { const version = getArgValue(attr.args?.[0]?.value); - if (version === 7) { + if (version === 4) { + result = result.uuidv4(); + } else if (version === 7) { result = result.uuidv7(); } else { - result = result.uuidv4(); + result = result.uuid(); } break; } diff --git a/tests/e2e/orm/validation/toplevel.test.ts b/tests/e2e/orm/validation/toplevel.test.ts index f1d2fcde5..2cc3b3a8d 100644 --- a/tests/e2e/orm/validation/toplevel.test.ts +++ b/tests/e2e/orm/validation/toplevel.test.ts @@ -20,6 +20,7 @@ describe('Toplevel field validation tests', () => { str10 String? @time(-1) str11 String? @uuid str12 String? @uuid(7) + str13 String? @uuid(4) } `, ); @@ -120,11 +121,27 @@ describe('Toplevel field validation tests', () => { // satisfies @uuid await expect(_t({ str11: '20ef31c8-a2c6-4dca-b87b-838e364ab4b3' })).toResolveTruthy(); + // satisfies @uuid, which is not pinned to a version, with a v7 value + await expect(_t({ str11: '019ff964-2f1d-7668-9a76-8648f2af9146' })).toResolveTruthy(); + // violates @uuid(7) await expect(_t({ str12: 'not-a-uuid' })).toBeRejectedByValidation(['Invalid UUID']); + // violates @uuid(7) with a v4 value + await expect(_t({ str12: '20ef31c8-a2c6-4dca-b87b-838e364ab4b3' })).toBeRejectedByValidation([ + 'Invalid UUID', + ]); + // satisfies @uuid(7) await expect(_t({ str12: '019ff964-2f1d-7668-9a76-8648f2af9146' })).toResolveTruthy(); + + // violates @uuid(4) with a v7 value + await expect(_t({ str13: '019ff964-2f1d-7668-9a76-8648f2af9146' })).toBeRejectedByValidation([ + 'Invalid UUID', + ]); + + // satisfies @uuid(4) + await expect(_t({ str13: '20ef31c8-a2c6-4dca-b87b-838e364ab4b3' })).toResolveTruthy(); } });