From b2215e97b1b053fbeb1ecef1f6571cafdfb754de Mon Sep 17 00:00:00 2001 From: Austin Chauncey Date: Thu, 16 Feb 2023 17:24:53 -0700 Subject: [PATCH 1/3] feat: add support for ulids --- deno/lib/ZodError.ts | 1 + deno/lib/__tests__/string.test.ts | 10 ++++++++++ deno/lib/types.ts | 18 ++++++++++++++++++ src/ZodError.ts | 1 + src/__tests__/string.test.ts | 10 ++++++++++ src/types.ts | 18 ++++++++++++++++++ 6 files changed, 58 insertions(+) diff --git a/deno/lib/ZodError.ts b/deno/lib/ZodError.ts index 365cb3cae..59a500756 100644 --- a/deno/lib/ZodError.ts +++ b/deno/lib/ZodError.ts @@ -96,6 +96,7 @@ export type StringValidation = | "regex" | "cuid" | "cuid2" + | "ulid" | "datetime" | { startsWith: string } | { endsWith: string }; diff --git a/deno/lib/__tests__/string.test.ts b/deno/lib/__tests__/string.test.ts index 3df55f7b2..c372a6590 100644 --- a/deno/lib/__tests__/string.test.ts +++ b/deno/lib/__tests__/string.test.ts @@ -194,6 +194,16 @@ test("cuid2", () => { } }); +test("ulid", () => { + const cuid = z.string().cuid(); + cuid.parse("01ARZ3NDEKTSV4RRFFQ69G5FAV"); + const result = cuid.safeParse("invalidulid"); + expect(result.success).toEqual(false); + if (!result.success) { + expect(result.error.issues[0].message).toEqual("Invalid ulid"); + } +}); + test("regex", () => { z.string() .regex(/^moo+$/) diff --git a/deno/lib/types.ts b/deno/lib/types.ts index df6c1391d..8520ab187 100644 --- a/deno/lib/types.ts +++ b/deno/lib/types.ts @@ -496,6 +496,7 @@ export type ZodStringCheck = | { kind: "uuid"; message?: string } | { kind: "cuid"; message?: string } | { kind: "cuid2"; message?: string } + | { kind: "ulid"; message?: string } | { kind: "startsWith"; value: string; message?: string } | { kind: "endsWith"; value: string; message?: string } | { kind: "regex"; regex: RegExp; message?: string } @@ -515,6 +516,7 @@ export interface ZodStringDef extends ZodTypeDef { const cuidRegex = /^c[^\s-]{8,}$/i; const cuid2Regex = /^[a-z][a-z0-9]*$/; +const ulidRegex = /[0-9A-HJKMNP-TV-Z]{26}/; const uuidRegex = /^([a-f0-9]{8}-[a-f0-9]{4}-[1-5][a-f0-9]{3}-[a-f0-9]{4}-[a-f0-9]{12}|00000000-0000-0000-0000-000000000000)$/i; // from https://stackoverflow.com/a/46181/1550155 @@ -692,6 +694,16 @@ export class ZodString extends ZodType { }); status.dirty(); } + } else if (check.kind === "ulid") { + if (!ulidRegex.test(input.data)) { + ctx = this._getOrReturnCtx(input, ctx); + addIssueToContext(ctx, { + validation: "ulid", + code: ZodIssueCode.invalid_string, + message: check.message, + }); + status.dirty(); + } } else if (check.kind === "url") { try { new URL(input.data); @@ -794,6 +806,9 @@ export class ZodString extends ZodType { cuid2(message?: errorUtil.ErrMessage) { return this._addCheck({ kind: "cuid2", ...errorUtil.errToObj(message) }); } + ulid(message?: errorUtil.ErrMessage) { + return this._addCheck({ kind: "ulid", ...errorUtil.errToObj(message) }); + } datetime( options?: | string @@ -903,6 +918,9 @@ export class ZodString extends ZodType { get isCUID2() { return !!this._def.checks.find((ch) => ch.kind === "cuid2"); } + get isULID() { + return !!this._def.checks.find((ch) => ch.kind === "cuid2"); + } get minLength() { let min: number | null = null; diff --git a/src/ZodError.ts b/src/ZodError.ts index 03a34432a..ed5368108 100644 --- a/src/ZodError.ts +++ b/src/ZodError.ts @@ -96,6 +96,7 @@ export type StringValidation = | "regex" | "cuid" | "cuid2" + | "ulid" | "datetime" | { startsWith: string } | { endsWith: string }; diff --git a/src/__tests__/string.test.ts b/src/__tests__/string.test.ts index 1f51004db..e98afe1bd 100644 --- a/src/__tests__/string.test.ts +++ b/src/__tests__/string.test.ts @@ -193,6 +193,16 @@ test("cuid2", () => { } }); +test("ulid", () => { + const cuid = z.string().cuid(); + cuid.parse("01ARZ3NDEKTSV4RRFFQ69G5FAV"); + const result = cuid.safeParse("invalidulid"); + expect(result.success).toEqual(false); + if (!result.success) { + expect(result.error.issues[0].message).toEqual("Invalid ulid"); + } +}); + test("regex", () => { z.string() .regex(/^moo+$/) diff --git a/src/types.ts b/src/types.ts index 2af24c0a9..c377e71e1 100644 --- a/src/types.ts +++ b/src/types.ts @@ -496,6 +496,7 @@ export type ZodStringCheck = | { kind: "uuid"; message?: string } | { kind: "cuid"; message?: string } | { kind: "cuid2"; message?: string } + | { kind: "ulid"; message?: string } | { kind: "startsWith"; value: string; message?: string } | { kind: "endsWith"; value: string; message?: string } | { kind: "regex"; regex: RegExp; message?: string } @@ -515,6 +516,7 @@ export interface ZodStringDef extends ZodTypeDef { const cuidRegex = /^c[^\s-]{8,}$/i; const cuid2Regex = /^[a-z][a-z0-9]*$/; +const ulidRegex = /[0-9A-HJKMNP-TV-Z]{26}/; const uuidRegex = /^([a-f0-9]{8}-[a-f0-9]{4}-[1-5][a-f0-9]{3}-[a-f0-9]{4}-[a-f0-9]{12}|00000000-0000-0000-0000-000000000000)$/i; // from https://stackoverflow.com/a/46181/1550155 @@ -692,6 +694,16 @@ export class ZodString extends ZodType { }); status.dirty(); } + } else if (check.kind === "ulid") { + if (!ulidRegex.test(input.data)) { + ctx = this._getOrReturnCtx(input, ctx); + addIssueToContext(ctx, { + validation: "ulid", + code: ZodIssueCode.invalid_string, + message: check.message, + }); + status.dirty(); + } } else if (check.kind === "url") { try { new URL(input.data); @@ -794,6 +806,9 @@ export class ZodString extends ZodType { cuid2(message?: errorUtil.ErrMessage) { return this._addCheck({ kind: "cuid2", ...errorUtil.errToObj(message) }); } + ulid(message?: errorUtil.ErrMessage) { + return this._addCheck({ kind: "ulid", ...errorUtil.errToObj(message) }); + } datetime( options?: | string @@ -903,6 +918,9 @@ export class ZodString extends ZodType { get isCUID2() { return !!this._def.checks.find((ch) => ch.kind === "cuid2"); } + get isULID() { + return !!this._def.checks.find((ch) => ch.kind === "cuid2"); + } get minLength() { let min: number | null = null; From b051546bc4d13190431406cf14b3a880004ed967 Mon Sep 17 00:00:00 2001 From: Austin Chauncey Date: Thu, 16 Feb 2023 17:30:55 -0700 Subject: [PATCH 2/3] chore: update docs --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 065fca695..698dc7f81 100644 --- a/README.md +++ b/README.md @@ -589,6 +589,7 @@ z.string().url(); z.string().uuid(); z.string().cuid(); z.string().cuid2(); +z.string().ulid(); z.string().regex(regex); z.string().startsWith(string); z.string().endsWith(string); From 04e2705a9aa75103564ddbef2492020fc60e3984 Mon Sep 17 00:00:00 2001 From: Austin Chauncey Date: Sun, 19 Feb 2023 04:17:12 -0700 Subject: [PATCH 3/3] chore: cleanup --- deno/lib/types.ts | 2 +- src/types.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/deno/lib/types.ts b/deno/lib/types.ts index 8520ab187..47b12d9cb 100644 --- a/deno/lib/types.ts +++ b/deno/lib/types.ts @@ -919,7 +919,7 @@ export class ZodString extends ZodType { return !!this._def.checks.find((ch) => ch.kind === "cuid2"); } get isULID() { - return !!this._def.checks.find((ch) => ch.kind === "cuid2"); + return !!this._def.checks.find((ch) => ch.kind === "ulid"); } get minLength() { diff --git a/src/types.ts b/src/types.ts index c377e71e1..a9f9b2e8d 100644 --- a/src/types.ts +++ b/src/types.ts @@ -919,7 +919,7 @@ export class ZodString extends ZodType { return !!this._def.checks.find((ch) => ch.kind === "cuid2"); } get isULID() { - return !!this._def.checks.find((ch) => ch.kind === "cuid2"); + return !!this._def.checks.find((ch) => ch.kind === "ulid"); } get minLength() {