Skip to content

Commit 11f845c

Browse files
authored
1 parent aeb6797 commit 11f845c

File tree

11 files changed

+181
-50
lines changed

11 files changed

+181
-50
lines changed

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,16 @@
22

33
## Version 18
44

5+
### v18.1.0
6+
7+
- Optimization for `zod` 3.23:
8+
- `zod` 3.23 offers
9+
[several features on handling strings](https://github.com/colinhacks/zod/releases/tag/v3.23.0);
10+
- It's also claimed to be "the final 3.x release before Zod 4.0".;
11+
- Using the featured `zod` refinements in the following proprietary schemas: `ez.dateIn()` and `ez.file("base64")`;
12+
- The changes are non-breaking and the compatibility to `zod` 3.22 remains;
13+
- Validation error messages will depend on actual `zod` version installed.
14+
515
### v18.0.0
616

717
- **Breaking changes**:

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@
160160
"tsx": "^4.6.2",
161161
"typescript": "^5.2.2",
162162
"vitest": "^1.5.0",
163-
"zod": "^3.22.3"
163+
"zod": "^3.23.0"
164164
},
165165
"keywords": [
166166
"nodejs",

src/date-in-schema.ts

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,15 @@ import { isValidDate, isoDateRegex } from "./schema-helpers";
44

55
export const ezDateInKind = "DateIn";
66

7-
export const dateIn = () =>
8-
proprietary(
7+
export const dateIn = () => {
8+
const base = z.string();
9+
const hasDateMethod = base.date?.() instanceof z.ZodString;
10+
const schema = hasDateMethod
11+
? z.union([base.date(), base.datetime(), base.datetime({ local: true })])
12+
: base.regex(isoDateRegex); // @todo remove after min zod v3.23 (v19)
13+
14+
return proprietary(
915
ezDateInKind,
10-
z
11-
.string()
12-
.regex(isoDateRegex)
13-
.transform((str) => new Date(str))
14-
.pipe(z.date().refine(isValidDate)),
16+
schema.transform((str) => new Date(str)).pipe(z.date().refine(isValidDate)),
1517
);
18+
};

src/documentation-helpers.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,10 @@ export const depictFile: Depicter<z.ZodType> = ({ schema }) => ({
163163
type: "string",
164164
format:
165165
schema instanceof z.ZodString
166-
? schema._def.checks.find((check) => check.kind === "regex")
166+
? schema._def.checks.find(
167+
/** @todo remove regex check when min zod v3.23 (v19) */
168+
(check) => check.kind === "regex" || check.kind === "base64",
169+
)
167170
? "byte"
168171
: "file"
169172
: "binary",

src/file-schema.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,24 @@ const bufferSchema = z.custom<Buffer>((subject) => Buffer.isBuffer(subject), {
77
message: "Expected Buffer",
88
});
99

10+
/** @todo remove after min zod v3.23 (v19) */
1011
const base64Regex =
1112
/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/;
1213

1314
const variants = {
1415
buffer: () => proprietary(ezFileKind, bufferSchema),
1516
string: () => proprietary(ezFileKind, z.string()),
1617
binary: () => proprietary(ezFileKind, bufferSchema.or(z.string())),
17-
base64: () =>
18-
proprietary(
18+
base64: () => {
19+
const base = z.string();
20+
const hasBase64Method = base.base64?.() instanceof z.ZodString;
21+
return proprietary(
1922
ezFileKind,
20-
z.string().regex(base64Regex, "Does not match base64 encoding"),
21-
),
23+
hasBase64Method
24+
? base.base64()
25+
: base.regex(base64Regex, "Does not match base64 encoding"), // @todo remove after min zod v3.23 (v19)
26+
);
27+
},
2228
};
2329

2430
type Variants = typeof variants;

src/schema-helpers.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ export const isValidDate = (date: Date): boolean => !isNaN(date.getTime());
66
* @example 2021-01-01T00:00:00Z
77
* @example 2021-01-01T00:00:00
88
* @example 2021-01-01
9+
* @todo remove after min zod v3.23 (v19)
910
*/
1011
export const isoDateRegex =
1112
/^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}:\d{2}(\.\d+)?)?Z?$/;
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2+
3+
exports[`ez.dateIn() current mode > parsing > should handle invalid date 1`] = `
4+
[
5+
{
6+
"code": "invalid_string",
7+
"message": "Invalid date",
8+
"path": [],
9+
"validation": "date",
10+
},
11+
]
12+
`;
13+
14+
exports[`ez.dateIn() current mode > parsing > should handle invalid format 1`] = `
15+
[
16+
{
17+
"code": "invalid_string",
18+
"message": "Invalid date",
19+
"path": [],
20+
"validation": "date",
21+
},
22+
]
23+
`;
24+
25+
exports[`ez.dateIn() current mode > parsing > should handle wrong parsed type 1`] = `
26+
[
27+
{
28+
"code": "invalid_union",
29+
"message": "Invalid input",
30+
"path": [],
31+
"unionErrors": [
32+
[ZodError: [
33+
{
34+
"code": "invalid_type",
35+
"expected": "string",
36+
"received": "number",
37+
"path": [],
38+
"message": "Expected string, received number"
39+
}
40+
]],
41+
[ZodError: [
42+
{
43+
"code": "invalid_type",
44+
"expected": "string",
45+
"received": "number",
46+
"path": [],
47+
"message": "Expected string, received number"
48+
}
49+
]],
50+
[ZodError: [
51+
{
52+
"code": "invalid_type",
53+
"expected": "string",
54+
"received": "number",
55+
"path": [],
56+
"message": "Expected string, received number"
57+
}
58+
]],
59+
],
60+
},
61+
]
62+
`;
63+
64+
exports[`ez.dateIn() legacy mode > parsing > should handle invalid date 1`] = `
65+
[
66+
{
67+
"code": "invalid_date",
68+
"message": "Invalid date",
69+
"path": [],
70+
},
71+
]
72+
`;
73+
74+
exports[`ez.dateIn() legacy mode > parsing > should handle invalid format 1`] = `
75+
[
76+
{
77+
"code": "invalid_string",
78+
"message": "Invalid",
79+
"path": [],
80+
"validation": "regex",
81+
},
82+
]
83+
`;
84+
85+
exports[`ez.dateIn() legacy mode > parsing > should handle wrong parsed type 1`] = `
86+
[
87+
{
88+
"code": "invalid_type",
89+
"expected": "string",
90+
"message": "Expected string, received number",
91+
"path": [],
92+
"received": "number",
93+
},
94+
]
95+
`;
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2+
3+
exports[`ez.file() current mode > parsing > should perform additional check for base64 file 1`] = `
4+
[
5+
{
6+
"code": "invalid_string",
7+
"message": "Invalid base64",
8+
"path": [],
9+
"validation": "base64",
10+
},
11+
]
12+
`;
13+
14+
exports[`ez.file() legacy mode > parsing > should perform additional check for base64 file 1`] = `
15+
[
16+
{
17+
"code": "invalid_string",
18+
"message": "Does not match base64 encoding",
19+
"path": [],
20+
"validation": "regex",
21+
},
22+
]
23+
`;

tests/unit/date-in-schema.spec.ts

Lines changed: 14 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,18 @@
11
import { z } from "zod";
22
import { getMeta } from "../../src/metadata";
33
import { ez } from "../../src";
4-
import { describe, expect, test } from "vitest";
4+
import { beforeAll, describe, expect, test, vi } from "vitest";
5+
6+
describe.each(["current", "legacy"])("ez.dateIn() %s mode", (mode) => {
7+
// @todo remove after min zod v3.23 (v19)
8+
beforeAll(() => {
9+
if (mode === "legacy") {
10+
vi.spyOn(z.ZodString.prototype, "date").mockImplementation(
11+
() => null as unknown as z.ZodString,
12+
);
13+
}
14+
});
515

6-
describe("ez.dateIn()", () => {
716
describe("creation", () => {
817
test("should create an instance", () => {
918
const schema = ez.dateIn();
@@ -18,15 +27,7 @@ describe("ez.dateIn()", () => {
1827
const result = schema.safeParse(123);
1928
expect(result.success).toBeFalsy();
2029
if (!result.success) {
21-
expect(result.error.issues).toEqual([
22-
{
23-
code: "invalid_type",
24-
expected: "string",
25-
message: "Expected string, received number",
26-
path: [],
27-
received: "number",
28-
},
29-
]);
30+
expect(result.error.issues).toMatchSnapshot();
3031
}
3132
});
3233

@@ -50,13 +51,7 @@ describe("ez.dateIn()", () => {
5051
const result = schema.safeParse("2022-01-32");
5152
expect(result.success).toBeFalsy();
5253
if (!result.success) {
53-
expect(result.error.issues).toEqual([
54-
{
55-
code: "invalid_date",
56-
message: "Invalid date",
57-
path: [],
58-
},
59-
]);
54+
expect(result.error.issues).toMatchSnapshot();
6055
}
6156
});
6257

@@ -65,14 +60,7 @@ describe("ez.dateIn()", () => {
6560
const result = schema.safeParse("12.01.2021");
6661
expect(result.success).toBeFalsy();
6762
if (!result.success) {
68-
expect(result.error.issues).toEqual([
69-
{
70-
code: "invalid_string",
71-
message: "Invalid",
72-
validation: "regex",
73-
path: [],
74-
},
75-
]);
63+
expect(result.error.issues).toMatchSnapshot();
7664
}
7765
});
7866
});

tests/unit/file-schema.spec.ts

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,18 @@ import { z } from "zod";
33
import { getMeta } from "../../src/metadata";
44
import { ez } from "../../src";
55
import { readFile } from "node:fs/promises";
6-
import { describe, expect, test } from "vitest";
6+
import { beforeAll, describe, expect, test, vi } from "vitest";
7+
8+
describe.each(["current", "legacy"])("ez.file() %s mode", (mode) => {
9+
// @todo remove after min zod v3.23 (v19)
10+
beforeAll(() => {
11+
if (mode === "legacy") {
12+
vi.spyOn(z.ZodString.prototype, "base64").mockImplementation(
13+
() => null as unknown as z.ZodString,
14+
);
15+
}
16+
});
717

8-
describe("ez.file()", () => {
918
describe("creation", () => {
1019
test("should create an instance being string by default", () => {
1120
const schema = ez.file();
@@ -76,14 +85,7 @@ describe("ez.file()", () => {
7685
const result = schema.safeParse("~~~~");
7786
expect(result.success).toBeFalsy();
7887
if (!result.success) {
79-
expect(result.error.issues).toEqual([
80-
{
81-
code: "invalid_string",
82-
message: "Does not match base64 encoding",
83-
validation: "regex",
84-
path: [],
85-
},
86-
]);
88+
expect(result.error.issues).toMatchSnapshot();
8789
}
8890
});
8991

0 commit comments

Comments
 (0)