Skip to content

Commit

Permalink
feat: z.string().ulid() - add support for ulids (#2049)
Browse files Browse the repository at this point in the history
* feat: add support for ulids

* chore: update docs

* chore: cleanup
  • Loading branch information
chaunceyau committed Mar 6, 2023
1 parent 728e56a commit 64883e4
Show file tree
Hide file tree
Showing 7 changed files with 61 additions and 1 deletion.
1 change: 1 addition & 0 deletions README.md
Expand Up @@ -650,6 +650,7 @@ z.string().emoji();
z.string().uuid();
z.string().cuid();
z.string().cuid2();
z.string().ulid();
z.string().regex(regex);
z.string().includes(string);
z.string().startsWith(string);
Expand Down
1 change: 1 addition & 0 deletions deno/lib/ZodError.ts
Expand Up @@ -96,6 +96,7 @@ export type StringValidation =
| "regex"
| "cuid"
| "cuid2"
| "ulid"
| "datetime"
| "ip"
| { includes: string; position?: number }
Expand Down
10 changes: 10 additions & 0 deletions deno/lib/__tests__/string.test.ts
Expand Up @@ -223,6 +223,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+$/)
Expand Down
21 changes: 20 additions & 1 deletion deno/lib/types.ts
Expand Up @@ -515,6 +515,7 @@ export type ZodStringCheck =
| { kind: "cuid"; message?: string }
| { kind: "includes"; value: string; position?: number; 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 }
Expand All @@ -537,6 +538,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
Expand Down Expand Up @@ -728,6 +730,16 @@ export class ZodString extends ZodType<string, ZodStringDef> {
});
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);
Expand Down Expand Up @@ -855,10 +867,14 @@ export class ZodString extends ZodType<string, ZodStringDef> {
return this._addCheck({ kind: "cuid2", ...errorUtil.errToObj(message) });
}

ulid(message?: errorUtil.ErrMessage) {
return this._addCheck({ kind: "ulid", ...errorUtil.errToObj(message) });
}

ip(options?: string | { version?: "v4" | "v6"; message?: string }) {
return this._addCheck({ kind: "ip", ...errorUtil.errToObj(options) });
}

datetime(
options?:
| string
Expand Down Expand Up @@ -989,6 +1005,9 @@ export class ZodString extends ZodType<string, ZodStringDef> {
get isCUID2() {
return !!this._def.checks.find((ch) => ch.kind === "cuid2");
}
get isULID() {
return !!this._def.checks.find((ch) => ch.kind === "ulid");
}
get isIP() {
return !!this._def.checks.find((ch) => ch.kind === "ip");
}
Expand Down
1 change: 1 addition & 0 deletions src/ZodError.ts
Expand Up @@ -96,6 +96,7 @@ export type StringValidation =
| "regex"
| "cuid"
| "cuid2"
| "ulid"
| "datetime"
| "ip"
| { includes: string; position?: number }
Expand Down
10 changes: 10 additions & 0 deletions src/__tests__/string.test.ts
Expand Up @@ -222,6 +222,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+$/)
Expand Down
18 changes: 18 additions & 0 deletions src/types.ts
Expand Up @@ -515,6 +515,7 @@ export type ZodStringCheck =
| { kind: "cuid"; message?: string }
| { kind: "includes"; value: string; position?: number; 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 }
Expand All @@ -537,6 +538,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
Expand Down Expand Up @@ -728,6 +730,16 @@ export class ZodString extends ZodType<string, ZodStringDef> {
});
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);
Expand Down Expand Up @@ -854,6 +866,9 @@ export class ZodString extends ZodType<string, ZodStringDef> {
cuid2(message?: errorUtil.ErrMessage) {
return this._addCheck({ kind: "cuid2", ...errorUtil.errToObj(message) });
}
ulid(message?: errorUtil.ErrMessage) {
return this._addCheck({ kind: "ulid", ...errorUtil.errToObj(message) });
}

ip(options?: string | { version?: "v4" | "v6"; message?: string }) {
return this._addCheck({ kind: "ip", ...errorUtil.errToObj(options) });
Expand Down Expand Up @@ -989,6 +1004,9 @@ export class ZodString extends ZodType<string, ZodStringDef> {
get isCUID2() {
return !!this._def.checks.find((ch) => ch.kind === "cuid2");
}
get isULID() {
return !!this._def.checks.find((ch) => ch.kind === "ulid");
}
get isIP() {
return !!this._def.checks.find((ch) => ch.kind === "ip");
}
Expand Down

0 comments on commit 64883e4

Please sign in to comment.