string.test.tsâ¢72.5 kB
import { randomBytes } from "node:crypto";
import { expect, test } from "vitest";
import * as z from "zod/v4";
const minFive = z.string().min(5, "min5");
const maxFive = z.string().max(5, "max5");
const justFive = z.string().length(5);
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 endsWith = z.string().endsWith("endsWith");
test("length checks", () => {
minFive.parse("12345");
minFive.parse("123456");
maxFive.parse("12345");
maxFive.parse("1234");
nonempty.parse("1");
justFive.parse("12345");
expect(() => minFive.parse("1234")).toThrow();
expect(() => maxFive.parse("123456")).toThrow();
expect(() => nonempty.parse("")).toThrow();
expect(() => justFive.parse("1234")).toThrow();
expect(() => justFive.parse("123456")).toThrow();
});
test("includes", () => {
includes.parse("XincludesXX");
includesFromIndex2.parse("XXXincludesXX");
expect(() => includes.parse("XincludeXX")).toThrow();
expect(() => includesFromIndex2.parse("XincludesXX")).toThrow();
});
test("startswith/endswith", () => {
startsWith.parse("startsWithX");
endsWith.parse("XendsWith");
expect(() => startsWith.parse("x")).toThrow();
expect(() => endsWith.parse("x")).toThrow();
});
test("email validations", () => {
const validEmails = [
`email@domain.com`,
`firstname.lastname@domain.com`,
`email@subdomain.domain.com`,
`firstname+lastname@domain.com`,
`1234567890@domain.com`,
`email@domain-one.com`,
`_______@domain.com`,
`email@domain.name`,
`email@domain.co.jp`,
`firstname-lastname@domain.com`,
`very.common@example.com`,
`disposable.style.email.with+symbol@example.com`,
`other.email-with-hyphen@example.com`,
`fully-qualified-domain@example.com`,
`user.name+tag+sorting@example.com`,
`x@example.com`,
`mojojojo@asdf.example.com`,
`example-indeed@strange-example.com`,
`example@s.example`,
`user-@example.org`,
`user@my-example.com`,
`a@b.cd`,
`work+user@mail.com`,
`tom@test.te-st.com`,
`something@subdomain.domain-with-hyphens.tld`,
`common'name@domain.com`,
`francois@etu.inp-n7.fr`,
];
const invalidEmails = [
// no "printable characters"
// `user%example.com@example.org`,
// `mailhost!username@example.org`,
// `test/test@test.com`,
// double @
`francois@@etu.inp-n7.fr`,
// do not support quotes
`"email"@domain.com`,
`"e asdf sadf ?<>ail"@domain.com`,
`" "@example.org`,
`"john..doe"@example.org`,
`"very.(),:;<>[]\".VERY.\"very@\\ \"very\".unusual"@strange.example.com`,
// do not support comma
`a,b@domain.com`,
// do not support IPv4
`email@123.123.123.123`,
`email@[123.123.123.123]`,
`postmaster@123.123.123.123`,
`user@[68.185.127.196]`,
`ipv4@[85.129.96.247]`,
`valid@[79.208.229.53]`,
`valid@[255.255.255.255]`,
`valid@[255.0.55.2]`,
`valid@[255.0.55.2]`,
// do not support ipv6
`hgrebert0@[IPv6:4dc8:ac7:ce79:8878:1290:6098:5c50:1f25]`,
`bshapiro4@[IPv6:3669:c709:e981:4884:59a3:75d1:166b:9ae]`,
`jsmith@[IPv6:2001:db8::1]`,
`postmaster@[IPv6:2001:0db8:85a3:0000:0000:8a2e:0370:7334]`,
`postmaster@[IPv6:2001:0db8:85a3:0000:0000:8a2e:0370:192.168.1.1]`,
// microsoft test cases
`plainaddress`,
`#@%^%#$@#$@#.com`,
`@domain.com`,
`Joe Smith <email@domain.com>`,
`email.domain.com`,
`email@domain@domain.com`,
`.email@domain.com`,
`email.@domain.com`,
`email..email@domain.com`,
`ããããã@domain.com`,
`email@domain.com (Joe Smith)`,
`email@domain`,
`email@-domain.com`,
`email@111.222.333.44444`,
`email@domain..com`,
`Abc.example.com`,
`A@b@c@example.com`,
`colin..hacks@domain.com`,
`a"b(c)d,e:f;g<h>i[j\k]l@example.com`,
`just"not"right@example.com`,
`this is"not\allowed@example.com`,
`this\ still\"not\\allowed@example.com`,
// random
`i_like_underscore@but_its_not_allowed_in_this_part.example.com`,
`QA[icon]CHOCOLATE[icon]@test.com`,
`invalid@-start.com`,
`invalid@end.com-`,
`a.b@c.d`,
`invalid@[1.1.1.-1]`,
`invalid@[68.185.127.196.55]`,
`temp@[192.168.1]`,
`temp@[9.18.122.]`,
`double..point@test.com`,
`asdad@test..com`,
`asdad@hghg...sd...au`,
`asdad@hghg........au`,
`invalid@[256.2.2.48]`,
`invalid@[256.2.2.48]`,
`invalid@[999.465.265.1]`,
`jkibbey4@[IPv6:82c4:19a8::70a9:2aac:557::ea69:d985:28d]`,
`mlivesay3@[9952:143f:b4df:2179:49a1:5e82:b92e:6b6]`,
`gbacher0@[IPv6:bc37:4d3f:5048:2e26:37cc:248e:df8e:2f7f:af]`,
`invalid@[IPv6:5348:4ed3:5d38:67fb:e9b:acd2:c13:192.168.256.1]`,
`test@.com`,
`aaaaaaaaaaaaaaalongemailthatcausesregexDoSvulnerability@test.c`,
];
const emailSchema = z.string().email();
expect(
validEmails.every((email) => {
return emailSchema.safeParse(email).success;
})
).toBe(true);
expect(
invalidEmails.every((email) => {
return emailSchema.safeParse(email).success === false;
})
).toBe(true);
});
test("base64 validations", () => {
const validBase64Strings = [
"SGVsbG8gV29ybGQ=", // "Hello World"
"VGhpcyBpcyBhbiBlbmNvZGVkIHN0cmluZw==", // "This is an encoded string"
"TWFueSBoYW5kcyBtYWtlIGxpZ2h0IHdvcms=", // "Many hands make light work"
"UGF0aWVuY2UgaXMgdGhlIGtleSB0byBzdWNjZXNz", // "Patience is the key to success"
"QmFzZTY0IGVuY29kaW5nIGlzIGZ1bg==", // "Base64 encoding is fun"
"MTIzNDU2Nzg5MA==", // "1234567890"
"YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXo=", // "abcdefghijklmnopqrstuvwxyz"
"QUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVo=", // "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"ISIkJSMmJyonKCk=", // "!\"#$%&'()*"
"", // Empty string is technically a valid base64
];
for (const str of validBase64Strings) {
expect(str + z.string().base64().safeParse(str).success).toBe(`${str}true`);
}
const invalidBase64Strings = [
"12345", // Not padded correctly, not a multiple of 4 characters
"SGVsbG8gV29ybGQ", // Missing padding
"VGhpcyBpcyBhbiBlbmNvZGVkIHN0cmluZw", // Missing padding
"!UGF0aWVuY2UgaXMgdGhlIGtleSB0byBzdWNjZXNz", // Invalid character '!'
"?QmFzZTY0IGVuY29kaW5nIGlzIGZ1bg==", // Invalid character '?'
".MTIzND2Nzg5MC4=", // Invalid character '.'
"QUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVo", // Missing padding
];
for (const str of invalidBase64Strings) {
expect(str + z.string().base64().safeParse(str).success).toBe(`${str}false`);
}
});
test("base64url validations", () => {
const base64url = z.string().base64url();
const validBase64URLStrings = [
"SGVsbG8gV29ybGQ", // "Hello World"
"VGhpcyBpcyBhbiBlbmNvZGVkIHN0cmluZw", // "This is an encoded string"
"TWFueSBoYW5kcyBtYWtlIGxpZ2h0IHdvcms", // "Many hands make light work"
"UGF0aWVuY2UgaXMgdGhlIGtleSB0byBzdWNjZXNz", // "Patience is the key to success"
"QmFzZTY0IGVuY29kaW5nIGlzIGZ1bg", // "Base64 encoding is fun"
"MTIzNDU2Nzg5MA", // "1234567890"
"YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXo", // "abcdefghijklmnopqrstuvwxyz"
"QUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVo", // "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"ISIkJSMmJyonKCk", // "!\"#$%&'()*"
"", // Empty string is technically valid base64url
"w7_Dv8O-w74K", // ÿÿßß
"123456",
];
for (const str of validBase64URLStrings) {
expect(str + base64url.safeParse(str).success).toBe(`${str}true`);
}
const invalidBase64URLStrings = [
"w7/Dv8O+w74K", // Has + and / characters (is base64)
"12345", // Invalid length (not a multiple of 4 characters when adding allowed number of padding characters)
"12345===", // Not padded correctly
"!UGF0aWVuY2UgaXMgdGhlIGtleSB0byBzdWNjZXNz", // Invalid character '!'
"?QmFzZTY0IGVuY29kaW5nIGlzIGZ1bg==", // Invalid character '?'
".MTIzND2Nzg5MC4=", // Invalid character '.'
// disallow valid padding
"SGVsbG8gV29ybGQ=", // "Hello World" with padding
"VGhpcyBpcyBhbiBlbmNvZGVkIHN0cmluZw==", // "This is an encoded string" with padding
"TWFueSBoYW5kcyBtYWtlIGxpZ2h0IHdvcms=", // "Many hands make light work" with padding
"QmFzZTY0IGVuY29kaW5nIGlzIGZ1bg==", // "Base64 encoding is fun" with padding
"MTIzNDU2Nzg5MA==", // "1234567890" with padding
"YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXo=", // "abcdefghijklmnopqrstuvwxyz with padding"
"QUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVo=", // "ABCDEFGHIJKLMNOPQRSTUVWXYZ" with padding
"ISIkJSMmJyonKCk=", // "!\"#$%&'()*" with padding
];
for (const str of invalidBase64URLStrings) {
expect(str + base64url.safeParse(str).success).toBe(`${str}false`);
}
});
test("big base64 and base64url", () => {
const bigbase64 = randomBytes(1024 * 1024 * 10).toString("base64");
z.base64().parse(bigbase64);
const bigbase64url = randomBytes(1024 * 1024 * 10).toString("base64url");
z.base64url().parse(bigbase64url);
});
function makeJwt(header: object, payload: object) {
const headerBase64 = Buffer.from(JSON.stringify(header)).toString("base64url");
const payloadBase64 = Buffer.from(JSON.stringify(payload)).toString("base64url");
const signature = "signature"; // Placeholder for the signature
return `${headerBase64}.${payloadBase64}.${signature}`;
}
test("jwt token", () => {
const jwt = z.string().jwt();
expect(() => jwt.parse("invalid")).toThrow();
expect(() => jwt.parse("invalid.invalid")).toThrow();
expect(() => jwt.parse("invalid.invalid.invalid")).toThrow();
// Valid JWTs
const es256jwt = z.string().jwt({ alg: "ES256" });
const d1 = makeJwt({ typ: "JWT", alg: "ES256" }, {});
jwt.parse(d1);
es256jwt.parse(d1);
// Invalid header
const d2 = makeJwt({}, {});
expect(() => jwt.parse(d2)).toThrow();
// Wrong algorithm
const d3 = makeJwt({ typ: "JWT", alg: "RS256" }, {});
expect(() => es256jwt.parse(d3)).toThrow();
// missing typ is fine
const d4 = makeJwt({ alg: "HS256" }, {});
jwt.parse(d4);
// type isn't JWT
const d5 = makeJwt({ typ: "SUP", alg: "HS256" }, { foo: "bar" });
expect(() => jwt.parse(d5)).toThrow();
// const ONE_PART = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9";
// const NOT_BASE64 =
// "headerIsNotBase64Encoded.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.RRi1X2IlXd5rZa9Mf_0VUOf-RxOzAhbB4tgViUGamWE";
// const NO_TYP =
// "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.GuoUe6tw79bJlbU1HU0ADX0pr0u2kf3r_4OdrDufSfQ";
// const TYP_NOT_JWT =
// "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpUVyJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.RRi1X2IlXd5rZa9Mf_0VUOf-RxOzAhbB4tgViUGamWE";
// const GOOD_JWT_HS256 =
// "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c";
// const GOOD_JWT_ES256 =
// "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.tyh-VfuzIxCyGYDlkBA7DfyjrqmSHu6pQ2hoZuFqUSLPNY2N0mpHb3nk5K17HWP_3cYHBw7AhHale5wky6-sVA";
// const jwtSchema = z.string().jwt();
// expect(() => jwtSchema.parse(ONE_PART)).toThrow();
// expect(() => jwtSchema.parse(NOT_BASE64)).toThrow();
// expect(() => jwtSchema.parse(TYP_NOT_JWT)).toThrow();
// expect(() => jwtSchema.parse(TYP_NOT_JWT)).toThrow();
// expect(() => jwtSchema.parse(TYP_NOT_JWT)).toThrow();
// expect(() => z.string().jwt({ alg: "ES256" }).parse(GOOD_JWT_HS256)).toThrow();
// expect(() => z.string().jwt({ alg: "HS256" }).parse(GOOD_JWT_ES256)).toThrow();
// //Success
// jwtSchema.parse(NO_TYP); // allow no typ
// expect(() => jwtSchema.parse(GOOD_JWT_HS256)).not.toThrow();
// expect(() => jwtSchema.parse(GOOD_JWT_ES256)).not.toThrow();
// expect(() => z.string().jwt({ alg: "HS256" }).parse(GOOD_JWT_HS256)).not.toThrow();
// expect(() => z.string().jwt({ alg: "ES256" }).parse(GOOD_JWT_ES256)).not.toThrow();
});
test("url validations", () => {
const url = z.string().url();
url.parse("http://google.com");
url.parse("https://google.com/asdf?asdf=ljk3lk4&asdf=234#asdf");
url.parse("https://anonymous:flabada@developer.mozilla.org/en-US/docs/Web/API/URL/password");
url.parse("https://localhost");
url.parse("https://my.local");
url.parse("http://aslkfjdalsdfkjaf");
url.parse("http://localhost");
expect(() => url.parse("asdf")).toThrow();
expect(() => url.parse("http:.......///broken.com")).toThrow();
expect(() => url.parse("c:")).toThrow();
expect(() => url.parse("WWW:WWW.COM")).toThrow();
expect(() => url.parse("https:/")).toThrow();
expect(() => url.parse("asdfj@lkjsdf.com")).toThrow();
});
test("url error overrides", () => {
try {
z.string().url().parse("https");
} catch (err) {
expect((err as z.ZodError).issues[0].message).toEqual("Invalid URL");
}
try {
z.string().url("badurl").parse("https");
} catch (err) {
expect((err as z.ZodError).issues[0].message).toEqual("badurl");
}
try {
z.string().url({ message: "badurl" }).parse("https");
} catch (err) {
expect((err as z.ZodError).issues[0].message).toEqual("badurl");
}
});
test("emoji validations", () => {
const emoji = z.string().emoji();
emoji.parse("ðððð");
emoji.parse("ðºð©âðð«¡");
emoji.parse("ððððâ€ïž");
emoji.parse("ðððð¡ðŠð¢ðšð«ðâð¡ð¹ðð¬â¡ïžð¹ððšâðãœïžðŠððŽððð°ð³ðºð");
emoji.parse("ð¹ð·ð€œð¿ââïž");
emoji.parse(
"ðððð€£ððð
ðððððððð¥°ðððâºïžâºðð€ð€©ð€ð€šððð¶ððð£ð¥ð®ð€ð¯ðªð«ðŽððððð€€ðððððð€ð²â¹ïžâ¹ððððð€ð¢ððŠð§ðšð©ð€¯ð¬ð°ð±ð¥µð¥¶ð³ð€ªðµð¡ð ð€¬ð·ð€ð€ð€¢ð€®ð€§ðð€ ð¥³ð¥Žð¥ºð€¥ð€«ð€ð§ð€ðð¿ð€¡ð¹ðºðâ ïžâ ð»ðœðŸð€ð©ðºðžð¹ð»ðŒðœðð¿ðŸðððð»ðŒðœðŸð¿ð¶ð¶ð»ð¶ðŒð¶ðœð¶ðŸð¶ð¿ð§ð§ð»ð§ðŒð§ðœð§ðŸð§ð¿ðŠðŠð»ðŠðŒðŠðœðŠðŸðŠð¿ð§ð§ð»ð§ðŒð§ðœð§ðŸð§ð¿ð§ð§ð»ð§ðŒð§ðœð§ðŸð§ð¿ðšðšð»ðšðŒðšðœðšðŸðšð¿ð©ð©ð»ð©ðŒð©ðœð©ðŸð©ð¿ð§ð§ð»ð§ðŒð§ðœð§ðŸð§ð¿ðŽðŽð»ðŽðŒðŽðœðŽðŸðŽð¿ðµðµð»ðµðŒðµðœðµðŸðµð¿ðšââïžðšââðšð»ââïžðšð»ââðšðŒââïžðšðŒââðšðœââïžðšðœââðšðŸââïžðšðŸââðšð¿ââïžðšð¿ââð©ââïžð©ââð©ð»ââïžð©ð»ââð©ðŒââïžð©ðŒââð©ðœââïžð©ðœââð©ðŸââïžð©ðŸââð©ð¿ââïžð©ð¿ââðšâððšð»âððšðŒâððšðœâððšðŸâððšð¿âðð©âðð©ð»âðð©ðŒâðð©ðœâðð©ðŸâðð©ð¿âððšâð«ðšð»âð«ðšðŒâð«ðšðœâð«ðšðŸâð«ðšð¿âð«ð©âð«ð©ð»âð«ð©ðŒâð«ð©ðœâð«ð©ðŸâð«ð©ð¿âð«ðšââïžðšââðšð»ââïžðšð»ââðšðŒââïžðšðŒââðšðœââïžðšðœââðšðŸââïžðšðŸââðšð¿ââïžðšð¿ââð©ââïžð©ââð©ð»ââïžð©ð»ââð©ðŒââïžð©ðŒââð©ðœââïžð©ðœââð©ðŸââïžð©ðŸââð©ð¿ââïžð©ð¿ââðšâðŸðšð»âðŸðšðŒâðŸðšðœâðŸðšðŸâðŸðšð¿âðŸð©âðŸð©ð»âðŸð©ðŒâðŸð©ðœâðŸð©ðŸâðŸð©ð¿âðŸðšâð³ðšð»âð³ðšðŒâð³ðšðœâð³ðšðŸâð³ðšð¿âð³ð©âð³ð©ð»âð³ð©ðŒâð³ð©ðœâð³ð©ðŸâð³ð©ð¿âð³ðšâð§ðšð»âð§ðšðŒâð§ðšðœâð§ðšðŸâð§ðšð¿âð§ð©âð§ð©ð»âð§ð©ðŒâð§ð©ðœâð§ð©ðŸâð§ð©ð¿âð§ðšâððšð»âððšðŒâððšðœâððšðŸâððšð¿âðð©âðð©ð»âðð©ðŒâðð©ðœâðð©ðŸâðð©ð¿âððšâðŒðšð»âðŒðšðŒâðŒðšðœâðŒðšðŸâðŒðšð¿âðŒð©âðŒð©ð»âðŒð©ðŒâðŒð©ðœâðŒð©ðŸâðŒð©ð¿âðŒðšâð¬ðšð»âð¬ðšðŒâð¬ðšðœâð¬ðšðŸâð¬ðšð¿âð¬ð©âð¬ð©ð»âð¬ð©ðŒâð¬ð©ðœâð¬ð©ðŸâð¬ð©ð¿âð¬ðšâð»ðšð»âð»ðšðŒâð»ðšðœâð»ðšðŸâð»ðšð¿âð»ð©âð»ð©ð»âð»ð©ðŒâð»ð©ðœâð»ð©ðŸâð»ð©ð¿âð»ðšâð€ðšð»âð€ðšðŒâð€ðšðœâð€ðšðŸâð€ðšð¿âð€ð©âð€ð©ð»âð€ð©ðŒâð€ð©ðœâð€ð©ðŸâð€ð©ð¿âð€ðšâðšðšð»âðšðšðŒâðšðšðœâðšðšðŸâðšðšð¿âðšð©âðšð©ð»âðšð©ðŒâðšð©ðœâðšð©ðŸâðšð©ð¿âðšðšââïžðšââðšð»ââïžðšð»ââðšðŒââïžðšðŒââðšðœââïžðšðœââðšðŸââïžðšðŸââðšð¿ââïžðšð¿ââð©ââïžð©ââð©ð»ââïžð©ð»ââð©ðŒââïžð©ðŒââð©ðœââïžð©ðœââð©ðŸââïžð©ðŸââð©ð¿ââïžð©ð¿ââðšâððšð»âððšðŒâððšðœâððšðŸâððšð¿âðð©âðð©ð»âðð©ðŒâðð©ðœâðð©ðŸâðð©ð¿âððšâððšð»âððšðŒâððšðœâððšðŸâððšð¿âðð©âðð©ð»âðð©ðŒâðð©ðœâðð©ðŸâðð©ð¿âðð®ð®ð»ð®ðŒð®ðœð®ðŸð®ð¿ð®ââïžð®ââð®ð»ââïžð®ð»ââð®ðŒââïžð®ðŒââð®ðœââïžð®ðœââð®ðŸââïžð®ðŸââð®ð¿ââïžð®ð¿ââð®ââïžð®ââð®ð»ââïžð®ð»ââð®ðŒââïžð®ðŒââð®ðœââïžð®ðœââð®ðŸââïžð®ðŸââð®ð¿ââïžð®ð¿ââðµïžðµðµð»ðµðŒðµðœðµðŸðµð¿ðµïžââïžðµââïžðµïžââðµââðµð»ââïžðµð»ââðµðŒââïžðµðŒââðµðœââïžðµðœââðµðŸââïžðµðŸââðµð¿ââïžðµð¿ââðµïžââïžðµââïžðµïžââðµââðµð»ââïžðµð»ââðµðŒââïžðµðŒââðµðœââïžðµðœââðµðŸââïžðµðŸââðµð¿ââïžðµð¿ââððð»ððŒððœððŸðð¿ðââïžðââðð»ââïžðð»ââððŒââïžððŒââððœââïžððœââððŸââïžððŸââðð¿ââïžðð¿ââðââïžðââðð»ââïžðð»ââððŒââïžððŒââððœââïžððœââððŸââïžððŸââðð¿ââïžðð¿ââð·ð·ð»ð·ðŒð·ðœð·ðŸð·ð¿ð·ââïžð·ââð·ð»ââïžð·ð»ââð·ðŒââïžð·ðŒââð·ðœââïžð·ðœââð·ðŸââïžð·ðŸââð·ð¿ââïžð·ð¿ââð·ââïžð·ââð·ð»ââïžð·ð»ââð·ðŒââïžð·ðŒââð·ðœââïžð·ðœââð·ðŸââïžð·ðŸââð·ð¿ââïžð·ð¿ââð€Žð€Žð»ð€ŽðŒð€Žðœð€ŽðŸð€Žð¿ðžðžð»ðžðŒðžðœðžðŸðžð¿ð³ð³ð»ð³ðŒð³ðœð³ðŸð³ð¿ð³ââïžð³ââð³ð»ââïžð³ð»ââð³ðŒââïžð³ðŒââð³ðœââïžð³ðœââð³ðŸââïžð³ðŸââð³ð¿ââïžð³ð¿ââð³ââïžð³ââð³ð»ââïžð³ð»ââð³ðŒââïžð³ðŒââð³ðœââïžð³ðœââð³ðŸââïžð³ðŸââð³ð¿ââïžð³ð¿ââð²ð²ð»ð²ðŒð²ðœð²ðŸð²ð¿ð§ð§ð»ð§ðŒð§ðœð§ðŸð§ð¿ð§ð§ð»ð§ðŒð§ðœð§ðŸð§ð¿ð±ð±ð»ð±ðŒð±ðœð±ðŸð±ð¿ð±ââïžð±ââð±ð»ââïžð±ð»ââð±ðŒââïžð±ðŒââð±ðœââïžð±ðœââð±ðŸââïžð±ðŸââð±ð¿ââïžð±ð¿ââð±ââïžð±ââð±ð»ââïžð±ð»ââð±ðŒââïžð±ðŒââð±ðœââïžð±ðœââð±ðŸââïžð±ðŸââð±ð¿ââïžð±ð¿ââðšâðаðšð»âðаðšðŒâðаðšðœâðаðšðŸâðаðšð¿âðаð©âðаð©ð»âðаð©ðŒâðаð©ðœâðаð©ðŸâðаð©ð¿âðаðšâðбðšð»âðбðšðŒâðбðšðœâðбðšðŸâðбðšð¿âðбð©âðбð©ð»âðбð©ðŒâðбð©ðœâðбð©ðŸâðбð©ð¿âðбðšâðвðšð»âðвðšðŒâðвðšðœâðвðšðŸâðвðšð¿âðвð©âðвð©ð»âðвð©ðŒâðвð©ðœâðвð©ðŸâðвð©ð¿âðвðšâðгðšð»âðгðšðŒâðгðšðœâðгðšðŸâðгðšð¿âðгð©âðгð©ð»âðгð©ðŒâðгð©ðœâðгð©ðŸâðгð©ð¿âðгð€µð€µð»ð€µðŒð€µðœð€µðŸð€µð¿ð°ð°ð»ð°ðŒð°ðœð°ðŸð°ð¿ð€°ð€°ð»ð€°ðŒð€°ðœð€°ðŸð€°ð¿ð€±ð€±ð»ð€±ðŒð€±ðœð€±ðŸð€±ð¿ðŒðŒð»ðŒðŒðŒðœðŒðŸðŒð¿ð
ð
ð»ð
ðŒð
ðœð
ðŸð
ð¿ð€¶ð€¶ð»ð€¶ðŒð€¶ðœð€¶ðŸð€¶ð¿ðŠžðŠžð»ðŠžðŒðŠžðœðŠžðŸðŠžð¿ðŠžââïžðŠžââðŠžð»ââïžðŠžð»ââðŠžðŒââïžðŠžðŒââðŠžðœââïžðŠžðœââðŠžðŸââïžðŠžðŸââðŠžð¿ââïžðŠžð¿ââðŠžââïžðŠžââðŠžð»ââïžðŠžð»ââðŠžðŒââïžðŠžðŒââðŠžðœââïžðŠžðœââðŠžðŸââïžðŠžðŸââðŠžð¿ââïžðŠžð¿ââðйðйð»ðйðŒðйðœðйðŸðйð¿ðйââïžðйââðйð»ââïžðйð»ââðйðŒââïžðйðŒââðйðœââïžðйðœââðйðŸââïžðйðŸââðйð¿ââïžðйð¿ââðйââïžðйââðйð»ââïžðйð»ââðйðŒââïžðйðŒââðйðœââïžðйðœââðйðŸââïžðйðŸââðйð¿ââïžðйð¿ââð§ð§ð»ð§ðŒð§ðœð§ðŸð§ð¿ð§ââïžð§ââð§ð»ââïžð§ð»ââð§ðŒââïžð§ðŒââð§ðœââïžð§ðœââð§ðŸââïžð§ðŸââð§ð¿ââïžð§ð¿ââð§ââïžð§ââð§ð»ââïžð§ð»ââð§ðŒââïžð§ðŒââð§ðœââïžð§ðœââð§ðŸââïžð§ðŸââð§ð¿ââïžð§ð¿ââð§ð§ð»ð§ðŒð§ðœð§ðŸð§ð¿ð§ââïžð§ââð§ð»ââïžð§ð»ââð§ðŒââïžð§ðŒââð§ðœââïžð§ðœââð§ðŸââïžð§ðŸââð§ð¿ââïžð§ð¿ââð§ââïžð§ââð§ð»ââïžð§ð»ââð§ðŒââïžð§ðŒââð§ðœââïžð§ðœââð§ðŸââïžð§ðŸââð§ð¿ââïžð§ð¿ââð§ð§ð»ð§ðŒð§ðœð§ðŸð§ð¿ð§ââïžð§ââð§ð»ââïžð§ð»ââð§ðŒââïžð§ðŒââð§ðœââïžð§ðœââð§ðŸââïžð§ðŸââð§ð¿ââïžð§ð¿ââð§ââïžð§ââð§ð»ââïžð§ð»ââð§ðŒââïžð§ðŒââð§ðœââïžð§ðœââð§ðŸââïžð§ðŸââð§ð¿ââïžð§ð¿ââð§ð§ð»ð§ðŒð§ðœð§ðŸð§ð¿ð§ââïžð§ââð§ð»ââïžð§ð»ââð§ðŒââïžð§ðŒââð§ðœââïžð§ðœââð§ðŸââïžð§ðŸââð§ð¿ââïžð§ð¿ââð§ââïžð§ââð§ð»ââïžð§ð»ââð§ðŒââïžð§ðŒââð§ðœââïžð§ðœââð§ðŸââïžð§ðŸââð§ð¿ââïžð§ð¿ââð§ð§ð»ð§ðŒð§ðœð§ðŸð§ð¿ð§ââïžð§ââð§ð»ââïžð§ð»ââð§ðŒââïžð§ðŒââð§ðœââïžð§ðœââð§ðŸââïžð§ðŸââð§ð¿ââïžð§ð¿ââð§ââïžð§ââð§ð»ââïžð§ð»ââð§ðŒââïžð§ðŒââð§ðœââïžð§ðœââð§ðŸââïžð§ðŸââð§ð¿ââïžð§ð¿ââð§ð§ââïžð§ââð§ââïžð§ââð§ð§ââïžð§ââð§ââïžð§ââððð»ððŒððœððŸðð¿ðââïžðââðð»ââïžðð»ââððŒââïžððŒââððœââïžððœââððŸââïžððŸââðð¿ââïžðð¿ââðââïžðââðð»ââïžðð»ââððŒââïžððŒââððœââïžððœââððŸââïžððŸââðð¿ââïžðð¿ââððð»ððŒððœððŸðð¿ðââïžðââðð»ââïžðð»ââððŒââïžððŒââððœââïžððœââððŸââïžððŸââðð¿ââïžðð¿ââðââïžðââðð»ââïžðð»ââððŒââïžððŒââððœââïžððœââððŸââïžððŸââðð¿ââïžðð¿ââð
ð
ð»ð
ðŒð
ðœð
ðŸð
ð¿ð
ââïžð
ââð
ð»ââïžð
ð»ââð
ðŒââïžð
ðŒââð
ðœââïžð
ðœââð
ðŸââïžð
ðŸââð
ð¿ââïžð
ð¿ââð
ââïžð
ââð
ð»ââïžð
ð»ââð
ðŒââïžð
ðŒââð
ðœââïžð
ðœââð
ðŸââïžð
ðŸââð
ð¿ââïžð
ð¿ââððð»ððŒððœððŸðð¿ðââïžðââðð»ââïžðð»ââððŒââïžððŒââððœââïžððœââððŸââïžððŸââðð¿ââïžðð¿ââðââïžðââðð»ââïžðð»ââððŒââïžððŒââððœââïžððœââððŸââïžððŸââðð¿ââïžðð¿ââððð»ððŒððœððŸðð¿ðââïžðââðð»ââïžðð»ââððŒââïžððŒââððœââïžððœââððŸââïžððŸââðð¿ââïžðð¿ââðââïžðââðð»ââïžðð»ââððŒââïžððŒââððœââïžððœââððŸââïžððŸââðð¿ââïžðð¿ââððð»ððŒððœððŸðð¿ðââïžðââðð»ââïžðð»ââððŒââïžððŒââððœââïžððœââððŸââïžððŸââðð¿ââïžðð¿ââðââïžðââðð»ââïžðð»ââððŒââïžððŒââððœââïžððœââððŸââïžððŸââðð¿ââïžðð¿ââððð»ððŒððœððŸðð¿ðââïžðââðð»ââïžðð»ââððŒââïžððŒââððœââïžððœââððŸââïžððŸââðð¿ââïžðð¿ââðââïžðââðð»ââïžðð»ââððŒââïžððŒââððœââïžððœââððŸââïžððŸââðð¿ââïžðð¿ââð€Šð€Šð»ð€ŠðŒð€Šðœð€ŠðŸð€Šð¿ð€Šââïžð€Šââð€Šð»ââïžð€Šð»ââð€ŠðŒââïžð€ŠðŒââð€Šðœââïžð€Šðœââð€ŠðŸââïžð€ŠðŸââð€Šð¿ââïžð€Šð¿ââð€Šââïžð€Šââð€Šð»ââïžð€Šð»ââð€ŠðŒââïžð€ŠðŒââð€Šðœââïžð€Šðœââð€ŠðŸââïžð€ŠðŸââð€Šð¿ââïžð€Šð¿ââð€·ð€·ð»ð€·ðŒð€·ðœð€·ðŸð€·ð¿ð€·ââïžð€·ââð€·ð»ââïžð€·ð»ââð€·ðŒââïžð€·ðŒââð€·ðœââïžð€·ðœââð€·ðŸââïžð€·ðŸââð€·ð¿ââïžð€·ð¿ââð€·ââïžð€·ââð€·ð»ââïžð€·ð»ââð€·ðŒââïžð€·ðŒââð€·ðœââïžð€·ðœââð€·ðŸââïžð€·ðŸââð€·ð¿ââïžð€·ð¿ââððð»ððŒððœððŸðð¿ðââïžðââðð»ââïžðð»ââððŒââïžððŒââððœââïžððœââððŸââïžððŸââðð¿ââïžðð¿ââðââïžðââðð»ââïžðð»ââððŒââïžððŒââððœââïžððœââððŸââïžððŸââðð¿ââïžðð¿ââððð»ððŒððœððŸðð¿ðââïžðââðð»ââïžðð»ââððŒââïžððŒââððœââïžððœââððŸââïžððŸââðð¿ââïžðð¿ââðââïžðââðð»ââïžðð»ââððŒââïžððŒââððœââïžððœââððŸââïžððŸââðð¿ââïžðð¿ââð¶ð¶ð»ð¶ðŒð¶ðœð¶ðŸð¶ð¿ð¶ââïžð¶ââð¶ð»ââïžð¶ð»ââð¶ðŒââïžð¶ðŒââð¶ðœââïžð¶ðœââð¶ðŸââïžð¶ðŸââð¶ð¿ââïžð¶ð¿ââð¶ââïžð¶ââð¶ð»ââïžð¶ð»ââð¶ðŒââïžð¶ðŒââð¶ðœââïžð¶ðœââð¶ðŸââïžð¶ðŸââð¶ð¿ââïžð¶ð¿ââððð»ððŒððœððŸðð¿ðââïžðââðð»ââïžðð»ââððŒââïžððŒââððœââïžððœââððŸââïžððŸââðð¿ââïžðð¿ââðââïžðââðð»ââïžðð»ââððŒââïžððŒââððœââïžððœââððŸââïžððŸââðð¿ââïžðð¿ââððð»ððŒððœððŸðð¿ðºðºð»ðºðŒðºðœðºðŸðºð¿ð¯ð¯ââïžð¯ââð¯ââïžð¯ââð§ð§ð»ð§ðŒð§ðœð§ðŸð§ð¿ð§ââïžð§ââð§ð»ââïžð§ð»ââð§ðŒââïžð§ðŒââð§ðœââïžð§ðœââð§ðŸââïžð§ðŸââð§ð¿ââïžð§ð¿ââð§ââïžð§ââð§ð»ââïžð§ð»ââð§ðŒââïžð§ðŒââð§ðœââïžð§ðœââð§ðŸââïžð§ðŸââð§ð¿ââïžð§ð¿ââð§ð§ð»ð§ðŒð§ðœð§ðŸð§ð¿ð§ââïžð§ââð§ð»ââïžð§ð»ââð§ðŒââïžð§ðŒââð§ðœââïžð§ðœââð§ðŸââïžð§ðŸââð§ð¿ââïžð§ð¿ââð§ââïžð§ââð§ð»ââïžð§ð»ââð§ðŒââïžð§ðŒââð§ðœââïžð§ðœââð§ðŸââïžð§ðŸââð§ð¿ââïžð§ð¿ââð§ð§ð»ð§ðŒð§ðœð§ðŸð§ð¿ð§ââïžð§ââð§ð»ââïžð§ð»ââð§ðŒââïžð§ðŒââð§ðœââïžð§ðœââð§ðŸââïžð§ðŸââð§ð¿ââïžð§ð¿ââð§ââïžð§ââð§ð»ââïžð§ð»ââð§ðŒââïžð§ðŒââð§ðœââïžð§ðœââð§ðŸââïžð§ðŸââð§ð¿ââïžð§ð¿ââððð»ððŒððœððŸðð¿ððð»ððŒððœððŸðð¿ðŽïžðŽðŽð»ðŽðŒðŽðœðŽðŸðŽð¿ð£ïžð£ð€ð¥ð€ºððð»ððŒððœððŸðð¿â·ïžâ·ððð»ððŒððœððŸðð¿ðïžððð»ððŒððœððŸðð¿ðïžââïžðââïžðïžââðââðð»ââïžðð»ââððŒââïžððŒââððœââïžððœââððŸââïžððŸââðð¿ââïžðð¿ââðïžââïžðââïžðïžââðââðð»ââïžðð»ââððŒââïžððŒââððœââïžððœââððŸââïžððŸââðð¿ââïžðð¿ââððð»ððŒððœððŸðð¿ðââïžðââðð»ââïžðð»ââððŒââïžððŒââððœââïžððœââððŸââïžððŸââðð¿ââïžðð¿ââðââïžðââðð»ââïžðð»ââððŒââïžððŒââððœââïžððœââððŸââïžððŸââðð¿ââïžðð¿ââð£ð£ð»ð£ðŒð£ðœð£ðŸð£ð¿ð£ââïžð£ââð£ð»ââïžð£ð»ââð£ðŒââïžð£ðŒââð£ðœââïžð£ðœââð£ðŸââïžð£ðŸââð£ð¿ââïžð£ð¿ââð£ââïžð£ââð£ð»ââïžð£ð»ââð£ðŒââïžð£ðŒââð£ðœââïžð£ðœââð£ðŸââïžð£ðŸââð£ð¿ââïžð£ð¿ââððð»ððŒððœððŸðð¿ðââïžðââðð»ââïžðð»ââððŒââïžððŒââððœââïžððœââððŸââïžððŸââðð¿ââïžðð¿ââðââïžðââðð»ââïžðð»ââððŒââïžððŒââððœââïžððœââððŸââïžððŸââðð¿ââïžðð¿âââ¹ïžâ¹â¹ð»â¹ðŒâ¹ðœâ¹ðŸâ¹ð¿â¹ïžââïžâ¹ââïžâ¹ïžâââ¹âââ¹ð»ââïžâ¹ð»âââ¹ðŒââïžâ¹ðŒâââ¹ðœââïžâ¹ðœâââ¹ðŸââïžâ¹ðŸâââ¹ð¿ââïžâ¹ð¿âââ¹ïžââïžâ¹ââïžâ¹ïžâââ¹âââ¹ð»ââïžâ¹ð»âââ¹ðŒââïžâ¹ðŒâââ¹ðœââïžâ¹ðœâââ¹ðŸââïžâ¹ðŸâââ¹ð¿ââïžâ¹ð¿ââðïžððð»ððŒððœððŸðð¿ðïžââïžðââïžðïžââðââðð»ââïžðð»ââððŒââïžððŒââððœââïžððœââððŸââïžððŸââðð¿ââïžðð¿ââðïžââïžðââïžðïžââðââðð»ââïžðð»ââððŒââïžððŒââððœââïžððœââððŸââïžððŸââðð¿ââïžðð¿ââðŽðŽð»ðŽðŒðŽðœðŽðŸðŽð¿ðŽââïžðŽââðŽð»ââïžðŽð»ââðŽðŒââïžðŽðŒââðŽðœââïžðŽðœââðŽðŸââïžðŽðŸââðŽð¿ââïžðŽð¿ââðŽââïžðŽââðŽð»ââïžðŽð»ââðŽðŒââïžðŽðŒââðŽðœââïžðŽðœââðŽðŸââïžðŽðŸââðŽð¿ââïžðŽð¿ââðµðµð»ðµðŒðµðœðµðŸðµð¿ðµââïžðµââðµð»ââïžðµð»ââðµðŒââïžðµðŒââðµðœââïžðµðœââðµðŸââïžðµðŸââðµð¿ââïžðµð¿ââðµââïžðµââðµð»ââïžðµð»ââðµðŒââïžðµðŒââðµðœââïžðµðœââðµðŸââïžðµðŸââðµð¿ââïžðµð¿ââðïžððïžðð€žð€žð»ð€žðŒð€žðœð€žðŸð€žð¿ð€žââïžð€žââð€žð»ââïžð€žð»ââð€žðŒââïžð€žðŒââð€žðœââïžð€žðœââð€žðŸââïžð€žðŸââð€žð¿ââïžð€žð¿ââð€žââïžð€žââð€žð»ââïžð€žð»ââð€žðŒââïžð€žðŒââð€žðœââïžð€žðœââð€žðŸââïžð€žðŸââð€žð¿ââïžð€žð¿ââð€Œð€Œââïžð€Œââð€Œââïžð€Œââð€œð€œð»ð€œðŒð€œðœð€œðŸð€œð¿ð€œââïžð€œââð€œð»ââïžð€œð»ââð€œðŒââïžð€œðŒââð€œðœââïžð€œðœââð€œðŸââïžð€œðŸââð€œð¿ââïžð€œð¿ââð€œââïžð€œââð€œð»ââïžð€œð»ââð€œðŒââïžð€œðŒââð€œðœââïžð€œðœââð€œðŸââïžð€œðŸââð€œð¿ââïžð€œð¿ââð€Ÿð€Ÿð»ð€ŸðŒð€Ÿðœð€ŸðŸð€Ÿð¿ð€Ÿââïžð€Ÿââð€Ÿð»ââïžð€Ÿð»ââð€ŸðŒââïžð€ŸðŒââð€Ÿðœââïžð€Ÿðœââð€ŸðŸââïžð€ŸðŸââð€Ÿð¿ââïžð€Ÿð¿ââð€Ÿââïžð€Ÿââð€Ÿð»ââïžð€Ÿð»ââð€ŸðŒââïžð€ŸðŒââð€Ÿðœââïžð€Ÿðœââð€ŸðŸââïžð€ŸðŸââð€Ÿð¿ââïžð€Ÿð¿ââð€¹ð€¹ð»ð€¹ðŒð€¹ðœð€¹ðŸð€¹ð¿ð€¹ââïžð€¹ââð€¹ð»ââïžð€¹ð»ââð€¹ðŒââïžð€¹ðŒââð€¹ðœââïžð€¹ðœââð€¹ðŸââïžð€¹ðŸââð€¹ð¿ââïžð€¹ð¿ââð€¹ââïžð€¹ââð€¹ð»ââïžð€¹ð»ââð€¹ðŒââïžð€¹ðŒââð€¹ðœââïžð€¹ðœââð€¹ðŸââïžð€¹ðŸââð€¹ð¿ââïžð€¹ð¿ââð«ð¬ððð©ââ€ïžâðâðšð©ââ€âðâðšðšââ€ïžâðâðšðšââ€âðâðšð©ââ€ïžâðâð©ð©ââ€âðâð©ðð©ââ€ïžâðšð©ââ€âðšðšââ€ïžâðšðšââ€âðšð©ââ€ïžâð©ð©ââ€âð©ðªðšâð©âðŠðšâð©âð§ðšâð©âð§âðŠðšâð©âðŠâðŠðšâð©âð§âð§ðšâðšâðŠðšâðšâð§ðšâðšâð§âðŠðšâðšâðŠâðŠðšâðšâð§âð§ð©âð©âðŠð©âð©âð§ð©âð©âð§âðŠð©âð©âðŠâðŠð©âð©âð§âð§ðšâðŠðšâðŠâðŠðšâð§ðšâð§âðŠðšâð§âð§ð©âðŠð©âðŠâðŠð©âð§ð©âð§âðŠð©âð§âð§ð€³ð€³ð»ð€³ðŒð€³ðœð€³ðŸð€³ð¿ðªðªð»ðªðŒðªðœðªðŸðªð¿ðеðеð»ðеðŒðеðœðеðŸðеð¿ðжðжð»ðжðŒðжðœðжðŸðжð¿ððð»ððŒððœððŸðð¿ððð»ððŒððœððŸðð¿âïžââð»âðŒâðœâðŸâð¿ððð»ððŒððœððŸðð¿ððð»ððŒððœððŸðð¿ððð»ððŒððœððŸðð¿âïžââð»âðŒâðœâðŸâð¿ð€ð€ð»ð€ðŒð€ðœð€ðŸð€ð¿ððð»ððŒððœððŸðð¿ð€ð€ð»ð€ðŒð€ðœð€ðŸð€ð¿ð€ð€ð»ð€ðŒð€ðœð€ðŸð€ð¿ðïžððð»ððŒððœððŸðð¿ââð»âðŒâðœâðŸâð¿ððð»ððŒððœððŸðð¿ððð»ððŒððœððŸðð¿ððð»ððŒððœððŸðð¿ââð»âðŒâðœâðŸâð¿ððð»ððŒððœððŸðð¿ð€ð€ð»ð€ðŒð€ðœð€ðŸð€ð¿ð€ð€ð»ð€ðŒð€ðœð€ðŸð€ð¿ð€ð€ð»ð€ðŒð€ðœð€ðŸð€ð¿ððð»ððŒððœððŸðð¿ð€ð€ð»ð€ðŒð€ðœð€ðŸð€ð¿âïžââð»âðŒâðœâðŸâð¿ððð»ððŒððœððŸðð¿ððð»ððŒððœððŸðð¿ððð»ððŒððœððŸðð¿ð€²ð€²ð»ð€²ðŒð€²ðœð€²ðŸð€²ð¿ððð»ððŒððœððŸðð¿ð€ð
ð
ð»ð
ðŒð
ðœð
ðŸð
ð¿ððð»ððŒððœððŸðð¿ððð»ððŒððœððŸðð¿ðаðбðвðгð£ððïžððïžâðšïžðâðšïžðïžâðšðâðšð§ ðŠŽðŠ·ð
ðððâ€ïžâ€ððððððððð§¡ðð€ðððâ£ïžâ£ðð€ð¢ð£ð¥ðŠðšð«ð¬ðšïžðšð¯ïžð¯ðð³ïžð³ðð¶ïžð¶ð¥œð¥Œðððð§£ð§€ð§¥ð§Šððððððððïžððððð¥Ÿð¥¿ð ð¡ð¢ððð©ðð§¢âïžâð¿ððððµððŠð¶ðð©ðºðŠðŠð±ððŠð¯ð
ððŽððŠðŠðŠð®ðððð·ðððœððððªð«ðŠðŠððŠðŠðððð¹ð°ðð¿ïžð¿ðŠðŠð»ðšðŒðŠðŠ¡ðŸðŠððð£ð€ð¥ðŠð§ðïžððŠ
ðŠðŠ¢ðŠðŠðŠðžðð¢ðŠðð²ððŠðŠð³ðð¬ðð ð¡ðŠðððŠðŠðŠðŠððŠðððððŠð·ïžð·ðžïžðžðŠðŠðŠ ððžð®ðµïžðµð¹ð¥ðºð»ðŒð·ð±ð²ð³ðŽðµðŸð¿âïžâðððððððððððð¥ððððððð¥ð
ð¥¥ð¥ðð¥ð¥ðœð¶ïžð¶ð¥ð¥¬ð¥Šðð¥ð°ðð¥ð¥ð¥šð¥¯ð¥ð§ððð¥©ð¥ððððð¥ªð®ð¯ð¥ð¥ð³ð¥ð²ð¥£ð¥ð¿ð§ð¥«ð±ððððððð ð¢ð£ð€ð¥ð¥®ð¡ð¥ð¥ ð¥¡ðŠð§ðšð©ðªðð°ð§ð¥§ð«ð¬ðð®ð¯ðŒð¥âðµð¶ðŸð·ðžð¹ðºð»ð¥ð¥ð¥€ð¥¢ðœïžðœðŽð¥ðªðºðððððºïžðºðŸð§ðïžðâ°ïžâ°ðð»ðïžððïžððïžððïžððïžððïžððïžððïžðð§±ðïžððïžðð ð¡ð¢ð£ð€ð¥ðŠðšð©ðªð«ð¬ðð¯ð°ððŒðœâªððâ©ïžâ©ðâ²âºðððïžððð
ðððâšïžâšðð ð¡ð¢ððªðððð
ððððððððððððððððððððððððð²ðŽð¹ðµðð£ïžð£ð€ïžð€ð¢ïžð¢âœðšð¥ðŠðð§ââµð¶ð€ð³ïžð³âŽïžâŽð¥ïžð¥ð¢âïžâð©ïžð©ð«ð¬ðºððð ð¡ð°ïžð°ððžðïžðð§³ââ³ââ°â±ïžâ±â²ïžâ²ð°ïžð°ðð§ðððððððððð ðð¡ðð¢ðð£ðð€ðð¥ððŠððððððððððððð¡ïžð¡âïžâððâðð âïžââ
âïžâð€ïžð€ð¥ïžð¥ðŠïžðŠð§ïžð§ðšïžðšð©ïžð©ðªïžðªð«ïžð«ð¬ïžð¬ðððâïžâââ±ïžâ±â¡âïžââïžâââïžâð¥ð§ðððððð§šâšðððððððððð§§ðððïžððïžðð«ðïžððð
ð¥ð¥ð¥âœâŸð¥ðððððŸð¥ð³ðððð¥ððžð¥ð¥ð¥
â³âžïžâžð£ðœð¿ð·ð¥ð¯ð±ð®ð§¿ð®ð¹ïžð¹ð°ð²ð§©ð§žâ ïžâ â¥ïžâ¥âŠïžâŠâ£ïžâ£âïžâðððŽððŒïžðŒðšð§µð§¶ððððð¢ð£ð¯ðððŒðµð¶ðïžððïžððïžðð€ð§ð»ð·ðžð¹ðºð»ð¥ð±ð²âïžâððð ððð»ð¥ïžð¥ðšïžðšâšïžâšð±ïžð±ð²ïžð²ðœðŸð¿ðð§®ð¥ðïžððœïžðœð¬ðºð·ðžð¹ðŒððð¯ïžð¯ð¡ðŠð®ððððððððððððð°ðïžðððð·ïžð·ð°ðŽðµð¶ð·ðžð³ð§Ÿð¹ð±ð²âïžâð§ðšð©ð€ð¥ðŠð«ðªð¬ðð®ð³ïžð³âïžââïžâðïžððïžððïžððïžðððŒðððïžðð
ððïžððïžððððððððððïžðððâïžâðïžððïžððïžðððððððïžððšâïžââïžâð ïžð ð¡ïžð¡âïžâð«ð¹ð¡ïžð¡ð§ð©âïžâðïžðâïžâðâïžâð§°ð§²âïžâð§ªð§«ð§¬ð¬ðð¡ðððªðïžððïžððœð¿ðð§Žð§·ð§¹ð§ºð§»ð§Œð§œð§¯ðð¬â°ïžâ°â±ïžâ±ð¿ð§ð®ð°â¿ð¹ðºð»ðŒðŸðððð
â ïžâ ðžâð«ð³ðð¯ð±ð·ðµðâ¢ïžâ¢â£ïžâ£â¬ïžâ¬âïžââ¡ïžâ¡âïžââ¬ïžâ¬âïžââ¬
ïžâ¬
âïžââïžââïžââ©ïžâ©âªïžâªâ€Žïžâ€Žâ€µïžâ€µððððððððâïžâðïžðâ¡ïžâ¡âžïžâžâ¯ïžâ¯âïžââŠïžâŠâªïžâªâ®ïžâ®ðð¯âââââââââââââðððâ¶ïžâ¶â©âïžââ¯ïžâ¯âïžââªâ®ïžâ®ðŒâ«ðœâ¬âžïžâžâ¹ïžâ¹âºïžâºâïžâðŠð
ðð¶ð³ðŽâïžââïžââïžââŸïžâŸâ»ïžâ»âïžâð±ðð°ââ
âïžââïžââïžâââââââ°â¿ãœïžãœâ³ïžâ³âŽïžâŽâïžââŒïžâŒâïžâââââã°ïžã°Â©ïžÂ©Â®ïžÂ®â¢ïžâ¢#ïžâ£#â£*ïžâ£*â£0ïžâ£0â£1ïžâ£1â£2ïžâ£2â£3ïžâ£3â£4ïžâ£4â£5ïžâ£5â£6ïžâ£6â£7ïžâ£7â£8ïžâ£8â£9ïžâ£9â£ðð¯ð ð¡ð¢ð£ð€ð
°ïžð
°ðð
±ïžð
±ðððâ¹ïžâ¹ðâïžâððð
Ÿïžð
Ÿðð
¿ïžð
¿ðððððïžðð·ïžð·ð¶ð¯ðð¹ðð²ððžðŽð³ãïžããïžãðºðµâªïžâªâ«ïžâ«â»ïžâ»âŒïžâŒâœâŸâ¬â¬ð¶ð·ðžð¹ðºð»ð ðð²ð³âªâ«ðŽðµðð©ððŽð³ïžð³ð³ïžâðð³âððŽââ ïžðŽââ ðŠðšðŠð©ðŠðªðŠð«ðŠð¬ðŠð®ðŠð±ðŠð²ðŠðŽðŠð¶ðŠð·ðŠðžðŠð¹ðŠðºðŠðŒðŠðœðŠð¿ð§ðŠð§ð§ð§ð©ð§ðªð§ð«ð§ð¬ð§ðð§ð®ð§ð¯ð§ð±ð§ð²ð§ð³ð§ðŽð§ð¶ð§ð·ð§ðžð§ð¹ð§ð»ð§ðŒð§ðŸð§ð¿ðšðŠðšðšðšð©ðšð«ðšð¬ðšððšð®ðšð°ðšð±ðšð²ðšð³ðšðŽðšðµðšð·ðšðºðšð»ðšðŒðšðœðšðŸðšð¿ð©ðªð©ð¬ð©ð¯ð©ð°ð©ð²ð©ðŽð©ð¿ðªðŠðªðšðªðªðªð¬ðªððªð·ðªðžðªð¹ðªðºð«ð®ð«ð¯ð«ð°ð«ð²ð«ðŽð«ð·ð¬ðŠð¬ð§ð¬ð©ð¬ðªð¬ð«ð¬ð¬ð¬ðð¬ð®ð¬ð±ð¬ð²ð¬ð³ð¬ðµð¬ð¶ð¬ð·ð¬ðžð¬ð¹ð¬ðºð¬ðŒð¬ðŸðð°ðð²ðð³ðð·ðð¹ððºð®ðšð®ð©ð®ðªð®ð±ð®ð²ð®ð³ð®ðŽð®ð¶ð®ð·ð®ðžð®ð¹ð¯ðªð¯ð²ð¯ðŽð¯ðµð°ðªð°ð¬ð°ðð°ð®ð°ð²ð°ð³ð°ðµð°ð·ð°ðŒð°ðŸð°ð¿ð±ðŠð±ð§ð±ðšð±ð®ð±ð°ð±ð·ð±ðžð±ð¹ð±ðºð±ð»ð±ðŸð²ðŠð²ðšð²ð©ð²ðªð²ð«ð²ð¬ð²ðð²ð°ð²ð±ð²ð²ð²ð³ð²ðŽð²ðµð²ð¶ð²ð·ð²ðžð²ð¹ð²ðºð²ð»ð²ðŒð²ðœð²ðŸð²ð¿ð³ðŠð³ðšð³ðªð³ð«ð³ð¬ð³ð®ð³ð±ð³ðŽð³ðµð³ð·ð³ðºð³ð¿ðŽð²ðµðŠðµðªðµð«ðµð¬ðµððµð°ðµð±ðµð²ðµð³ðµð·ðµðžðµð¹ðµðŒðµðŸð¶ðŠð·ðªð·ðŽð·ðžð·ðºð·ðŒðžðŠðžð§ðžðšðžð©ðžðªðžð¬ðžððžð®ðžð¯ðžð°ðžð±ðžð²ðžð³ðžðŽðžð·ðžðžðžð¹ðžð»ðžðœðžðŸðžð¿ð¹ðŠð¹ðšð¹ð©ð¹ð«ð¹ð¬ð¹ðð¹ð¯ð¹ð°ð¹ð±ð¹ð²ð¹ð³ð¹ðŽð¹ð·ð¹ð¹ð¹ð»ð¹ðŒð¹ð¿ðºðŠðºð¬ðºð²ðºð³ðºðžðºðŸðºð¿ð»ðŠð»ðšð»ðªð»ð¬ð»ð®ð»ð³ð»ðºðŒð«ðŒðžðœð°ðŸðªðŸð¹ð¿ðŠð¿ð²ð¿ðŒðŽó §ó ¢ó ¥ó ®ó §ó ¿ðŽó §ó ¢ó ³ó £ó Žó ¿ðŽó §ó ¢ó ·ó ¬ó ³ó ¿"
);
expect(() => emoji.parse(":-)")).toThrow();
expect(() => emoji.parse("ð is an emoji")).toThrow();
expect(() => emoji.parse("ðstuff")).toThrow();
expect(() => emoji.parse("stuffð")).toThrow();
});
test("nanoid", () => {
const nanoid = z.string().nanoid("custom error");
nanoid.parse("lfNZluvAxMkf7Q8C5H-QS");
nanoid.parse("mIU_4PJWikaU8fMbmkouz");
nanoid.parse("Hb9ZUtUa2JDm_dD-47EGv");
nanoid.parse("5Noocgv_8vQ9oPijj4ioQ");
const result = nanoid.safeParse("Xq90uDyhddC53KsoASYJGX");
expect(result).toMatchObject({ success: false });
expect(result.error!.issues[0].message).toEqual("custom error");
expect(result.error).toMatchInlineSnapshot(`
[ZodError: [
{
"origin": "string",
"code": "invalid_format",
"format": "nanoid",
"pattern": "/^[a-zA-Z0-9_-]{21}$/",
"path": [],
"message": "custom error"
}
]]
`);
});
test("bad nanoid", () => {
const nanoid = z.string().nanoid("custom error");
nanoid.parse("ySh_984wpDUu7IQRrLXAp");
const result = nanoid.safeParse("invalid nanoid");
expect(result).toMatchObject({ success: false });
expect(result.error!.issues[0].message).toEqual("custom error");
expect(result.error).toMatchInlineSnapshot(`
[ZodError: [
{
"origin": "string",
"code": "invalid_format",
"format": "nanoid",
"pattern": "/^[a-zA-Z0-9_-]{21}$/",
"path": [],
"message": "custom error"
}
]]
`);
});
test("good uuid", () => {
const uuid = z.string().uuid("custom error");
const goodUuids = [
"9491d710-3185-1e06-bea0-6a2f275345e0",
"9491d710-3185-2e06-bea0-6a2f275345e0",
"9491d710-3185-3e06-bea0-6a2f275345e0",
"9491d710-3185-4e06-bea0-6a2f275345e0",
"9491d710-3185-5e06-bea0-6a2f275345e0",
"9491d710-3185-5e06-aea0-6a2f275345e0",
"9491d710-3185-5e06-8ea0-6a2f275345e0",
"9491d710-3185-5e06-9ea0-6a2f275345e0",
"00000000-0000-0000-0000-000000000000",
];
for (const goodUuid of goodUuids) {
const result = uuid.safeParse(goodUuid);
expect(result.success).toEqual(true);
}
});
test(`bad uuid`, () => {
const uuid = z.string().uuid("custom error");
for (const badUuid of [
"9491d710-3185-0e06-bea0-6a2f275345e0",
"9491d710-3185-5e06-0ea0-6a2f275345e0",
"d89e7b01-7598-ed11-9d7a-0022489382fd", // new sequential id
"b3ce60f8-e8b9-40f5-1150-172ede56ff74", // Variant 0 - RFC 4122: Reserved, NCS backward compatibility
"92e76bf9-28b3-4730-cd7f-cb6bc51f8c09", // Variant 2 - RFC 4122: Reserved, Microsoft Corporation backward compatibility
"invalid uuid",
"9491d710-3185-4e06-bea0-6a2f275345e0X",
"ffffffff-ffff-ffff-ffff-ffffffffffff",
]) {
const result = uuid.safeParse(badUuid);
expect(result).toMatchObject({ success: false });
expect(result.error?.issues[0].message).toEqual("custom error");
}
});
test("good guid", () => {
const guid = z.string().guid("custom error");
for (const goodGuid of [
"9491d710-3185-4e06-bea0-6a2f275345e0",
"d89e7b01-7598-ed11-9d7a-0022489382fd", // new sequential id
"b3ce60f8-e8b9-40f5-1150-172ede56ff74", // Variant 0 - RFC 4122: Reserved, NCS backward compatibility
"92e76bf9-28b3-4730-cd7f-cb6bc51f8c09", // Variant 2 - RFC 4122: Reserved, Microsoft Corporation backward compatibility
"00000000-0000-0000-0000-000000000000",
"ffffffff-ffff-ffff-ffff-ffffffffffff",
]) {
const result = guid.safeParse(goodGuid);
expect(result.success).toEqual(true);
}
});
test("bad guid", () => {
const guid = z.string().guid("custom error");
for (const badGuid of ["9491d710-3185-4e06-bea0-6a2f275345e0X"]) {
const result = guid.safeParse(badGuid);
expect(result).toMatchObject({ success: false });
expect(result.error?.issues[0].message).toEqual("custom error");
}
});
test("cuid", () => {
const cuid = z.string().cuid();
cuid.parse("ckopqwooh000001la8mbi2im9");
const result = cuid.safeParse("cifjhdsfhsd-invalid-cuid");
expect(result).toMatchObject({ success: false });
expect(result.error!.issues[0].message).toEqual("Invalid cuid");
expect(result.error).toMatchInlineSnapshot(`
[ZodError: [
{
"origin": "string",
"code": "invalid_format",
"format": "cuid",
"pattern": "/^[cC][^\\\\s-]{8,}$/",
"path": [],
"message": "Invalid cuid"
}
]]
`);
});
test("cuid2", () => {
const cuid2 = z.string().cuid2();
const validStrings = [
"a", // short string
"tz4a98xxat96iws9zmbrgj3a", // normal string
"kf5vz6ssxe4zjcb409rjgo747tc5qjazgptvotk6", // longer than require("@paralleldrive/cuid2").bigLength
];
for (const s of validStrings) {
cuid2.parse(s);
}
const invalidStrings = [
"", // empty string
"tz4a98xxat96iws9zMbrgj3a", // include uppercase
"tz4a98xxat96iws-zmbrgj3a", // involve symbols
];
const results = invalidStrings.map((s) => cuid2.safeParse(s));
expect(results.every((r) => !r.success)).toEqual(true);
if (!results[0].success) {
expect(results[0].error.issues[0].message).toEqual("Invalid cuid2");
}
});
test("ulid", () => {
const ulid = z.string().ulid();
ulid.parse("01ARZ3NDEKTSV4RRFFQ69G5FAV");
const result = ulid.safeParse("invalidulid");
expect(result).toMatchObject({ success: false });
const tooLong = "01ARZ3NDEKTSV4RRFFQ69G5FAVA";
expect(ulid.safeParse(tooLong)).toMatchObject({ success: false });
const caseInsensitive = ulid.safeParse("01arZ3nDeKTsV4RRffQ69G5FAV");
expect(caseInsensitive.success).toEqual(true);
expect(result.error!.issues[0].message).toEqual("Invalid ULID");
expect(result.error).toMatchInlineSnapshot(`
[ZodError: [
{
"origin": "string",
"code": "invalid_format",
"format": "ulid",
"pattern": "/^[0-9A-HJKMNP-TV-Za-hjkmnp-tv-z]{26}$/",
"path": [],
"message": "Invalid ULID"
}
]]
`);
});
test("xid", () => {
const xid = z.string().xid();
xid.parse("9m4e2mr0ui3e8a215n4g");
const result = xid.safeParse("invalidxid");
expect(result).toMatchObject({ success: false });
expect(result.error!.issues[0].message).toEqual("Invalid XID");
expect(result.error).toMatchInlineSnapshot(`
[ZodError: [
{
"origin": "string",
"code": "invalid_format",
"format": "xid",
"pattern": "/^[0-9a-vA-V]{20}$/",
"path": [],
"message": "Invalid XID"
}
]]
`);
});
test("ksuid", () => {
const ksuid = z.string().ksuid();
ksuid.parse("0o0t9hkGxgFLtd3lmJ4TSTeY0Vb");
const result = ksuid.safeParse("invalidksuid");
expect(result).toMatchObject({ success: false });
const tooLong = "0o0t9hkGxgFLtd3lmJ4TSTeY0VbA";
expect(ksuid.safeParse(tooLong)).toMatchObject({ success: false });
expect(result.error!.issues).toMatchInlineSnapshot(`
[
{
"code": "invalid_format",
"format": "ksuid",
"message": "Invalid KSUID",
"origin": "string",
"path": [],
"pattern": "/^[A-Za-z0-9]{27}$/",
},
]
`);
});
test("regex", () => {
z.string()
.regex(/^moo+$/)
.parse("mooooo");
expect(() => z.string().uuid().parse("purr")).toThrow();
});
test("regexp error message", () => {
const result = z
.string()
.regex(/^moo+$/)
.safeParse("boooo");
expect(result.error!.issues).toMatchInlineSnapshot(`
[
{
"code": "invalid_format",
"format": "regex",
"message": "Invalid string: must match pattern /^moo+$/",
"origin": "string",
"path": [],
"pattern": "/^moo+$/",
},
]
`);
expect(() => z.string().uuid().parse("purr")).toThrow();
});
test("regexp error custom message", () => {
const result = z
.string()
.regex(/^moo+$/, { message: "Custom error message" })
.safeParse("boooo");
expect(result.error!.issues).toMatchInlineSnapshot(`
[
{
"code": "invalid_format",
"format": "regex",
"message": "Custom error message",
"origin": "string",
"path": [],
"pattern": "/^moo+$/",
},
]
`);
expect(() => z.string().uuid().parse("purr")).toThrow();
});
test("regex lastIndex reset", () => {
const schema = z.string().regex(/^\d+$/g);
expect(schema.safeParse("123").success).toEqual(true);
expect(schema.safeParse("123").success).toEqual(true);
expect(schema.safeParse("123").success).toEqual(true);
expect(schema.safeParse("123").success).toEqual(true);
expect(schema.safeParse("123").success).toEqual(true);
});
test("format", () => {
expect(z.string().email().format).toEqual("email");
expect(z.string().url().format).toEqual("url");
expect(z.string().jwt().format).toEqual("jwt");
expect(z.string().emoji().format).toEqual("emoji");
expect(z.string().guid().format).toEqual("guid");
expect(z.string().uuid().format).toEqual("uuid");
expect(z.string().uuidv4().format).toEqual("uuid");
expect(z.string().uuidv6().format).toEqual("uuid");
expect(z.string().uuidv7().format).toEqual("uuid");
expect(z.string().nanoid().format).toEqual("nanoid");
expect(z.string().guid().format).toEqual("guid");
expect(z.string().cuid().format).toEqual("cuid");
expect(z.string().cuid2().format).toEqual("cuid2");
expect(z.string().ulid().format).toEqual("ulid");
expect(z.string().base64().format).toEqual("base64");
// expect(z.string().jsonString().format).toEqual("json_string");
// expect(z.string().json().format).toEqual("json_string");
expect(z.string().xid().format).toEqual("xid");
expect(z.string().ksuid().format).toEqual("ksuid");
// expect(z.string().ip().format).toEqual("ip");
expect(z.string().ipv4().format).toEqual("ipv4");
expect(z.string().ipv6().format).toEqual("ipv6");
expect(z.string().e164().format).toEqual("e164");
expect(z.string().datetime().format).toEqual("datetime");
expect(z.string().date().format).toEqual("date");
expect(z.string().time().format).toEqual("time");
expect(z.string().duration().format).toEqual("duration");
});
test("min max getters", () => {
expect(z.string().min(5).minLength).toEqual(5);
expect(z.string().min(5).min(10).minLength).toEqual(10);
expect(z.string().minLength).toEqual(null);
expect(z.string().max(5).maxLength).toEqual(5);
expect(z.string().max(5).max(1).maxLength).toEqual(1);
expect(z.string().max(5).max(10).maxLength).toEqual(5);
expect(z.string().maxLength).toEqual(null);
});
test("trim", () => {
expect(z.string().trim().min(2).parse(" 12 ")).toEqual("12");
// ordering of methods is respected
expect(z.string().min(2).trim().parse(" 1 ")).toEqual("1");
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 parsing", () => {
const datetime = z.string().datetime();
datetime.parse("1970-01-01T00:00:00.000Z");
datetime.parse("2022-10-13T09:52:31.816Z");
datetime.parse("2022-10-13T09:52:31.8162314Z");
datetime.parse("1970-01-01T00:00:00Z");
datetime.parse("2022-10-13T09:52:31Z");
expect(() => datetime.parse("")).toThrow();
expect(() => datetime.parse("foo")).toThrow();
expect(() => datetime.parse("2020-10-14")).toThrow();
expect(() => datetime.parse("T18:45:12.123")).toThrow();
expect(() => datetime.parse("2020-10-14T17:42:29+00:00")).toThrow();
const datetimeNoMs = z.string().datetime({ precision: 0 });
datetimeNoMs.parse("1970-01-01T00:00:00Z");
datetimeNoMs.parse("2022-10-13T09:52:31Z");
expect(() => datetimeNoMs.parse("tuna")).toThrow();
expect(() => datetimeNoMs.parse("1970-01-01T00:00:00.000Z")).toThrow();
expect(() => datetimeNoMs.parse("1970-01-01T00:00:00.Z")).toThrow();
expect(() => datetimeNoMs.parse("2022-10-13T09:52:31.816Z")).toThrow();
const datetime3Ms = z.string().datetime({ precision: 3 });
datetime3Ms.parse("1970-01-01T00:00:00.000Z");
datetime3Ms.parse("2022-10-13T09:52:31.123Z");
expect(() => datetime3Ms.parse("tuna")).toThrow();
expect(() => datetime3Ms.parse("1970-01-01T00:00:00.1Z")).toThrow();
expect(() => datetime3Ms.parse("1970-01-01T00:00:00.12Z")).toThrow();
expect(() => datetime3Ms.parse("2022-10-13T09:52:31Z")).toThrow();
const datetimeOffset = z.string().datetime({ offset: true });
datetimeOffset.parse("1970-01-01T00:00:00.000Z");
datetimeOffset.parse("2022-10-13T09:52:31.816234134Z");
datetimeOffset.parse("1970-01-01T00:00:00Z");
datetimeOffset.parse("2022-10-13T09:52:31.4Z");
datetimeOffset.parse("2020-10-14T17:42:29+00:00");
datetimeOffset.parse("2020-10-14T17:42:29+03:15");
datetimeOffset.parse("2020-10-14T17:42:29+0315");
expect(() => datetimeOffset.parse("2020-10-14T17:42:29+03"));
expect(() => datetimeOffset.parse("tuna")).toThrow();
expect(() => datetimeOffset.parse("2022-10-13T09:52:31.Z")).toThrow();
const datetimeOffsetNoMs = z.string().datetime({ offset: true, precision: 0 });
datetimeOffsetNoMs.parse("1970-01-01T00:00:00Z");
datetimeOffsetNoMs.parse("2022-10-13T09:52:31Z");
datetimeOffsetNoMs.parse("2020-10-14T17:42:29+00:00");
datetimeOffsetNoMs.parse("2020-10-14T17:42:29+0000");
expect(() => datetimeOffsetNoMs.parse("2020-10-14T17:42:29+00")).toThrow();
expect(() => datetimeOffsetNoMs.parse("tuna")).toThrow();
expect(() => datetimeOffsetNoMs.parse("1970-01-01T00:00:00.000Z")).toThrow();
expect(() => datetimeOffsetNoMs.parse("1970-01-01T00:00:00.Z")).toThrow();
expect(() => datetimeOffsetNoMs.parse("2022-10-13T09:52:31.816Z")).toThrow();
expect(() => datetimeOffsetNoMs.parse("2020-10-14T17:42:29.124+00:00")).toThrow();
const datetimeOffset4Ms = z.string().datetime({ offset: true, precision: 4 });
datetimeOffset4Ms.parse("1970-01-01T00:00:00.1234Z");
datetimeOffset4Ms.parse("2020-10-14T17:42:29.1234+00:00");
datetimeOffset4Ms.parse("2020-10-14T17:42:29.1234+0000");
expect(() => datetimeOffset4Ms.parse("2020-10-14T17:42:29.1234+00")).toThrow();
expect(() => datetimeOffset4Ms.parse("tuna")).toThrow();
expect(() => datetimeOffset4Ms.parse("1970-01-01T00:00:00.123Z")).toThrow();
expect(() => datetimeOffset4Ms.parse("2020-10-14T17:42:29.124+00:00")).toThrow();
});
test("date parsing", () => {
const date = z.string().date();
date.parse("1970-01-01");
date.parse("2022-01-31");
date.parse("2022-03-31");
date.parse("2022-04-30");
date.parse("2022-05-31");
date.parse("2022-06-30");
date.parse("2022-07-31");
date.parse("2022-08-31");
date.parse("2022-09-30");
date.parse("2022-10-31");
date.parse("2022-11-30");
date.parse("2022-12-31");
date.parse("2000-02-29");
date.parse("2400-02-29");
expect(() => date.parse("2022-02-29")).toThrow();
expect(() => date.parse("2100-02-29")).toThrow();
expect(() => date.parse("2200-02-29")).toThrow();
expect(() => date.parse("2300-02-29")).toThrow();
expect(() => date.parse("2500-02-29")).toThrow();
expect(() => date.parse("")).toThrow();
expect(() => date.parse("foo")).toThrow();
expect(() => date.parse("200-01-01")).toThrow();
expect(() => date.parse("20000-01-01")).toThrow();
expect(() => date.parse("2000-0-01")).toThrow();
expect(() => date.parse("2000-011-01")).toThrow();
expect(() => date.parse("2000-01-0")).toThrow();
expect(() => date.parse("2000-01-011")).toThrow();
expect(() => date.parse("2000/01/01")).toThrow();
expect(() => date.parse("01-01-2022")).toThrow();
expect(() => date.parse("01/01/2022")).toThrow();
expect(() => date.parse("2000-01-01 00:00:00Z")).toThrow();
expect(() => date.parse("2020-10-14T17:42:29+00:00")).toThrow();
expect(() => date.parse("2020-10-14T17:42:29Z")).toThrow();
expect(() => date.parse("2020-10-14T17:42:29")).toThrow();
expect(() => date.parse("2020-10-14T17:42:29.123Z")).toThrow();
expect(() => date.parse("2000-00-12")).toThrow();
expect(() => date.parse("2000-12-00")).toThrow();
expect(() => date.parse("2000-01-32")).toThrow();
expect(() => date.parse("2000-13-01")).toThrow();
expect(() => date.parse("2000-21-01")).toThrow();
expect(() => date.parse("2000-02-30")).toThrow();
expect(() => date.parse("2000-02-31")).toThrow();
expect(() => date.parse("2000-04-31")).toThrow();
expect(() => date.parse("2000-06-31")).toThrow();
expect(() => date.parse("2000-09-31")).toThrow();
expect(() => date.parse("2000-11-31")).toThrow();
});
test("time parsing", () => {
const time = z.string().time();
time.parse("00:00:00");
time.parse("23:00:00");
time.parse("00:59:00");
time.parse("00:00:59");
time.parse("23:59:59");
time.parse("09:52:31");
time.parse("23:59:59.9999999");
expect(() => time.parse("")).toThrow();
expect(() => time.parse("foo")).toThrow();
expect(() => time.parse("00:00:00Z")).toThrow();
expect(() => time.parse("0:00:00")).toThrow();
expect(() => time.parse("00:0:00")).toThrow();
expect(() => time.parse("00:00:0")).toThrow();
expect(() => time.parse("00:00:00.000+00:00")).toThrow();
expect(() => time.parse("24:00:00")).toThrow();
expect(() => time.parse("00:60:00")).toThrow();
expect(() => time.parse("00:00:60")).toThrow();
expect(() => time.parse("24:60:60")).toThrow();
const time2 = z.string().time({ precision: 2 });
time2.parse("00:00:00.00");
time2.parse("09:52:31.12");
time2.parse("23:59:59.99");
expect(() => time2.parse("")).toThrow();
expect(() => time2.parse("foo")).toThrow();
expect(() => time2.parse("00:00:00")).toThrow();
expect(() => time2.parse("00:00:00.00Z")).toThrow();
expect(() => time2.parse("00:00:00.0")).toThrow();
expect(() => time2.parse("00:00:00.000")).toThrow();
expect(() => time2.parse("00:00:00.00+00:00")).toThrow();
});
test("duration", () => {
const duration = z.string().duration();
const validDurations = [
"P3Y6M4DT12H30M5S",
"P2Y9M3DT12H31M8.001S",
// "+P3Y6M4DT12H30M5S",
// "-PT0.001S",
// "+PT0.001S",
"PT0,001S",
"PT12H30M5S",
// "-P2M1D",
// "P-2M-1D",
// "-P5DT10H",
// "P-5DT-10H",
"P1Y",
"P2MT30M",
"PT6H",
"P5W",
// "P0.5Y",
// "P0,5Y",
// "P42YT7.004M",
];
const invalidDurations = [
"foo bar",
"",
" ",
"P",
"PT",
"P1Y2MT",
"T1H",
"P0.5Y1D",
"P0,5Y6M",
"P1YT",
"P-2M-1D",
"P-5DT-10H",
"P1W2D",
"-P1D",
];
for (const val of validDurations) {
const result = duration.safeParse(val);
if (!result.success) {
throw Error(`Valid duration could not be parsed: ${val}`);
}
}
for (const val of invalidDurations) {
const result = duration.safeParse(val);
if (result.success) {
throw Error(`Invalid duration was successful parsed: ${val}`);
}
expect(result.error.issues[0].message).toEqual("Invalid ISO duration");
}
});
// test("IP validation", () => {
// const ipSchema = z.string().ip();
// // General IP validation (accepts both v4 and v6)
// expect(ipSchema.safeParse("114.71.82.94").success).toBe(true);
// expect(ipSchema.safeParse("0.0.0.0").success).toBe(true);
// expect(ipSchema.safeParse("37.85.236.115").success).toBe(true);
// expect(ipSchema.safeParse("1e5e:e6c8:daac:514b:114b:e360:d8c0:682c").success).toBe(true);
// expect(ipSchema.safeParse("9d4:c956:420f:5788:4339:9b3b:2418:75c3").success).toBe(true);
// expect(ipSchema.safeParse("a6ea::2454:a5ce:94.105.123.75").success).toBe(true);
// expect(ipSchema.safeParse("474f:4c83::4e40:a47:ff95:0cda").success).toBe(true);
// expect(ipSchema.safeParse("d329:0:25b4:db47:a9d1:0:4926:0000").success).toBe(true);
// expect(ipSchema.safeParse("e48:10fb:1499:3e28:e4b6:dea5:4692:912c").success).toBe(true);
// expect(ipSchema.safeParse("d329:1be4:25b4:db47:a9d1:dc71:4926:992c:14af").success).toBe(false);
// expect(ipSchema.safeParse("d5e7:7214:2b78::3906:85e6:53cc:709:32ba").success).toBe(false);
// expect(ipSchema.safeParse("8f69::c757:395e:976e::3441").success).toBe(false);
// expect(ipSchema.safeParse("54cb::473f:d516:0.255.256.22").success).toBe(false);
// expect(ipSchema.safeParse("54cb::473f:d516:192.168.1").success).toBe(false);
// expect(ipSchema.safeParse("256.0.4.4").success).toBe(false);
// expect(ipSchema.safeParse("-1.0.555.4").success).toBe(false);
// expect(ipSchema.safeParse("0.0.0.0.0").success).toBe(false);
// expect(ipSchema.safeParse("1.1.1").success).toBe(false);
// });
test("IPv4 validation", () => {
const ipv4 = z.string().ipv4();
// Valid IPv4 addresses
expect(ipv4.safeParse("114.71.82.94").success).toBe(true);
expect(ipv4.safeParse("0.0.0.0").success).toBe(true);
expect(ipv4.safeParse("37.85.236.115").success).toBe(true);
expect(ipv4.safeParse("192.168.0.1").success).toBe(true);
expect(ipv4.safeParse("255.255.255.255").success).toBe(true);
expect(ipv4.safeParse("1.2.3.4").success).toBe(true);
// Invalid IPv4 addresses
expect(ipv4.safeParse("256.0.4.4").success).toBe(false);
expect(ipv4.safeParse("-1.0.555.4").success).toBe(false);
expect(ipv4.safeParse("0.0.0.0.0").success).toBe(false);
expect(ipv4.safeParse("1.1.1").success).toBe(false);
expect(ipv4.safeParse("1e5e:e6c8:daac:514b:114b:e360:d8c0:682c").success).toBe(false);
expect(ipv4.safeParse("a6ea::2454:a5ce:94.105.123.75").success).toBe(false);
expect(ipv4.safeParse("not an ip").success).toBe(false);
expect(ipv4.safeParse("1.2.3").success).toBe(false);
expect(ipv4.safeParse("1.2.3.4.5").success).toBe(false);
expect(ipv4.safeParse("1.2.3.256").success).toBe(false);
// Test specific error
expect(() => ipv4.parse("6097:adfa:6f0b:220d:db08:5021:6191:7990")).toThrow();
});
test("IPv6 validation", () => {
const ipv6 = z.string().ipv6();
// Valid IPv6 addresses
expect(ipv6.safeParse("1e5e:e6c8:daac:514b:114b:e360:d8c0:682c").success).toBe(true);
expect(ipv6.safeParse("9d4:c956:420f:5788:4339:9b3b:2418:75c3").success).toBe(true);
expect(ipv6.safeParse("a6ea::2454:a5ce:94.105.123.75").success).toBe(true);
expect(ipv6.safeParse("474f:4c83::4e40:a47:ff95:0cda").success).toBe(true);
expect(ipv6.safeParse("d329:0:25b4:db47:a9d1:0:4926:0000").success).toBe(true);
expect(ipv6.safeParse("e48:10fb:1499:3e28:e4b6:dea5:4692:912c").success).toBe(true);
expect(ipv6.safeParse("::1").success).toBe(true);
expect(ipv6.safeParse("2001:db8::").success).toBe(true);
expect(ipv6.safeParse("2001:0db8:85a3:0000:0000:8a2e:0370:7334").success).toBe(true);
expect(ipv6.safeParse("2001:db8::192.168.0.1").success).toBe(true);
expect(ipv6.safeParse("::ffff:192.168.0.1").success).toBe(true);
expect(ipv6.safeParse("::ffff:c000:0280").success).toBe(true); // IPv4-mapped IPv6 address
expect(ipv6.safeParse("64:ff9b::192.168.0.1").success).toBe(true); // IPv4/IPv6 translation
// Invalid IPv6 addresses
expect(ipv6.safeParse("d329:1be4:25b4:db47:a9d1:dc71:4926:992c:14af").success).toBe(false);
expect(ipv6.safeParse("d5e7:7214:2b78::3906:85e6:53cc:709:32ba").success).toBe(false);
expect(ipv6.safeParse("8f69::c757:395e:976e::3441").success).toBe(false);
expect(ipv6.safeParse("54cb::473f:d516:0.255.256.22").success).toBe(false);
expect(ipv6.safeParse("54cb::473f:d516:192.168.1").success).toBe(false);
expect(ipv6.safeParse("114.71.82.94").success).toBe(false);
expect(ipv6.safeParse("not an ip").success).toBe(false);
expect(ipv6.safeParse("g123::1234:5678").success).toBe(false);
// Test specific error
expect(() => ipv6.parse("254.164.77.1")).toThrow();
});
test("CIDR v4 validation", () => {
const cidrV4 = z.string().cidrv4();
// Valid CIDR v4 addresses
expect(cidrV4.safeParse("192.168.0.0/24").success).toBe(true);
expect(cidrV4.safeParse("10.0.0.0/8").success).toBe(true);
expect(cidrV4.safeParse("172.16.0.0/12").success).toBe(true);
expect(cidrV4.safeParse("0.0.0.0/0").success).toBe(true);
expect(cidrV4.safeParse("255.255.255.255/32").success).toBe(true);
// Invalid CIDR v4 addresses
expect(cidrV4.safeParse("192.168.0.0").success).toBe(false); // Missing prefix
expect(cidrV4.safeParse("192.168.0.0/33").success).toBe(false); // Invalid prefix length
expect(cidrV4.safeParse("256.0.0.0/24").success).toBe(false); // Invalid IP
expect(cidrV4.safeParse("192.168.0.0/-1").success).toBe(false); // Negative prefix length
expect(cidrV4.safeParse("not a cidr").success).toBe(false); // Invalid format
});
test("CIDR v6 validation", () => {
const cidrV6 = z.string().cidrv6();
// Valid CIDR v6 addresses
expect(cidrV6.safeParse("2001:db8::/32").success).toBe(true);
expect(cidrV6.safeParse("::/0").success).toBe(true);
expect(cidrV6.safeParse("fe80::/10").success).toBe(true);
expect(cidrV6.safeParse("::1/128").success).toBe(true);
expect(cidrV6.safeParse("2001:0db8:85a3::/64").success).toBe(true);
// Invalid CIDR v6 addresses
expect(cidrV6.safeParse("2001:db8::").success).toBe(false); // Missing prefix
expect(cidrV6.safeParse("2001:db8::/129").success).toBe(false); // Invalid prefix length
expect(cidrV6.safeParse("2001:db8::/abc").success).toBe(false); // Invalid prefix format
expect(cidrV6.safeParse("not a cidr").success).toBe(false); // Invalid format
expect(cidrV6.safeParse("192.168.0.0/24").success).toBe(false); // IPv4 CIDR in v6 validation
});
test("E.164 validation", () => {
const e164Number = z.string().e164();
expect(e164Number.safeParse("+1555555").success).toBe(true);
const validE164Numbers = [
"+1555555", // min-length (7 digits + '+')
"+15555555",
"+155555555",
"+1555555555",
"+15555555555",
"+155555555555",
"+1555555555555",
"+15555555555555",
"+155555555555555",
"+105555555555555",
"+100555555555555", // max-length (15 digits + '+')
];
const invalidE164Numbers = [
"", // empty
"+", // only plus sign
"-", // wrong sign
" 555555555", // starts with space
"555555555", // missing plus sign
"+1 555 555 555", // space after plus sign
"+1555 555 555", // space between numbers
"+1555+555", // multiple plus signs
"+1555555555555555", // too long
"+115abc55", // non numeric characters in number part
"+1555555 ", // space after number
];
expect(validE164Numbers.every((number) => e164Number.safeParse(number).success)).toBe(true);
expect(invalidE164Numbers.every((number) => e164Number.safeParse(number).success === false)).toBe(true);
});