Skip to content

Commit

Permalink
feat: Add support for ISO 4217 Currencies
Browse files Browse the repository at this point in the history
  • Loading branch information
Volcanoxp committed Dec 4, 2024
1 parent 207205c commit d93271a
Show file tree
Hide file tree
Showing 9 changed files with 497 additions and 2 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -798,6 +798,7 @@ z.string().date(); // ISO date format (YYYY-MM-DD)
z.string().time(); // ISO time format (HH:mm:ss[.SSSSSS])
z.string().duration(); // ISO 8601 duration
z.string().base64();
z.string().currency(); // ISO 4217 currencies
```

> Check out [validator.js](https://github.com/validatorjs/validator.js) for a bunch of other useful string validation functions that can be used in conjunction with [Refinements](#refine).
Expand Down
1 change: 1 addition & 0 deletions README_ZH.md
Original file line number Diff line number Diff line change
Expand Up @@ -492,6 +492,7 @@ z.string().ip(); // 默认为 IPv4 和 IPv6,选项见下文
z.string().trim(); // 减除空白
z.string().toLowerCase(); // 小写化
z.string().toUpperCase(); // 大写化
z.string().currency(); // ISO 4217
```

> 请查看 [validator.js](https://github.com/validatorjs/validator.js),了解可与 [Refinements](#refine) 结合使用的大量其他有用字符串验证函数。
Expand Down
1 change: 1 addition & 0 deletions deno/lib/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -798,6 +798,7 @@ z.string().date(); // ISO date format (YYYY-MM-DD)
z.string().time(); // ISO time format (HH:mm:ss[.SSSSSS])
z.string().duration(); // ISO 8601 duration
z.string().base64();
z.string().currency(); // ISO 4217 currencies
```

> Check out [validator.js](https://github.com/validatorjs/validator.js) for a bunch of other useful string validation functions that can be used in conjunction with [Refinements](#refine).
Expand Down
1 change: 1 addition & 0 deletions deno/lib/ZodError.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ export type StringValidation =
| "duration"
| "ip"
| "base64"
| "currency"
| { includes: string; position?: number }
| { startsWith: string }
| { endsWith: string };
Expand Down
39 changes: 39 additions & 0 deletions deno/lib/__tests__/string.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,7 @@ test("checks getters", () => {
expect(z.string().email().isNANOID).toEqual(false);
expect(z.string().email().isIP).toEqual(false);
expect(z.string().email().isULID).toEqual(false);
expect(z.string().email().isCurrency).toEqual(false);

expect(z.string().url().isEmail).toEqual(false);
expect(z.string().url().isURL).toEqual(true);
Expand All @@ -383,6 +384,7 @@ test("checks getters", () => {
expect(z.string().url().isNANOID).toEqual(false);
expect(z.string().url().isIP).toEqual(false);
expect(z.string().url().isULID).toEqual(false);
expect(z.string().url().isCurrency).toEqual(false);

expect(z.string().cuid().isEmail).toEqual(false);
expect(z.string().cuid().isURL).toEqual(false);
Expand All @@ -392,6 +394,7 @@ test("checks getters", () => {
expect(z.string().cuid().isNANOID).toEqual(false);
expect(z.string().cuid().isIP).toEqual(false);
expect(z.string().cuid().isULID).toEqual(false);
expect(z.string().cuid().isCurrency).toEqual(false);

expect(z.string().cuid2().isEmail).toEqual(false);
expect(z.string().cuid2().isURL).toEqual(false);
Expand All @@ -401,6 +404,7 @@ test("checks getters", () => {
expect(z.string().cuid2().isNANOID).toEqual(false);
expect(z.string().cuid2().isIP).toEqual(false);
expect(z.string().cuid2().isULID).toEqual(false);
expect(z.string().cuid2().isCurrency).toEqual(false);

expect(z.string().uuid().isEmail).toEqual(false);
expect(z.string().uuid().isURL).toEqual(false);
Expand All @@ -410,6 +414,7 @@ test("checks getters", () => {
expect(z.string().uuid().isNANOID).toEqual(false);
expect(z.string().uuid().isIP).toEqual(false);
expect(z.string().uuid().isULID).toEqual(false);
expect(z.string().uuid().isCurrency).toEqual(false);

expect(z.string().nanoid().isEmail).toEqual(false);
expect(z.string().nanoid().isURL).toEqual(false);
Expand All @@ -419,6 +424,7 @@ test("checks getters", () => {
expect(z.string().nanoid().isNANOID).toEqual(true);
expect(z.string().nanoid().isIP).toEqual(false);
expect(z.string().nanoid().isULID).toEqual(false);
expect(z.string().nanoid().isCurrency).toEqual(false);

expect(z.string().ip().isEmail).toEqual(false);
expect(z.string().ip().isURL).toEqual(false);
Expand All @@ -428,6 +434,7 @@ test("checks getters", () => {
expect(z.string().ip().isNANOID).toEqual(false);
expect(z.string().ip().isIP).toEqual(true);
expect(z.string().ip().isULID).toEqual(false);
expect(z.string().ip().isCurrency).toEqual(false);

expect(z.string().ulid().isEmail).toEqual(false);
expect(z.string().ulid().isURL).toEqual(false);
Expand All @@ -437,6 +444,17 @@ test("checks getters", () => {
expect(z.string().ulid().isNANOID).toEqual(false);
expect(z.string().ulid().isIP).toEqual(false);
expect(z.string().ulid().isULID).toEqual(true);
expect(z.string().ulid().isCurrency).toEqual(false);

expect(z.string().currency().isEmail).toEqual(false);
expect(z.string().currency().isURL).toEqual(false);
expect(z.string().currency().isCUID).toEqual(false);
expect(z.string().currency().isCUID2).toEqual(false);
expect(z.string().currency().isUUID).toEqual(false);
expect(z.string().currency().isNANOID).toEqual(false);
expect(z.string().currency().isIP).toEqual(false);
expect(z.string().currency().isULID).toEqual(false);
expect(z.string().currency().isCurrency).toEqual(true);
});

test("min max getters", () => {
Expand Down Expand Up @@ -769,3 +787,24 @@ test("IP validation", () => {
invalidIPs.every((ip) => ipSchema.safeParse(ip).success === false)
).toBe(true);
});

test("currency", () => {
const currency = z.string().currency();
expect(currency.isCurrency).toEqual(true);

const validCurrencies = ["USD", "EUR", "CAD", "TOP", "ALL", "PEN"];

const invalidCurrencies = ["AAA", "322", "AXAXAX"];

const currencySchema = z.string().currency();
expect(
validCurrencies.every(
(currency) => currencySchema.safeParse(currency).success
)
).toBe(true);
expect(
invalidCurrencies.every(
(currency) => currencySchema.safeParse(currency).success === false
)
).toBe(true);
});
208 changes: 207 additions & 1 deletion deno/lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -565,7 +565,8 @@ export type ZodStringCheck =
}
| { kind: "duration"; message?: string }
| { kind: "ip"; version?: IpVersion; message?: string }
| { kind: "base64"; message?: string };
| { kind: "base64"; message?: string }
| { kind: "currency"; message?: string };

export interface ZodStringDef extends ZodTypeDef {
checks: ZodStringCheck[];
Expand Down Expand Up @@ -671,6 +672,193 @@ function isValidIP(ip: string, version?: IpVersion) {
return false;
}

// ISO 4217
const currencies = new Set([
"AED",
"AFN",
"ALL",
"AMD",
"ANG",
"AOA",
"ARS",
"AUD",
"AWG",
"AZN",
"BAM",
"BBD",
"BDT",
"BGN",
"BHD",
"BIF",
"BMD",
"BND",
"BOB",
"BOV",
"BRL",
"BSD",
"BTN",
"BWP",
"BYN",
"BZD",
"CAD",
"CDF",
"CHE",
"CHF",
"CHW",
"CLF",
"CLP",
"CNY",
"COP",
"COU",
"CRC",
"CUP",
"CVE",
"CZK",
"DJF",
"DKK",
"DOP",
"DZD",
"EGP",
"ERN",
"ETB",
"EUR",
"FJD",
"FKP",
"GBP",
"GEL",
"GHS",
"GIP",
"GMD",
"GNF",
"GTQ",
"GYD",
"HKD",
"HNL",
"HTG",
"HUF",
"IDR",
"ILS",
"INR",
"IQD",
"IRR",
"ISK",
"JMD",
"JOD",
"JPY",
"KES",
"KGS",
"KHR",
"KMF",
"KPW",
"KRW",
"KWD",
"KYD",
"KZT",
"LAK",
"LBP",
"LKR",
"LRD",
"LSL",
"LYD",
"MAD",
"MDL",
"MGA",
"MKD",
"MMK",
"MNT",
"MOP",
"MRU",
"MUR",
"MVR",
"MWK",
"MXN",
"MXV",
"MYR",
"MZN",
"NAD",
"NGN",
"NIO",
"NOK",
"NPR",
"NZD",
"OMR",
"PAB",
"PEN",
"PGK",
"PHP",
"PKR",
"PLN",
"PYG",
"QAR",
"RON",
"RSD",
"RUB",
"RWF",
"SAR",
"SBD",
"SCR",
"SDG",
"SEK",
"SGD",
"SHP",
"SLE",
"SOS",
"SRD",
"SSP",
"STN",
"SVC",
"SYP",
"SZL",
"THB",
"TJS",
"TMT",
"TND",
"TOP",
"TRY",
"TTD",
"TWD",
"TZS",
"UAH",
"UGX",
"USD",
"USN",
"UYI",
"UYU",
"UYW",
"UZS",
"VED",
"VES",
"VND",
"VUV",
"WST",
"XAF",
"XAG",
"XAU",
"XBA",
"XBB",
"XBC",
"XBD",
"XCD",
"XDR",
"XOF",
"XPD",
"XPF",
"XPT",
"XSU",
"XTS",
"XUA",
"XXX",
"YER",
"ZAR",
"ZMW",
"ZWL",
]);

function isValidCurrency(currency: string) {
if (currencies.has(currency)) return true;
return false;
}

export class ZodString extends ZodType<string, ZodStringDef, string> {
_parse(input: ParseInput): ParseReturnType<string> {
if (this._def.coerce) {
Expand Down Expand Up @@ -943,6 +1131,16 @@ export class ZodString extends ZodType<string, ZodStringDef, string> {
});
status.dirty();
}
} else if (check.kind === "currency") {
if (!isValidCurrency(input.data)) {
ctx = this._getOrReturnCtx(input, ctx);
addIssueToContext(ctx, {
validation: "currency",
code: ZodIssueCode.invalid_string,
message: check.message,
});
status.dirty();
}
} else {
util.assertNever(check);
}
Expand Down Expand Up @@ -1002,6 +1200,10 @@ export class ZodString extends ZodType<string, ZodStringDef, string> {
return this._addCheck({ kind: "base64", ...errorUtil.errToObj(message) });
}

currency(message?: errorUtil.ErrMessage) {
return this._addCheck({ kind: "currency", ...errorUtil.errToObj(message) });
}

ip(options?: string | { version?: IpVersion; message?: string }) {
return this._addCheck({ kind: "ip", ...errorUtil.errToObj(options) });
}
Expand Down Expand Up @@ -1203,6 +1405,10 @@ export class ZodString extends ZodType<string, ZodStringDef, string> {
return !!this._def.checks.find((ch) => ch.kind === "base64");
}

get isCurrency() {
return !!this._def.checks.find((ch) => ch.kind === "currency");
}

get minLength() {
let min: number | null = null;
for (const ch of this._def.checks) {
Expand Down
1 change: 1 addition & 0 deletions src/ZodError.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ export type StringValidation =
| "duration"
| "ip"
| "base64"
| "currency"
| { includes: string; position?: number }
| { startsWith: string }
| { endsWith: string };
Expand Down
Loading

0 comments on commit d93271a

Please sign in to comment.