Skip to content

Commit

Permalink
Prettier
Browse files Browse the repository at this point in the history
  • Loading branch information
colinhacks committed Dec 10, 2024
1 parent 13af7a3 commit 8976023
Show file tree
Hide file tree
Showing 7 changed files with 114 additions and 56 deletions.
10 changes: 6 additions & 4 deletions ERROR_HANDLING.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,10 +92,12 @@ Here's a sample Person schema.
```ts
const person = z.object({
names: z.array(z.string()).nonempty(), // at least 1 name
address: z.object({
line1: z.string(),
zipCode: z.number().min(10000), // American 5-digit code
}).strict() // do not allow unrecognized keys
address: z
.object({
line1: z.string(),
zipCode: z.number().min(10000), // American 5-digit code
})
.strict(), // do not allow unrecognized keys
});
```

Expand Down
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -614,7 +614,6 @@ bun add zod@canary # bun
pnpm add zod@canary # pnpm
```


> The rest of this README assumes you are using npm and importing directly from the `"zod"` package.
## Basic usage
Expand Down
1 change: 0 additions & 1 deletion README_ZH.md
Original file line number Diff line number Diff line change
Expand Up @@ -326,7 +326,6 @@ bun add zod # bun
pnpm add zod # pnpm
```


> README 的剩余部分假定你是直接通过 npm 安装的`zod`包。
# 基本用法
Expand Down
25 changes: 7 additions & 18 deletions deno/lib/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,7 @@
- [Utilities for Zod](#utilities-for-zod)
- [Installation](#installation)
- [Requirements](#requirements)
- [From `npm` (Node/Bun)](#from-npm-nodebun)
- [From `deno.land/x` (Deno)](#from-denolandx-deno)
- [From `npm`](#from-npm)
- [Basic usage](#basic-usage)
- [Primitives](#primitives)
- [Coercion for primitives](#coercion-for-primitives)
Expand All @@ -81,7 +80,7 @@
- [BigInts](#bigints)
- [NaNs](#nans)
- [Booleans](#booleans)
- [Dates](#dates)
- [Dates](#dates-1)
- [Zod enums](#zod-enums)
- [Native enums](#native-enums)
- [Optionals](#optionals)
Expand Down Expand Up @@ -493,6 +492,7 @@ There are a growing number of tools that are built atop or support Zod natively!
- [`tapiduck`](https://github.com/sumukhbarve/monoduck/blob/main/src/tapiduck/README.md): End-to-end typesafe JSON APIs with Zod and Express; a bit like tRPC, but simpler.
- [`koa-zod-router`](https://github.com/JakeFenley/koa-zod-router): Create typesafe routes in Koa with I/O validation using Zod.
- [`zod-sockets`](https://github.com/RobinTail/zod-sockets): Zod-powered Socket.IO microframework with I/O validation and built-in AsyncAPI specs
- [`oas-tszod-gen`](https://github.com/inkognitro/oas-tszod-gen): Client SDK code generator to convert OpenApi v3 specifications into TS endpoint caller functions with Zod types.

#### Form integrations

Expand All @@ -511,6 +511,7 @@ There are a growing number of tools that are built atop or support Zod natively!
- [`mobx-zod-form`](https://github.com/MonoidDev/mobx-zod-form): Data-first form builder based on MobX & Zod.
- [`@vee-validate/zod`](https://github.com/logaretm/vee-validate/tree/main/packages/zod): Form library for Vue.js with Zod schema validation.
- [`zod-form-renderer`](https://github.com/thepeaklab/zod-form-renderer): Auto-infer form fields from zod schema and render them with react-hook-form with E2E type safety.
- [`antd-zod`](https://github.com/MrBr/antd-zod): Zod adapter for Ant Design form fields validation.

#### Zod to X

Expand Down Expand Up @@ -593,10 +594,11 @@ There are a growing number of tools that are built atop or support Zod natively!
}
```

### From `npm` (Node/Bun)
### From `npm`

```sh
npm install zod # npm
deno add npm:zod # deno
yarn add zod # yarn
bun add zod # bun
pnpm add zod # pnpm
Expand All @@ -606,25 +608,12 @@ Zod also publishes a canary version on every commit. To install the canary:

```sh
npm install zod@canary # npm
deno add npm:zod@canary # deno
yarn add zod@canary # yarn
bun add zod@canary # bun
pnpm add zod@canary # pnpm
```

### From `deno.land/x` (Deno)

Unlike Node, Deno relies on direct URL imports instead of a package manager like NPM. Zod is available on [deno.land/x](https://deno.land/x). The latest version can be imported like so:

```ts
import { z } from "https://deno.land/x/zod/mod.ts";
```

You can also specify a particular version:

```ts
import { z } from "https://deno.land/x/[email protected]/mod.ts";
```

> The rest of this README assumes you are using npm and importing directly from the `"zod"` package.
## Basic usage
Expand Down
1 change: 1 addition & 0 deletions deno/lib/ZodError.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ export type StringValidation =
| "ip"
| "cidr"
| "base64"
| "base64url"
| { includes: string; position?: number }
| { startsWith: string }
| { endsWith: string };
Expand Down
107 changes: 76 additions & 31 deletions deno/lib/__tests__/string.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,40 +165,85 @@ test("email validations", () => {
).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 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 valid base64
"w7/Dv8O+w74K", // ÿÿþþ
];

test.each(validBase64Strings)("base64 should accept %s", (str: string) => {
expect(z.string().base64().safeParse(str).success).toBe(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
];
const invalidBase64Strings = [
"12345", // Not padded correctly, not a multiple of 4 characters
"12345===", // Not padded correctly
"SGVsbG8gV29ybGQ", // Missing padding
"VGhpcyBpcyBhbiBlbmNvZGVkIHN0cmluZw", // Missing padding
"!UGF0aWVuY2UgaXMgdGhlIGtleSB0byBzdWNjZXNz", // Invalid character '!'
"?QmFzZTY0IGVuY29kaW5nIGlzIGZ1bg==", // Invalid character '?'
".MTIzND2Nzg5MC4=", // Invalid character '.'
"QUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVo", // Missing padding
"w7_Dv8O-w74K", // Has - and _ characters (is base64url)
];

test.each(invalidBase64Strings)("base64 should reject %s", (str: string) => {
expect(z.string().base64().safeParse(str).success).toBe(false);
});

for (const str of invalidBase64Strings) {
expect(str + z.string().base64().safeParse(str).success).toBe(
str + "false"
);
const validBase64urlStrings = [
"SGVsbG8gV29ybGQ", // "Hello World"
"SGVsbG8gV29ybGQ=", // "Hello World" with padding
"VGhpcyBpcyBhbiBlbmNvZGVkIHN0cmluZw", // "This is an encoded string"
"VGhpcyBpcyBhbiBlbmNvZGVkIHN0cmluZw==", // "This is an encoded string" with padding
"TWFueSBoYW5kcyBtYWtlIGxpZ2h0IHdvcms", // "Many hands make light work"
"TWFueSBoYW5kcyBtYWtlIGxpZ2h0IHdvcms=", // "Many hands make light work" with padding
"UGF0aWVuY2UgaXMgdGhlIGtleSB0byBzdWNjZXNz", // "Patience is the key to success"
"QmFzZTY0IGVuY29kaW5nIGlzIGZ1bg", // "Base64 encoding is fun"
"QmFzZTY0IGVuY29kaW5nIGlzIGZ1bg==", // "Base64 encoding is fun" with padding
"MTIzNDU2Nzg5MA", // "1234567890"
"MTIzNDU2Nzg5MA==", // "1234567890" with padding
"YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXo", // "abcdefghijklmnopqrstuvwxyz"
"YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXo=", // "abcdefghijklmnopqrstuvwxyz with padding"
"QUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVo", // "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"QUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVo=", // "ABCDEFGHIJKLMNOPQRSTUVWXYZ" with padding
"ISIkJSMmJyonKCk", // "!\"#$%&'()*"
"ISIkJSMmJyonKCk=", // "!\"#$%&'()*" with padding
"", // Empty string is technically valid base64url
"w7_Dv8O-w74K", // ÿÿþþ
"123456",
];

test.each(validBase64urlStrings)(
"base64url should accept %s",
(str: string) => {
expect(z.string().base64url().safeParse(str).success).toBe(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 '.'
];

test.each(invalidBase64urlStrings)(
"base64url should reject %s",
(str: string) => {
expect(z.string().base64url().safeParse(str).success).toBe(false);
}
);

test("url validations", () => {
const url = z.string().url();
Expand Down
25 changes: 24 additions & 1 deletion deno/lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -566,7 +566,8 @@ export type ZodStringCheck =
| { kind: "duration"; message?: string }
| { kind: "ip"; version?: IpVersion; message?: string }
| { kind: "cidr"; version?: IpVersion; message?: string }
| { kind: "base64"; message?: string };
| { kind: "base64"; message?: string }
| { kind: "base64url"; message?: string };

export interface ZodStringDef extends ZodTypeDef {
checks: ZodStringCheck[];
Expand Down Expand Up @@ -623,6 +624,10 @@ const ipv6CidrRegex =
const base64Regex =
/^([0-9a-zA-Z+/]{4})*(([0-9a-zA-Z+/]{2}==)|([0-9a-zA-Z+/]{3}=))?$/;

// https://base64.guru/standards/base64url
const base64urlRegex =
/^([0-9a-zA-Z-_]{4})*(([0-9a-zA-Z-_]{2}(==)?)|([0-9a-zA-Z-_]{3}(=)?))?$/;

// simple
// const dateRegexSource = `\\d{4}-\\d{2}-\\d{2}`;
// no leap year validation
Expand Down Expand Up @@ -969,6 +974,16 @@ export class ZodString extends ZodType<string, ZodStringDef, string> {
});
status.dirty();
}
} else if (check.kind === "base64url") {
if (!base64urlRegex.test(input.data)) {
ctx = this._getOrReturnCtx(input, ctx);
addIssueToContext(ctx, {
validation: "base64url",
code: ZodIssueCode.invalid_string,
message: check.message,
});
status.dirty();
}
} else {
util.assertNever(check);
}
Expand Down Expand Up @@ -1027,6 +1042,10 @@ export class ZodString extends ZodType<string, ZodStringDef, string> {
base64(message?: errorUtil.ErrMessage) {
return this._addCheck({ kind: "base64", ...errorUtil.errToObj(message) });
}
base64url(message?: errorUtil.ErrMessage) {
// base64url encoding is a modification of base64 that can safely be used in URLs and filenames
return this._addCheck({ kind: "base64url", ...errorUtil.errToObj(message) });
}

ip(options?: string | { version?: IpVersion; message?: string }) {
return this._addCheck({ kind: "ip", ...errorUtil.errToObj(options) });
Expand Down Expand Up @@ -1235,6 +1254,10 @@ export class ZodString extends ZodType<string, ZodStringDef, string> {
get isBase64() {
return !!this._def.checks.find((ch) => ch.kind === "base64");
}
get isBase64url() {
// base64url encoding is a modification of base64 that can safely be used in URLs and filenames
return !!this._def.checks.find((ch) => ch.kind === "base64url");
}

get minLength() {
let min: number | null = null;
Expand Down

0 comments on commit 8976023

Please sign in to comment.