From 1066a646b8878121c4f6351f7ad2b38e66c29274 Mon Sep 17 00:00:00 2001 From: Joseph Lozano Date: Thu, 16 Feb 2023 00:54:08 -0500 Subject: [PATCH 1/2] z.string().emoji() --- deno/lib/ZodError.ts | 1 + deno/lib/__tests__/string.test.ts | 9 +++++++++ deno/lib/types.ts | 22 ++++++++++++++++++++++ src/ZodError.ts | 1 + src/__tests__/string.test.ts | 9 +++++++++ src/types.ts | 22 ++++++++++++++++++++++ 6 files changed, 64 insertions(+) diff --git a/deno/lib/ZodError.ts b/deno/lib/ZodError.ts index b6834a777..365cb3cae 100644 --- a/deno/lib/ZodError.ts +++ b/deno/lib/ZodError.ts @@ -91,6 +91,7 @@ export interface ZodInvalidDateIssue extends ZodIssueBase { export type StringValidation = | "email" | "url" + | "emoji" | "uuid" | "regex" | "cuid" diff --git a/deno/lib/__tests__/string.test.ts b/deno/lib/__tests__/string.test.ts index 8cac57112..7908f855e 100644 --- a/deno/lib/__tests__/string.test.ts +++ b/deno/lib/__tests__/string.test.ts @@ -129,6 +129,15 @@ test("url error overrides", () => { } }); +test("emoji validations", () => { + const emoji = z.string().emoji(); + try { + emoji.parse("πŸΊπŸ‘©β€πŸš€πŸ«‘"); + emoji.parse("πŸ’š πŸ’™ πŸ’œ πŸ’› ❀️"); + expect(() => emoji.parse(":-)")).toThrow(); + } catch (err) {} +}); + test("uuid", () => { const uuid = z.string().uuid("custom error"); uuid.parse("9491d710-3185-4e06-bea0-6a2f275345e0"); diff --git a/deno/lib/types.ts b/deno/lib/types.ts index 420238114..d56076fe7 100644 --- a/deno/lib/types.ts +++ b/deno/lib/types.ts @@ -492,6 +492,7 @@ export type ZodStringCheck = | { kind: "length"; value: number; message?: string } | { kind: "email"; message?: string } | { kind: "url"; message?: string } + | { kind: "emoji"; message?: string } | { kind: "uuid"; message?: string } | { kind: "cuid"; message?: string } | { kind: "cuid2"; message?: string } @@ -526,6 +527,11 @@ const uuidRegex = const emailRegex = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|([^-]([a-zA-Z0-9-]*\.)+[a-zA-Z]{2,}))$/; +// from https://thekevinscott.com/emojis-in-javascript/#writing-a-regular-expression + +const emojiRegex = + /(?:[\u2700-\u27bf]|(?:\ud83c[\udde6-\uddff]){2}|[\ud800-\udbff][\udc00-\udfff])[\ufe0e\ufe0f]?(?:[\u0300-\u036f\ufe20-\ufe23\u20d0-\u20f0]|\ud83c[\udffb-\udfff])?(?:\u200d(?:[^\ud800-\udfff]|(?:\ud83c[\udde6-\uddff]){2}|[\ud800-\udbff][\udc00-\udfff])[\ufe0e\ufe0f]?(?:[\u0300-\u036f\ufe20-\ufe23\u20d0-\u20f0]|\ud83c[\udffb-\udfff])?)*/; + // interface IsDateStringOptions extends StringDateOptions { /** * Match any configuration @@ -653,6 +659,16 @@ export class ZodString extends ZodType { }); status.dirty(); } + } else if (check.kind === "emoji") { + if (!emojiRegex.test(input.data)) { + ctx = this._getOrReturnCtx(input, ctx); + addIssueToContext(ctx, { + validation: "emoji", + code: ZodIssueCode.invalid_string, + message: check.message, + }); + status.dirty(); + } } else if (check.kind === "uuid") { if (!uuidRegex.test(input.data)) { ctx = this._getOrReturnCtx(input, ctx); @@ -773,6 +789,9 @@ export class ZodString extends ZodType { url(message?: errorUtil.ErrMessage) { return this._addCheck({ kind: "url", ...errorUtil.errToObj(message) }); } + emoji(message?: errorUtil.ErrMessage) { + return this._addCheck({ kind: "emoji", ...errorUtil.errToObj(message) }); + } uuid(message?: errorUtil.ErrMessage) { return this._addCheck({ kind: "uuid", ...errorUtil.errToObj(message) }); } @@ -879,6 +898,9 @@ export class ZodString extends ZodType { get isURL() { return !!this._def.checks.find((ch) => ch.kind === "url"); } + get isEmoji() { + return !!this._def.checks.find((ch) => ch.kind === "emoji"); + } get isUUID() { return !!this._def.checks.find((ch) => ch.kind === "uuid"); } diff --git a/src/ZodError.ts b/src/ZodError.ts index e8128c292..03a34432a 100644 --- a/src/ZodError.ts +++ b/src/ZodError.ts @@ -91,6 +91,7 @@ export interface ZodInvalidDateIssue extends ZodIssueBase { export type StringValidation = | "email" | "url" + | "emoji" | "uuid" | "regex" | "cuid" diff --git a/src/__tests__/string.test.ts b/src/__tests__/string.test.ts index cb075cf75..89e40feab 100644 --- a/src/__tests__/string.test.ts +++ b/src/__tests__/string.test.ts @@ -128,6 +128,15 @@ test("url error overrides", () => { } }); +test("emoji validations", () => { + const emoji = z.string().emoji(); + try { + emoji.parse("πŸΊπŸ‘©β€πŸš€πŸ«‘"); + emoji.parse("πŸ’š πŸ’™ πŸ’œ πŸ’› ❀️"); + expect(() => emoji.parse(":-)")).toThrow(); + } catch (err) {} +}); + test("uuid", () => { const uuid = z.string().uuid("custom error"); uuid.parse("9491d710-3185-4e06-bea0-6a2f275345e0"); diff --git a/src/types.ts b/src/types.ts index c0c057f05..7e2099fbd 100644 --- a/src/types.ts +++ b/src/types.ts @@ -492,6 +492,7 @@ export type ZodStringCheck = | { kind: "length"; value: number; message?: string } | { kind: "email"; message?: string } | { kind: "url"; message?: string } + | { kind: "emoji"; message?: string } | { kind: "uuid"; message?: string } | { kind: "cuid"; message?: string } | { kind: "cuid2"; message?: string } @@ -526,6 +527,11 @@ const uuidRegex = const emailRegex = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|([^-]([a-zA-Z0-9-]*\.)+[a-zA-Z]{2,}))$/; +// from https://thekevinscott.com/emojis-in-javascript/#writing-a-regular-expression + +const emojiRegex = + /(?:[\u2700-\u27bf]|(?:\ud83c[\udde6-\uddff]){2}|[\ud800-\udbff][\udc00-\udfff])[\ufe0e\ufe0f]?(?:[\u0300-\u036f\ufe20-\ufe23\u20d0-\u20f0]|\ud83c[\udffb-\udfff])?(?:\u200d(?:[^\ud800-\udfff]|(?:\ud83c[\udde6-\uddff]){2}|[\ud800-\udbff][\udc00-\udfff])[\ufe0e\ufe0f]?(?:[\u0300-\u036f\ufe20-\ufe23\u20d0-\u20f0]|\ud83c[\udffb-\udfff])?)*/; + // interface IsDateStringOptions extends StringDateOptions { /** * Match any configuration @@ -653,6 +659,16 @@ export class ZodString extends ZodType { }); status.dirty(); } + } else if (check.kind === "emoji") { + if (!emojiRegex.test(input.data)) { + ctx = this._getOrReturnCtx(input, ctx); + addIssueToContext(ctx, { + validation: "emoji", + code: ZodIssueCode.invalid_string, + message: check.message, + }); + status.dirty(); + } } else if (check.kind === "uuid") { if (!uuidRegex.test(input.data)) { ctx = this._getOrReturnCtx(input, ctx); @@ -773,6 +789,9 @@ export class ZodString extends ZodType { url(message?: errorUtil.ErrMessage) { return this._addCheck({ kind: "url", ...errorUtil.errToObj(message) }); } + emoji(message?: errorUtil.ErrMessage) { + return this._addCheck({ kind: "emoji", ...errorUtil.errToObj(message) }); + } uuid(message?: errorUtil.ErrMessage) { return this._addCheck({ kind: "uuid", ...errorUtil.errToObj(message) }); } @@ -879,6 +898,9 @@ export class ZodString extends ZodType { get isURL() { return !!this._def.checks.find((ch) => ch.kind === "url"); } + get isEmoji() { + return !!this._def.checks.find((ch) => ch.kind === "emoji"); + } get isUUID() { return !!this._def.checks.find((ch) => ch.kind === "uuid"); } From 02ff61d395cf232526e683bfc7621d0e1863d764 Mon Sep 17 00:00:00 2001 From: Joseph Lozano Date: Thu, 16 Feb 2023 00:56:37 -0500 Subject: [PATCH 2/2] test emojis mixed with other chars --- deno/lib/__tests__/string.test.ts | 1 + src/__tests__/string.test.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/deno/lib/__tests__/string.test.ts b/deno/lib/__tests__/string.test.ts index 7908f855e..307ba31e2 100644 --- a/deno/lib/__tests__/string.test.ts +++ b/deno/lib/__tests__/string.test.ts @@ -135,6 +135,7 @@ test("emoji validations", () => { emoji.parse("πŸΊπŸ‘©β€πŸš€πŸ«‘"); emoji.parse("πŸ’š πŸ’™ πŸ’œ πŸ’› ❀️"); expect(() => emoji.parse(":-)")).toThrow(); + expect(() => emoji.parse("πŸ˜€ is an emoji")).toThrow() } catch (err) {} }); diff --git a/src/__tests__/string.test.ts b/src/__tests__/string.test.ts index 89e40feab..c330fa697 100644 --- a/src/__tests__/string.test.ts +++ b/src/__tests__/string.test.ts @@ -134,6 +134,7 @@ test("emoji validations", () => { emoji.parse("πŸΊπŸ‘©β€πŸš€πŸ«‘"); emoji.parse("πŸ’š πŸ’™ πŸ’œ πŸ’› ❀️"); expect(() => emoji.parse(":-)")).toThrow(); + expect(() => emoji.parse("πŸ˜€ is an emoji")).toThrow() } catch (err) {} });