Skip to content

Commit

Permalink
feat: added support multiple parameters in .startsWith() and .endsWit…
Browse files Browse the repository at this point in the history
…h() in string for ticket #3683
  • Loading branch information
Kumar06Lav committed Aug 8, 2024
1 parent 821d45b commit c7ba592
Show file tree
Hide file tree
Showing 7 changed files with 83 additions and 20 deletions.
10 changes: 8 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -735,8 +735,8 @@ z.string().cuid2();
z.string().ulid();
z.string().regex(regex);
z.string().includes(string);
z.string().startsWith(string);
z.string().endsWith(string);
z.string().startsWith(string | string[]);
z.string().endsWith(string | string[]);
z.string().datetime(); // ISO 8601; by default only `Z` timezone allowed
z.string().ip(); // defaults to allow both IPv4 and IPv6

Expand Down Expand Up @@ -775,7 +775,13 @@ z.string().emoji({ message: "Contains non-emoji characters" });
z.string().uuid({ message: "Invalid UUID" });
z.string().includes("tuna", { message: "Must include tuna" });
z.string().startsWith("https://", { message: "Must provide secure URL" });
z.string().startsWith(["https://", "http://"], {
message: "Must provide a valid URL starting with http:// or https://",
});
z.string().endsWith(".com", { message: "Only .com domains allowed" });
z.string().endsWith([".com", ".org"], {
message: "Only .com or .org domains allowed",
});
z.string().datetime({ message: "Invalid datetime string! Must be UTC." });
z.string().date({ message: "Invalid date string!" });
z.string().time({ message: "Invalid time string!" });
Expand Down
10 changes: 8 additions & 2 deletions README_ZH.md
Original file line number Diff line number Diff line change
Expand Up @@ -483,8 +483,8 @@ z.string().ulid();
z.string().duration();
z.string().regex(regex);
z.string().includes(string);
z.string().startsWith(string);
z.string().endsWith(string);
z.string().startsWith(string | string[]);
z.string().endsWith(string | string[]);
z.string().datetime(); // ISO 8601;默认值为无 UTC 偏移,选项见下文
z.string().ip(); // 默认为 IPv4 和 IPv6,选项见下文

Expand Down Expand Up @@ -517,7 +517,13 @@ z.string().emoji({ message: "Contains non-emoji characters" });
z.string().uuid({ message: "Invalid UUID" });
z.string().includes("tuna", { message: "Must include tuna" });
z.string().startsWith("https://", { message: "Must provide secure URL" });
z.string().startsWith(["https://", "http://"], {
message: "Must provide a valid URL starting with http:// or https://",
});
z.string().endsWith(".com", { message: "Only .com domains allowed" });
z.string().endsWith([".com", ".org"], {
message: "Only .com or .org domains allowed",
});
z.string().datetime({ message: "Invalid datetime string! Must be UTC." });
z.string().ip({ message: "Invalid IP address" });
```
Expand Down
15 changes: 11 additions & 4 deletions deno/lib/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,7 @@ Sponsorship at any level is appreciated and encouraged. If you built a paid prod
<td align="center">
<p></p>
<p>
<a href="https://speakeasyapi.dev/?utm_source=zod+docs">
<a href="https://speakeasy.com/?utm_source=zod+docs">
<picture height="40px">
<source media="(prefers-color-scheme: dark)" srcset="https://github.com/colinhacks/zod/assets/3084745/b1d86601-c7fb-483c-9927-5dc24ce8b737">
<img alt="speakeasy" height="40px" src="https://github.com/colinhacks/zod/assets/3084745/647524a4-22bb-4199-be70-404207a5a2b5">
Expand All @@ -261,7 +261,7 @@ Sponsorship at any level is appreciated and encouraged. If you built a paid prod
<br />
SDKs & Terraform providers for your API
<br/>
<a href="https://speakeasyapi.dev/?utm_source=zod+docs" style="text-decoration:none;">speakeasyapi.dev</a>
<a href="https://speakeasy.com/?utm_source=zod+docs" style="text-decoration:none;">speakeasy.com</a>
</p>
<p></p>
</td>
Expand Down Expand Up @@ -490,6 +490,7 @@ There are a growing number of tools that are built atop or support Zod natively!
- [`zod-prisma`](https://github.com/CarterGrimmeisen/zod-prisma): Generate Zod schemas from your Prisma schema.
- [`Supervillain`](https://github.com/Southclaws/supervillain): Generate Zod schemas from your Go structs.
- [`prisma-zod-generator`](https://github.com/omar-dulaimi/prisma-zod-generator): Emit Zod schemas from your Prisma schema.
- [`drizzle-zod`](https://orm.drizzle.team/docs/zod): Emit Zod schemas from your Drizzle schema.
- [`prisma-trpc-generator`](https://github.com/omar-dulaimi/prisma-trpc-generator): Emit fully implemented tRPC routers and their validation schemas using Zod.
- [`zod-prisma-types`](https://github.com/chrishoermann/zod-prisma-types) Create Zod types from your Prisma models.
- [`quicktype`](https://app.quicktype.io/): Convert JSON objects and JSON schemas into Zod schemas.
Expand Down Expand Up @@ -734,8 +735,8 @@ z.string().cuid2();
z.string().ulid();
z.string().regex(regex);
z.string().includes(string);
z.string().startsWith(string);
z.string().endsWith(string);
z.string().startsWith(string | string[]);
z.string().endsWith(string | string[]);
z.string().datetime(); // ISO 8601; by default only `Z` timezone allowed
z.string().ip(); // defaults to allow both IPv4 and IPv6

Expand Down Expand Up @@ -774,7 +775,13 @@ z.string().emoji({ message: "Contains non-emoji characters" });
z.string().uuid({ message: "Invalid UUID" });
z.string().includes("tuna", { message: "Must include tuna" });
z.string().startsWith("https://", { message: "Must provide secure URL" });
z.string().startsWith(["https://", "http://"], {
message: "Must provide a valid URL starting with http:// or https://",
});
z.string().endsWith(".com", { message: "Only .com domains allowed" });
z.string().endsWith([".com", ".org"], {
message: "Only .com or .org domains allowed",
});
z.string().datetime({ message: "Invalid datetime string! Must be UTC." });
z.string().date({ message: "Invalid date string!" });
z.string().time({ message: "Invalid time string!" });
Expand Down
6 changes: 6 additions & 0 deletions deno/lib/__tests__/string.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ const nonempty = z.string().min(1, "nonempty");
const includes = z.string().includes("includes");
const includesFromIndex2 = z.string().includes("includes", { position: 2 });
const startsWith = z.string().startsWith("startsWith");
const startsWithMultiple = z.string().startsWith(["foo_", "bar_", "baz_"]);
const endsWith = z.string().endsWith("endsWith");
const endsWithMultiple = z.string().endsWith(["foo_", "bar_", "baz_"]);

test("passing validations", () => {
minFive.parse("12345");
Expand All @@ -23,7 +25,9 @@ test("passing validations", () => {
includes.parse("XincludesXX");
includesFromIndex2.parse("XXXincludesXX");
startsWith.parse("startsWithX");
startsWithMultiple.parse("baz_12345");
endsWith.parse("XendsWith");
endsWithMultiple.parse("12345baz_");
});

test("failing validations", () => {
Expand All @@ -35,7 +39,9 @@ test("failing validations", () => {
expect(() => includes.parse("XincludeXX")).toThrow();
expect(() => includesFromIndex2.parse("XincludesXX")).toThrow();
expect(() => startsWith.parse("x")).toThrow();
expect(() => startsWithMultiple.parse("x")).toThrow();
expect(() => endsWith.parse("x")).toThrow();
expect(() => endsWithMultiple.parse("x")).toThrow();
});

test("email validations", () => {
Expand Down
28 changes: 22 additions & 6 deletions deno/lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -542,6 +542,8 @@ export type ZodStringCheck =
| { kind: "ulid"; message?: string }
| { kind: "startsWith"; value: string; message?: string }
| { kind: "endsWith"; value: string; message?: string }
| { kind: "startsWith"; value: string | string[]; message?: string }
| { kind: "endsWith"; value: string | string[]; message?: string }
| { kind: "regex"; regex: RegExp; message?: string }
| { kind: "trim"; message?: string }
| { kind: "toLowerCase"; message?: string }
Expand Down Expand Up @@ -856,21 +858,35 @@ export class ZodString extends ZodType<string, ZodStringDef, string> {
} else if (check.kind === "toUpperCase") {
input.data = input.data.toUpperCase();
} else if (check.kind === "startsWith") {
if (!(input.data as string).startsWith(check.value)) {
if (
(typeof check.value === "string" &&
!(input.data as string).startsWith(check.value)) ||
(Array.isArray(check.value) &&
!check.value.some((e: string) =>
(input.data as string)?.startsWith(e)
))
) {
ctx = this._getOrReturnCtx(input, ctx);
addIssueToContext(ctx, {
code: ZodIssueCode.invalid_string,
validation: { startsWith: check.value },
validation: { startsWith: check.value.toString() },
message: check.message,
});
status.dirty();
}
} else if (check.kind === "endsWith") {
if (!(input.data as string).endsWith(check.value)) {
if (
(typeof check.value === "string" &&
!(input.data as string).endsWith(check.value)) ||
(Array.isArray(check.value) &&
!check.value.some((e: string) =>
(input.data as string)?.endsWith(e)
))
) {
ctx = this._getOrReturnCtx(input, ctx);
addIssueToContext(ctx, {
code: ZodIssueCode.invalid_string,
validation: { endsWith: check.value },
validation: { endsWith: check.value.toString() },
message: check.message,
});
status.dirty();
Expand Down Expand Up @@ -1082,15 +1098,15 @@ export class ZodString extends ZodType<string, ZodStringDef, string> {
});
}

startsWith(value: string, message?: errorUtil.ErrMessage) {
startsWith(value: string | string[], message?: errorUtil.ErrMessage) {
return this._addCheck({
kind: "startsWith",
value: value,
...errorUtil.errToObj(message),
});
}

endsWith(value: string, message?: errorUtil.ErrMessage) {
endsWith(value: string | string[], message?: errorUtil.ErrMessage) {
return this._addCheck({
kind: "endsWith",
value: value,
Expand Down
6 changes: 6 additions & 0 deletions src/__tests__/string.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ const nonempty = z.string().min(1, "nonempty");
const includes = z.string().includes("includes");
const includesFromIndex2 = z.string().includes("includes", { position: 2 });
const startsWith = z.string().startsWith("startsWith");
const startsWithMultiple = z.string().startsWith(["foo_", "bar_", "baz_"]);
const endsWith = z.string().endsWith("endsWith");
const endsWithMultiple = z.string().endsWith(["foo_", "bar_", "baz_"]);

test("passing validations", () => {
minFive.parse("12345");
Expand All @@ -22,7 +24,9 @@ test("passing validations", () => {
includes.parse("XincludesXX");
includesFromIndex2.parse("XXXincludesXX");
startsWith.parse("startsWithX");
startsWithMultiple.parse("baz_12345");
endsWith.parse("XendsWith");
endsWithMultiple.parse("12345baz_");
});

test("failing validations", () => {
Expand All @@ -34,7 +38,9 @@ test("failing validations", () => {
expect(() => includes.parse("XincludeXX")).toThrow();
expect(() => includesFromIndex2.parse("XincludesXX")).toThrow();
expect(() => startsWith.parse("x")).toThrow();
expect(() => startsWithMultiple.parse("x")).toThrow();
expect(() => endsWith.parse("x")).toThrow();
expect(() => endsWithMultiple.parse("x")).toThrow();
});

test("email validations", () => {
Expand Down
28 changes: 22 additions & 6 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -542,6 +542,8 @@ export type ZodStringCheck =
| { kind: "ulid"; message?: string }
| { kind: "startsWith"; value: string; message?: string }
| { kind: "endsWith"; value: string; message?: string }
| { kind: "startsWith"; value: string | string[]; message?: string }
| { kind: "endsWith"; value: string | string[]; message?: string }
| { kind: "regex"; regex: RegExp; message?: string }
| { kind: "trim"; message?: string }
| { kind: "toLowerCase"; message?: string }
Expand Down Expand Up @@ -856,21 +858,35 @@ export class ZodString extends ZodType<string, ZodStringDef, string> {
} else if (check.kind === "toUpperCase") {
input.data = input.data.toUpperCase();
} else if (check.kind === "startsWith") {
if (!(input.data as string).startsWith(check.value)) {
if (
(typeof check.value === "string" &&
!(input.data as string).startsWith(check.value)) ||
(Array.isArray(check.value) &&
!check.value.some((e: string) =>
(input.data as string)?.startsWith(e)
))
) {
ctx = this._getOrReturnCtx(input, ctx);
addIssueToContext(ctx, {
code: ZodIssueCode.invalid_string,
validation: { startsWith: check.value },
validation: { startsWith: check.value.toString() },
message: check.message,
});
status.dirty();
}
} else if (check.kind === "endsWith") {
if (!(input.data as string).endsWith(check.value)) {
if (
(typeof check.value === "string" &&
!(input.data as string).endsWith(check.value)) ||
(Array.isArray(check.value) &&
!check.value.some((e: string) =>
(input.data as string)?.endsWith(e)
))
) {
ctx = this._getOrReturnCtx(input, ctx);
addIssueToContext(ctx, {
code: ZodIssueCode.invalid_string,
validation: { endsWith: check.value },
validation: { endsWith: check.value.toString() },
message: check.message,
});
status.dirty();
Expand Down Expand Up @@ -1082,15 +1098,15 @@ export class ZodString extends ZodType<string, ZodStringDef, string> {
});
}

startsWith(value: string, message?: errorUtil.ErrMessage) {
startsWith(value: string | string[], message?: errorUtil.ErrMessage) {
return this._addCheck({
kind: "startsWith",
value: value,
...errorUtil.errToObj(message),
});
}

endsWith(value: string, message?: errorUtil.ErrMessage) {
endsWith(value: string | string[], message?: errorUtil.ErrMessage) {
return this._addCheck({
kind: "endsWith",
value: value,
Expand Down

0 comments on commit c7ba592

Please sign in to comment.