Skip to content

Commit c244fb6

Browse files
feat: z.string().emoji() (#2045)
* z.string().emoji() * test emojis mixed with other chars
1 parent e693919 commit c244fb6

File tree

6 files changed

+66
-0
lines changed

6 files changed

+66
-0
lines changed

Diff for: deno/lib/ZodError.ts

+1
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ export interface ZodInvalidDateIssue extends ZodIssueBase {
9191
export type StringValidation =
9292
| "email"
9393
| "url"
94+
| "emoji"
9495
| "uuid"
9596
| "regex"
9697
| "cuid"

Diff for: deno/lib/__tests__/string.test.ts

+10
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,16 @@ test("url error overrides", () => {
129129
}
130130
});
131131

132+
test("emoji validations", () => {
133+
const emoji = z.string().emoji();
134+
try {
135+
emoji.parse("🍺👩‍🚀🫡");
136+
emoji.parse("💚 💙 💜 💛 ❤️");
137+
expect(() => emoji.parse(":-)")).toThrow();
138+
expect(() => emoji.parse("😀 is an emoji")).toThrow()
139+
} catch (err) {}
140+
});
141+
132142
test("uuid", () => {
133143
const uuid = z.string().uuid("custom error");
134144
uuid.parse("9491d710-3185-4e06-bea0-6a2f275345e0");

Diff for: deno/lib/types.ts

+22
Original file line numberDiff line numberDiff line change
@@ -492,6 +492,7 @@ export type ZodStringCheck =
492492
| { kind: "length"; value: number; message?: string }
493493
| { kind: "email"; message?: string }
494494
| { kind: "url"; message?: string }
495+
| { kind: "emoji"; message?: string }
495496
| { kind: "uuid"; message?: string }
496497
| { kind: "cuid"; message?: string }
497498
| { kind: "cuid2"; message?: string }
@@ -526,6 +527,11 @@ const uuidRegex =
526527
const emailRegex =
527528
/^(([^<>()[\]\\.,;:\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,}))$/;
528529

530+
// from https://thekevinscott.com/emojis-in-javascript/#writing-a-regular-expression
531+
532+
const emojiRegex =
533+
/(?:[\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])?)*/;
534+
529535
// interface IsDateStringOptions extends StringDateOptions {
530536
/**
531537
* Match any configuration
@@ -653,6 +659,16 @@ export class ZodString extends ZodType<string, ZodStringDef> {
653659
});
654660
status.dirty();
655661
}
662+
} else if (check.kind === "emoji") {
663+
if (!emojiRegex.test(input.data)) {
664+
ctx = this._getOrReturnCtx(input, ctx);
665+
addIssueToContext(ctx, {
666+
validation: "emoji",
667+
code: ZodIssueCode.invalid_string,
668+
message: check.message,
669+
});
670+
status.dirty();
671+
}
656672
} else if (check.kind === "uuid") {
657673
if (!uuidRegex.test(input.data)) {
658674
ctx = this._getOrReturnCtx(input, ctx);
@@ -773,6 +789,9 @@ export class ZodString extends ZodType<string, ZodStringDef> {
773789
url(message?: errorUtil.ErrMessage) {
774790
return this._addCheck({ kind: "url", ...errorUtil.errToObj(message) });
775791
}
792+
emoji(message?: errorUtil.ErrMessage) {
793+
return this._addCheck({ kind: "emoji", ...errorUtil.errToObj(message) });
794+
}
776795
uuid(message?: errorUtil.ErrMessage) {
777796
return this._addCheck({ kind: "uuid", ...errorUtil.errToObj(message) });
778797
}
@@ -879,6 +898,9 @@ export class ZodString extends ZodType<string, ZodStringDef> {
879898
get isURL() {
880899
return !!this._def.checks.find((ch) => ch.kind === "url");
881900
}
901+
get isEmoji() {
902+
return !!this._def.checks.find((ch) => ch.kind === "emoji");
903+
}
882904
get isUUID() {
883905
return !!this._def.checks.find((ch) => ch.kind === "uuid");
884906
}

Diff for: src/ZodError.ts

+1
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ export interface ZodInvalidDateIssue extends ZodIssueBase {
9191
export type StringValidation =
9292
| "email"
9393
| "url"
94+
| "emoji"
9495
| "uuid"
9596
| "regex"
9697
| "cuid"

Diff for: src/__tests__/string.test.ts

+10
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,16 @@ test("url error overrides", () => {
128128
}
129129
});
130130

131+
test("emoji validations", () => {
132+
const emoji = z.string().emoji();
133+
try {
134+
emoji.parse("🍺👩‍🚀🫡");
135+
emoji.parse("💚 💙 💜 💛 ❤️");
136+
expect(() => emoji.parse(":-)")).toThrow();
137+
expect(() => emoji.parse("😀 is an emoji")).toThrow()
138+
} catch (err) {}
139+
});
140+
131141
test("uuid", () => {
132142
const uuid = z.string().uuid("custom error");
133143
uuid.parse("9491d710-3185-4e06-bea0-6a2f275345e0");

Diff for: src/types.ts

+22
Original file line numberDiff line numberDiff line change
@@ -492,6 +492,7 @@ export type ZodStringCheck =
492492
| { kind: "length"; value: number; message?: string }
493493
| { kind: "email"; message?: string }
494494
| { kind: "url"; message?: string }
495+
| { kind: "emoji"; message?: string }
495496
| { kind: "uuid"; message?: string }
496497
| { kind: "cuid"; message?: string }
497498
| { kind: "cuid2"; message?: string }
@@ -526,6 +527,11 @@ const uuidRegex =
526527
const emailRegex =
527528
/^(([^<>()[\]\\.,;:\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,}))$/;
528529

530+
// from https://thekevinscott.com/emojis-in-javascript/#writing-a-regular-expression
531+
532+
const emojiRegex =
533+
/(?:[\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])?)*/;
534+
529535
// interface IsDateStringOptions extends StringDateOptions {
530536
/**
531537
* Match any configuration
@@ -653,6 +659,16 @@ export class ZodString extends ZodType<string, ZodStringDef> {
653659
});
654660
status.dirty();
655661
}
662+
} else if (check.kind === "emoji") {
663+
if (!emojiRegex.test(input.data)) {
664+
ctx = this._getOrReturnCtx(input, ctx);
665+
addIssueToContext(ctx, {
666+
validation: "emoji",
667+
code: ZodIssueCode.invalid_string,
668+
message: check.message,
669+
});
670+
status.dirty();
671+
}
656672
} else if (check.kind === "uuid") {
657673
if (!uuidRegex.test(input.data)) {
658674
ctx = this._getOrReturnCtx(input, ctx);
@@ -773,6 +789,9 @@ export class ZodString extends ZodType<string, ZodStringDef> {
773789
url(message?: errorUtil.ErrMessage) {
774790
return this._addCheck({ kind: "url", ...errorUtil.errToObj(message) });
775791
}
792+
emoji(message?: errorUtil.ErrMessage) {
793+
return this._addCheck({ kind: "emoji", ...errorUtil.errToObj(message) });
794+
}
776795
uuid(message?: errorUtil.ErrMessage) {
777796
return this._addCheck({ kind: "uuid", ...errorUtil.errToObj(message) });
778797
}
@@ -879,6 +898,9 @@ export class ZodString extends ZodType<string, ZodStringDef> {
879898
get isURL() {
880899
return !!this._def.checks.find((ch) => ch.kind === "url");
881900
}
901+
get isEmoji() {
902+
return !!this._def.checks.find((ch) => ch.kind === "emoji");
903+
}
882904
get isUUID() {
883905
return !!this._def.checks.find((ch) => ch.kind === "uuid");
884906
}

0 commit comments

Comments
 (0)