Skip to content

Commit

Permalink
lowercase method for ZodString (#2038)
Browse files Browse the repository at this point in the history
* lowercase method for ZodString

* Rename to toLowerCase. Add toUpperCase.:

* Update readme

---------

Co-authored-by: Colin McDonnell <colinmcd94@gmail.com>
  • Loading branch information
levinuncu and colinhacks committed Feb 26, 2023
1 parent 8de36eb commit 16beeb5
Show file tree
Hide file tree
Showing 6 changed files with 53 additions and 3 deletions.
2 changes: 2 additions & 0 deletions README.md
Expand Up @@ -596,6 +596,8 @@ z.string().regex(regex);
z.string().startsWith(string);
z.string().endsWith(string);
z.string().trim(); // trim whitespace
z.string().toLowerCase(); // toLowerCase
z.string().toUpperCase(); // toLowerCase
z.string().datetime(); // defaults to UTC, see below for options
z.string().ip(); // defaults to IPv4 and IPv6, see below for options
```
Expand Down
2 changes: 2 additions & 0 deletions deno/lib/README.md
Expand Up @@ -596,6 +596,8 @@ z.string().regex(regex);
z.string().startsWith(string);
z.string().endsWith(string);
z.string().trim(); // trim whitespace
z.string().toLowerCase(); // toLowerCase
z.string().toUpperCase(); // toLowerCase
z.string().datetime(); // defaults to UTC, see below for options
z.string().ip(); // defaults to IPv4 and IPv6, see below for options
```
Expand Down
5 changes: 5 additions & 0 deletions deno/lib/__tests__/string.test.ts
Expand Up @@ -309,6 +309,11 @@ test("trim", () => {
expect(() => z.string().trim().min(2).parse(" 1 ")).toThrow();
});

test("lowerCase", () => {
expect(z.string().toLowerCase().parse("ASDF")).toEqual("asdf");
expect(z.string().toUpperCase().parse("asdf")).toEqual("ASDF");
});

test("datetime", () => {
const a = z.string().datetime({});
expect(a.isDatetime).toEqual(true);
Expand Down
21 changes: 19 additions & 2 deletions deno/lib/types.ts
Expand Up @@ -501,6 +501,8 @@ export type ZodStringCheck =
| { kind: "endsWith"; value: string; message?: string }
| { kind: "regex"; regex: RegExp; message?: string }
| { kind: "trim"; message?: string }
| { kind: "toLowerCase"; message?: string }
| { kind: "toUpperCase"; message?: string }
| {
kind: "datetime";
offset: boolean;
Expand Down Expand Up @@ -529,15 +531,14 @@ const uuidRegex =
const emailRegex =
/^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[(((25[0-5])|(2[0-4][0-9])|(1[0-9]{2})|([0-9]{1,2}))\.){3}((25[0-5])|(2[0-4][0-9])|(1[0-9]{2})|([0-9]{1,2}))\])|(\[IPv6:(([a-f0-9]{1,4}:){7}|::([a-f0-9]{1,4}:){0,6}|([a-f0-9]{1,4}:){1}:([a-f0-9]{1,4}:){0,5}|([a-f0-9]{1,4}:){2}:([a-f0-9]{1,4}:){0,4}|([a-f0-9]{1,4}:){3}:([a-f0-9]{1,4}:){0,3}|([a-f0-9]{1,4}:){4}:([a-f0-9]{1,4}:){0,2}|([a-f0-9]{1,4}:){5}:([a-f0-9]{1,4}:){0,1})([a-f0-9]{1,4}|(((25[0-5])|(2[0-4][0-9])|(1[0-9]{2})|([0-9]{1,2}))\.){3}((25[0-5])|(2[0-4][0-9])|(1[0-9]{2})|([0-9]{1,2})))\])|([A-Za-z0-9]([A-Za-z0-9-]*[A-Za-z0-9])*(\.[A-Za-z]{2,})+))$/;
// from https://thekevinscott.com/emojis-in-javascript/#writing-a-regular-expression
const emojiRegex = /^(\p{Extended_Pictographic}|\p{Emoji_Component})+$/u;

const ipv4Regex =
/^(((25[0-5])|(2[0-4][0-9])|(1[0-9]{2})|([0-9]{1,2}))\.){3}((25[0-5])|(2[0-4][0-9])|(1[0-9]{2})|([0-9]{1,2}))$/;

const ipv6Regex =
/^(([a-f0-9]{1,4}:){7}|::([a-f0-9]{1,4}:){0,6}|([a-f0-9]{1,4}:){1}:([a-f0-9]{1,4}:){0,5}|([a-f0-9]{1,4}:){2}:([a-f0-9]{1,4}:){0,4}|([a-f0-9]{1,4}:){3}:([a-f0-9]{1,4}:){0,3}|([a-f0-9]{1,4}:){4}:([a-f0-9]{1,4}:){0,2}|([a-f0-9]{1,4}:){5}:([a-f0-9]{1,4}:){0,1})([a-f0-9]{1,4}|(((25[0-5])|(2[0-4][0-9])|(1[0-9]{2})|([0-9]{1,2}))\.){3}((25[0-5])|(2[0-4][0-9])|(1[0-9]{2})|([0-9]{1,2})))$/;

const emojiRegex = /^(\p{Extended_Pictographic}|\p{Emoji_Component})+$/u;

// Adapted from https://stackoverflow.com/a/3143231
const datetimeRegex = (args: { precision: number | null; offset: boolean }) => {
if (args.precision) {
Expand Down Expand Up @@ -735,6 +736,10 @@ export class ZodString extends ZodType<string, ZodStringDef> {
}
} else if (check.kind === "trim") {
input.data = input.data.trim();
} else if (check.kind === "toLowerCase") {
input.data = input.data.toLowerCase();
} else if (check.kind === "toUpperCase") {
input.data = input.data.toUpperCase();
} else if (check.kind === "startsWith") {
if (!(input.data as string).startsWith(check.value)) {
ctx = this._getOrReturnCtx(input, ctx);
Expand Down Expand Up @@ -913,6 +918,18 @@ export class ZodString extends ZodType<string, ZodStringDef> {
checks: [...this._def.checks, { kind: "trim" }],
});

toLowerCase = () =>
new ZodString({
...this._def,
checks: [...this._def.checks, { kind: "toLowerCase" }],
});

toUpperCase = () =>
new ZodString({
...this._def,
checks: [...this._def.checks, { kind: "toUpperCase" }],
});

get isDatetime() {
return !!this._def.checks.find((ch) => ch.kind === "datetime");
}
Expand Down
5 changes: 5 additions & 0 deletions src/__tests__/string.test.ts
Expand Up @@ -308,6 +308,11 @@ test("trim", () => {
expect(() => z.string().trim().min(2).parse(" 1 ")).toThrow();
});

test("lowerCase", () => {
expect(z.string().toLowerCase().parse("ASDF")).toEqual("asdf");
expect(z.string().toUpperCase().parse("asdf")).toEqual("ASDF");
});

test("datetime", () => {
const a = z.string().datetime({});
expect(a.isDatetime).toEqual(true);
Expand Down
21 changes: 20 additions & 1 deletion src/types.ts
Expand Up @@ -501,6 +501,8 @@ export type ZodStringCheck =
| { kind: "endsWith"; value: string; message?: string }
| { kind: "regex"; regex: RegExp; message?: string }
| { kind: "trim"; message?: string }
| { kind: "toLowerCase"; message?: string }
| { kind: "toUpperCase"; message?: string }
| {
kind: "datetime";
offset: boolean;
Expand All @@ -526,7 +528,8 @@ const uuidRegex =
// const emailRegex = /^(([^<>()[\].,;:\s@"]+(\.[^<>()[\].,;:\s@"]+)*)|(".+"))@((?!-)([^<>()[\].,;:\s@"]+\.)+[^<>()[\].,;:\s@"]{1,})[^-<>()[\].,;:\s@"]$/i;
// eslint-disable-next-line

const emailRegex = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[(((25[0-5])|(2[0-4][0-9])|(1[0-9]{2})|([0-9]{1,2}))\.){3}((25[0-5])|(2[0-4][0-9])|(1[0-9]{2})|([0-9]{1,2}))\])|(\[IPv6:(([a-f0-9]{1,4}:){7}|::([a-f0-9]{1,4}:){0,6}|([a-f0-9]{1,4}:){1}:([a-f0-9]{1,4}:){0,5}|([a-f0-9]{1,4}:){2}:([a-f0-9]{1,4}:){0,4}|([a-f0-9]{1,4}:){3}:([a-f0-9]{1,4}:){0,3}|([a-f0-9]{1,4}:){4}:([a-f0-9]{1,4}:){0,2}|([a-f0-9]{1,4}:){5}:([a-f0-9]{1,4}:){0,1})([a-f0-9]{1,4}|(((25[0-5])|(2[0-4][0-9])|(1[0-9]{2})|([0-9]{1,2}))\.){3}((25[0-5])|(2[0-4][0-9])|(1[0-9]{2})|([0-9]{1,2})))\])|([A-Za-z0-9]([A-Za-z0-9-]*[A-Za-z0-9])*(\.[A-Za-z]{2,})+))$/;
const emailRegex =
/^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[(((25[0-5])|(2[0-4][0-9])|(1[0-9]{2})|([0-9]{1,2}))\.){3}((25[0-5])|(2[0-4][0-9])|(1[0-9]{2})|([0-9]{1,2}))\])|(\[IPv6:(([a-f0-9]{1,4}:){7}|::([a-f0-9]{1,4}:){0,6}|([a-f0-9]{1,4}:){1}:([a-f0-9]{1,4}:){0,5}|([a-f0-9]{1,4}:){2}:([a-f0-9]{1,4}:){0,4}|([a-f0-9]{1,4}:){3}:([a-f0-9]{1,4}:){0,3}|([a-f0-9]{1,4}:){4}:([a-f0-9]{1,4}:){0,2}|([a-f0-9]{1,4}:){5}:([a-f0-9]{1,4}:){0,1})([a-f0-9]{1,4}|(((25[0-5])|(2[0-4][0-9])|(1[0-9]{2})|([0-9]{1,2}))\.){3}((25[0-5])|(2[0-4][0-9])|(1[0-9]{2})|([0-9]{1,2})))\])|([A-Za-z0-9]([A-Za-z0-9-]*[A-Za-z0-9])*(\.[A-Za-z]{2,})+))$/;
// from https://thekevinscott.com/emojis-in-javascript/#writing-a-regular-expression
const emojiRegex = /^(\p{Extended_Pictographic}|\p{Emoji_Component})+$/u;

Expand Down Expand Up @@ -733,6 +736,10 @@ export class ZodString extends ZodType<string, ZodStringDef> {
}
} else if (check.kind === "trim") {
input.data = input.data.trim();
} else if (check.kind === "toLowerCase") {
input.data = input.data.toLowerCase();
} else if (check.kind === "toUpperCase") {
input.data = input.data.toUpperCase();
} else if (check.kind === "startsWith") {
if (!(input.data as string).startsWith(check.value)) {
ctx = this._getOrReturnCtx(input, ctx);
Expand Down Expand Up @@ -911,6 +918,18 @@ export class ZodString extends ZodType<string, ZodStringDef> {
checks: [...this._def.checks, { kind: "trim" }],
});

toLowerCase = () =>
new ZodString({
...this._def,
checks: [...this._def.checks, { kind: "toLowerCase" }],
});

toUpperCase = () =>
new ZodString({
...this._def,
checks: [...this._def.checks, { kind: "toUpperCase" }],
});

get isDatetime() {
return !!this._def.checks.find((ch) => ch.kind === "datetime");
}
Expand Down

0 comments on commit 16beeb5

Please sign in to comment.