zod 4.1.0-canary.20250821T014930 → 4.1.0-canary.20250823T071040
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.cjs +17 -7
- package/package.json +1 -1
- package/src/v4/classic/external.ts +0 -1
- package/src/v4/classic/parse.ts +49 -0
- package/src/v4/classic/schemas.ts +145 -7
- package/src/v4/classic/tests/catch.test.ts +25 -0
- package/src/v4/classic/tests/codec-examples.test.ts +538 -0
- package/src/v4/classic/tests/codec.test.ts +532 -0
- package/src/v4/classic/tests/continuability.test.ts +1 -1
- package/src/v4/classic/tests/default.test.ts +32 -0
- package/src/v4/classic/tests/firstparty.test.ts +4 -0
- package/src/v4/classic/tests/function.test.ts +31 -31
- package/src/v4/classic/tests/hash.test.ts +68 -0
- package/src/v4/classic/tests/nonoptional.test.ts +15 -0
- package/src/v4/classic/tests/object.test.ts +31 -0
- package/src/v4/classic/tests/pipe.test.ts +25 -5
- package/src/v4/classic/tests/prefault.test.ts +25 -0
- package/src/v4/classic/tests/preprocess.test.ts +1 -6
- package/src/v4/classic/tests/refine.test.ts +76 -3
- package/src/v4/classic/tests/string-formats.test.ts +16 -0
- package/src/v4/classic/tests/string.test.ts +82 -1
- package/src/v4/classic/tests/stringbool.test.ts +40 -0
- package/src/v4/classic/tests/template-literal.test.ts +1 -1
- package/src/v4/classic/tests/to-json-schema.test.ts +21 -2
- package/src/v4/classic/tests/transform.test.ts +7 -0
- package/src/v4/classic/tests/union.test.ts +1 -1
- package/src/v4/core/api.ts +25 -35
- package/src/v4/core/core.ts +7 -26
- package/src/v4/core/index.ts +0 -1
- package/src/v4/core/json-schema.ts +1 -0
- package/src/v4/core/parse.ts +101 -0
- package/src/v4/core/regexes.ts +40 -1
- package/src/v4/core/schemas.ts +521 -129
- package/src/v4/core/to-json-schema.ts +43 -8
- package/src/v4/core/util.ts +73 -0
- package/src/v4/mini/external.ts +0 -1
- package/src/v4/mini/parse.ts +14 -1
- package/src/v4/mini/schemas.ts +153 -12
- package/src/v4/mini/tests/codec.test.ts +499 -0
- package/src/v4/mini/tests/object.test.ts +9 -0
- package/src/v4/mini/tests/string.test.ts +16 -0
- package/v3/index.cjs +17 -7
- package/v4/classic/coerce.cjs +17 -7
- package/v4/classic/compat.cjs +17 -7
- package/v4/classic/errors.cjs +17 -7
- package/v4/classic/external.cjs +18 -9
- package/v4/classic/external.d.cts +1 -1
- package/v4/classic/external.d.ts +1 -1
- package/v4/classic/external.js +1 -1
- package/v4/classic/index.cjs +17 -7
- package/v4/classic/iso.cjs +17 -7
- package/v4/classic/parse.cjs +27 -8
- package/v4/classic/parse.d.cts +8 -0
- package/v4/classic/parse.d.ts +8 -0
- package/v4/classic/parse.js +9 -0
- package/v4/classic/schemas.cjs +76 -11
- package/v4/classic/schemas.d.cts +48 -2
- package/v4/classic/schemas.d.ts +48 -2
- package/v4/classic/schemas.js +51 -3
- package/v4/core/api.cjs +36 -35
- package/v4/core/api.d.cts +3 -4
- package/v4/core/api.d.ts +3 -4
- package/v4/core/api.js +19 -24
- package/v4/core/checks.cjs +17 -7
- package/v4/core/core.cjs +8 -1
- package/v4/core/core.d.cts +3 -0
- package/v4/core/core.d.ts +3 -0
- package/v4/core/core.js +6 -0
- package/v4/core/errors.cjs +17 -7
- package/v4/core/index.cjs +17 -8
- package/v4/core/index.d.cts +0 -1
- package/v4/core/index.d.ts +0 -1
- package/v4/core/index.js +0 -1
- package/v4/core/json-schema.d.cts +1 -0
- package/v4/core/json-schema.d.ts +1 -0
- package/v4/core/parse.cjs +62 -8
- package/v4/core/parse.d.cts +24 -0
- package/v4/core/parse.d.ts +24 -0
- package/v4/core/parse.js +36 -0
- package/v4/core/regexes.cjs +34 -2
- package/v4/core/regexes.d.cts +16 -0
- package/v4/core/regexes.d.ts +16 -0
- package/v4/core/regexes.js +32 -1
- package/v4/core/schemas.cjs +326 -84
- package/v4/core/schemas.d.cts +61 -3
- package/v4/core/schemas.d.ts +61 -3
- package/v4/core/schemas.js +308 -76
- package/v4/core/to-json-schema.cjs +42 -5
- package/v4/core/to-json-schema.d.cts +4 -3
- package/v4/core/to-json-schema.d.ts +4 -3
- package/v4/core/to-json-schema.js +42 -5
- package/v4/core/util.cjs +69 -0
- package/v4/core/util.d.cts +10 -0
- package/v4/core/util.d.ts +10 -0
- package/v4/core/util.js +62 -0
- package/v4/locales/ar.cjs +17 -7
- package/v4/locales/az.cjs +17 -7
- package/v4/locales/be.cjs +17 -7
- package/v4/locales/bg.cjs +17 -7
- package/v4/locales/ca.cjs +17 -7
- package/v4/locales/cs.cjs +17 -7
- package/v4/locales/da.cjs +17 -7
- package/v4/locales/de.cjs +17 -7
- package/v4/locales/en.cjs +17 -7
- package/v4/locales/eo.cjs +17 -7
- package/v4/locales/es.cjs +17 -7
- package/v4/locales/fa.cjs +17 -7
- package/v4/locales/fi.cjs +17 -7
- package/v4/locales/fr-CA.cjs +17 -7
- package/v4/locales/fr.cjs +17 -7
- package/v4/locales/he.cjs +17 -7
- package/v4/locales/hu.cjs +17 -7
- package/v4/locales/id.cjs +17 -7
- package/v4/locales/is.cjs +17 -7
- package/v4/locales/it.cjs +17 -7
- package/v4/locales/ja.cjs +17 -7
- package/v4/locales/kh.cjs +17 -7
- package/v4/locales/ko.cjs +17 -7
- package/v4/locales/mk.cjs +17 -7
- package/v4/locales/ms.cjs +17 -7
- package/v4/locales/nl.cjs +17 -7
- package/v4/locales/no.cjs +17 -7
- package/v4/locales/ota.cjs +17 -7
- package/v4/locales/pl.cjs +17 -7
- package/v4/locales/ps.cjs +17 -7
- package/v4/locales/pt.cjs +17 -7
- package/v4/locales/ru.cjs +17 -7
- package/v4/locales/sl.cjs +17 -7
- package/v4/locales/sv.cjs +17 -7
- package/v4/locales/ta.cjs +17 -7
- package/v4/locales/th.cjs +17 -7
- package/v4/locales/tr.cjs +17 -7
- package/v4/locales/ua.cjs +17 -7
- package/v4/locales/ur.cjs +17 -7
- package/v4/locales/vi.cjs +17 -7
- package/v4/locales/yo.cjs +17 -7
- package/v4/locales/zh-CN.cjs +17 -7
- package/v4/locales/zh-TW.cjs +17 -7
- package/v4/mini/coerce.cjs +17 -7
- package/v4/mini/external.cjs +18 -9
- package/v4/mini/external.d.cts +1 -1
- package/v4/mini/external.d.ts +1 -1
- package/v4/mini/external.js +1 -1
- package/v4/mini/index.cjs +17 -7
- package/v4/mini/iso.cjs +17 -7
- package/v4/mini/parse.cjs +9 -1
- package/v4/mini/parse.d.cts +1 -1
- package/v4/mini/parse.d.ts +1 -1
- package/v4/mini/parse.js +1 -1
- package/v4/mini/schemas.cjs +75 -10
- package/v4/mini/schemas.d.cts +49 -1
- package/v4/mini/schemas.d.ts +49 -1
- package/v4/mini/schemas.js +49 -2
- package/src/v4/core/function.ts +0 -176
- package/v4/core/function.cjs +0 -102
- package/v4/core/function.d.cts +0 -52
- package/v4/core/function.d.ts +0 -52
- package/v4/core/function.js +0 -75
|
@@ -0,0 +1,538 @@
|
|
|
1
|
+
import { expect, test } from "vitest";
|
|
2
|
+
import * as z from "zod/v4";
|
|
3
|
+
|
|
4
|
+
// ============================================================================
|
|
5
|
+
// Number/BigInt Codecs
|
|
6
|
+
// ============================================================================
|
|
7
|
+
|
|
8
|
+
const stringToNumber = () =>
|
|
9
|
+
z.codec(z.string(), z.number(), {
|
|
10
|
+
decode: (str) => Number.parseFloat(str),
|
|
11
|
+
encode: (num) => num.toString(),
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
const stringToInt = () =>
|
|
15
|
+
z.codec(z.string(), z.int(), {
|
|
16
|
+
decode: (str) => Number.parseInt(str, 10),
|
|
17
|
+
encode: (num) => num.toString(),
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
const stringToBigInt = () =>
|
|
21
|
+
z.codec(z.string(), z.bigint(), {
|
|
22
|
+
decode: (str) => BigInt(str),
|
|
23
|
+
encode: (bigint) => bigint.toString(),
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
const numberToBigInt = () =>
|
|
27
|
+
z.codec(z.int(), z.bigint(), {
|
|
28
|
+
decode: (num) => BigInt(num),
|
|
29
|
+
encode: (bigint) => Number(bigint),
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
// ============================================================================
|
|
33
|
+
// Date/Duration Codecs
|
|
34
|
+
// ============================================================================
|
|
35
|
+
|
|
36
|
+
const isoDatetimeToDate = () =>
|
|
37
|
+
z.codec(z.iso.datetime(), z.date(), {
|
|
38
|
+
decode: (isoString) => new Date(isoString),
|
|
39
|
+
encode: (date) => date.toISOString(),
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
const epochSecondsToDate = () =>
|
|
43
|
+
z.codec(z.int().min(0), z.date(), {
|
|
44
|
+
decode: (seconds) => new Date(seconds * 1000),
|
|
45
|
+
encode: (date) => Math.floor(date.getTime() / 1000),
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
const epochMillisToDate = () =>
|
|
49
|
+
z.codec(z.int().min(0), z.date(), {
|
|
50
|
+
decode: (millis) => new Date(millis),
|
|
51
|
+
encode: (date) => date.getTime(),
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
// ============================================================================
|
|
55
|
+
// JSON Codec
|
|
56
|
+
// ============================================================================
|
|
57
|
+
|
|
58
|
+
const json = <T extends z.ZodTypeAny>(schema: T) =>
|
|
59
|
+
z.codec(z.string(), schema, {
|
|
60
|
+
decode: (jsonString) => JSON.parse(jsonString),
|
|
61
|
+
encode: (value) => JSON.stringify(value),
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
// ============================================================================
|
|
65
|
+
// Text/Bytes Codecs
|
|
66
|
+
// ============================================================================
|
|
67
|
+
|
|
68
|
+
const utf8ToBytes = () =>
|
|
69
|
+
z.codec(z.string(), z.instanceof(Uint8Array), {
|
|
70
|
+
decode: (str) => new TextEncoder().encode(str),
|
|
71
|
+
encode: (bytes) => new TextDecoder().decode(bytes),
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
const bytesToUtf8 = () =>
|
|
75
|
+
z.codec(z.instanceof(Uint8Array), z.string(), {
|
|
76
|
+
decode: (bytes) => new TextDecoder().decode(bytes),
|
|
77
|
+
encode: (str) => new TextEncoder().encode(str),
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
// ============================================================================
|
|
81
|
+
// Binary-to-text Codecs
|
|
82
|
+
// ============================================================================
|
|
83
|
+
|
|
84
|
+
// Using utility functions from z.core.util
|
|
85
|
+
|
|
86
|
+
const base64 = () =>
|
|
87
|
+
z.codec(z.base64(), z.instanceof(Uint8Array), {
|
|
88
|
+
decode: (base64String) => z.core.util.base64ToUint8Array(base64String),
|
|
89
|
+
encode: (bytes) => z.core.util.uint8ArrayToBase64(bytes),
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
const base64urlToBytes = () =>
|
|
93
|
+
z.codec(z.base64url(), z.instanceof(Uint8Array), {
|
|
94
|
+
decode: (base64urlString) => z.core.util.base64urlToUint8Array(base64urlString),
|
|
95
|
+
encode: (bytes) => z.core.util.uint8ArrayToBase64url(bytes),
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
const hexToBytes = () =>
|
|
99
|
+
z.codec(z.hex(), z.instanceof(Uint8Array), {
|
|
100
|
+
decode: (hexString) => z.core.util.hexToUint8Array(hexString),
|
|
101
|
+
encode: (bytes) => z.core.util.uint8ArrayToHex(bytes),
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
// ============================================================================
|
|
105
|
+
// URL Codecs
|
|
106
|
+
// ============================================================================
|
|
107
|
+
|
|
108
|
+
const stringToURL = () =>
|
|
109
|
+
z.codec(z.url(), z.instanceof(URL), {
|
|
110
|
+
decode: (urlString) => new URL(urlString),
|
|
111
|
+
encode: (url) => url.href,
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
const stringToHttpURL = () =>
|
|
115
|
+
z.codec(z.httpUrl(), z.instanceof(URL), {
|
|
116
|
+
decode: (urlString) => new URL(urlString),
|
|
117
|
+
encode: (url) => url.href,
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
const uriComponent = () =>
|
|
121
|
+
z.codec(z.string(), z.string(), {
|
|
122
|
+
decode: (encodedString) => decodeURIComponent(encodedString),
|
|
123
|
+
encode: (decodedString) => encodeURIComponent(decodedString),
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
// ============================================================================
|
|
127
|
+
// Boolean Codec
|
|
128
|
+
// ============================================================================
|
|
129
|
+
|
|
130
|
+
const stringToBoolean = (options?: { truthy?: string[]; falsy?: string[] }) => z.stringbool(options);
|
|
131
|
+
|
|
132
|
+
// ============================================================================
|
|
133
|
+
// Tests
|
|
134
|
+
// ============================================================================
|
|
135
|
+
|
|
136
|
+
test("stringToNumber codec", () => {
|
|
137
|
+
const codec = stringToNumber();
|
|
138
|
+
|
|
139
|
+
// Test decode
|
|
140
|
+
expect(z.decode(codec, "42.5")).toBe(42.5);
|
|
141
|
+
expect(z.decode(codec, "0")).toBe(0);
|
|
142
|
+
expect(z.decode(codec, "-123.456")).toBe(-123.456);
|
|
143
|
+
|
|
144
|
+
// Test encode
|
|
145
|
+
expect(z.encode(codec, 42.5)).toBe("42.5");
|
|
146
|
+
expect(z.encode(codec, 0)).toBe("0");
|
|
147
|
+
expect(z.encode(codec, -123.456)).toBe("-123.456");
|
|
148
|
+
|
|
149
|
+
// Test round trip
|
|
150
|
+
const original = "3.14159";
|
|
151
|
+
const roundTrip = z.encode(codec, z.decode(codec, original));
|
|
152
|
+
expect(roundTrip).toBe("3.14159");
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
test("stringToInt codec", () => {
|
|
156
|
+
const codec = stringToInt();
|
|
157
|
+
|
|
158
|
+
// Test decode
|
|
159
|
+
expect(z.decode(codec, "42")).toBe(42);
|
|
160
|
+
expect(z.decode(codec, "0")).toBe(0);
|
|
161
|
+
expect(z.decode(codec, "-123")).toBe(-123);
|
|
162
|
+
|
|
163
|
+
// Test encode
|
|
164
|
+
expect(z.encode(codec, 42)).toBe("42");
|
|
165
|
+
expect(z.encode(codec, 0)).toBe("0");
|
|
166
|
+
expect(z.encode(codec, -123)).toBe("-123");
|
|
167
|
+
|
|
168
|
+
// Test round trip
|
|
169
|
+
const original = "999";
|
|
170
|
+
const roundTrip = z.encode(codec, z.decode(codec, original));
|
|
171
|
+
expect(roundTrip).toBe("999");
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
test("stringToBigInt codec", () => {
|
|
175
|
+
const codec = stringToBigInt();
|
|
176
|
+
|
|
177
|
+
// Test decode
|
|
178
|
+
expect(z.decode(codec, "123456789012345678901234567890")).toBe(123456789012345678901234567890n);
|
|
179
|
+
expect(z.decode(codec, "0")).toBe(0n);
|
|
180
|
+
expect(z.decode(codec, "-999")).toBe(-999n);
|
|
181
|
+
|
|
182
|
+
// Test encode
|
|
183
|
+
expect(z.encode(codec, 123456789012345678901234567890n)).toBe("123456789012345678901234567890");
|
|
184
|
+
expect(z.encode(codec, 0n)).toBe("0");
|
|
185
|
+
expect(z.encode(codec, -999n)).toBe("-999");
|
|
186
|
+
|
|
187
|
+
// Test round trip
|
|
188
|
+
const original = "987654321098765432109876543210";
|
|
189
|
+
const roundTrip = z.encode(codec, z.decode(codec, original));
|
|
190
|
+
expect(roundTrip).toBe("987654321098765432109876543210");
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
test("numberToBigInt codec", () => {
|
|
194
|
+
const codec = numberToBigInt();
|
|
195
|
+
|
|
196
|
+
// Test decode
|
|
197
|
+
expect(z.decode(codec, 42)).toBe(42n);
|
|
198
|
+
expect(z.decode(codec, 0)).toBe(0n);
|
|
199
|
+
expect(z.decode(codec, -123)).toBe(-123n);
|
|
200
|
+
|
|
201
|
+
// Test encode
|
|
202
|
+
expect(z.encode(codec, 42n)).toBe(42);
|
|
203
|
+
expect(z.encode(codec, 0n)).toBe(0);
|
|
204
|
+
expect(z.encode(codec, -123n)).toBe(-123);
|
|
205
|
+
|
|
206
|
+
// Test round trip
|
|
207
|
+
const original = 999;
|
|
208
|
+
const roundTrip = z.encode(codec, z.decode(codec, original));
|
|
209
|
+
expect(roundTrip).toBe(999);
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
test("isoDatetimeToDate codec", () => {
|
|
213
|
+
const codec = isoDatetimeToDate();
|
|
214
|
+
|
|
215
|
+
// Test decode
|
|
216
|
+
const decoded = z.decode(codec, "2024-01-15T10:30:00.000Z");
|
|
217
|
+
expect(decoded).toBeInstanceOf(Date);
|
|
218
|
+
expect(decoded.getTime()).toBe(1705314600000);
|
|
219
|
+
|
|
220
|
+
// Test encode
|
|
221
|
+
const date = new Date("2024-01-15T10:30:00.000Z");
|
|
222
|
+
expect(z.encode(codec, date)).toBe("2024-01-15T10:30:00.000Z");
|
|
223
|
+
|
|
224
|
+
// Test round trip
|
|
225
|
+
const original = "2024-12-25T15:45:30.123Z";
|
|
226
|
+
const roundTrip = z.encode(codec, z.decode(codec, original));
|
|
227
|
+
expect(roundTrip).toBe("2024-12-25T15:45:30.123Z");
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
test("epochSecondsToDate codec", () => {
|
|
231
|
+
const codec = epochSecondsToDate();
|
|
232
|
+
|
|
233
|
+
// Test decode
|
|
234
|
+
const decoded = z.decode(codec, 1705314600);
|
|
235
|
+
expect(decoded).toBeInstanceOf(Date);
|
|
236
|
+
expect(decoded.getTime()).toBe(1705314600000);
|
|
237
|
+
|
|
238
|
+
// Test encode
|
|
239
|
+
const date = new Date(1705314600000);
|
|
240
|
+
expect(z.encode(codec, date)).toBe(1705314600);
|
|
241
|
+
|
|
242
|
+
// Test round trip
|
|
243
|
+
const original = 1640995200; // 2022-01-01 00:00:00 UTC
|
|
244
|
+
const roundTrip = z.encode(codec, z.decode(codec, original));
|
|
245
|
+
expect(roundTrip).toBe(1640995200);
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
test("epochMillisToDate codec", () => {
|
|
249
|
+
const codec = epochMillisToDate();
|
|
250
|
+
|
|
251
|
+
// Test decode
|
|
252
|
+
const decoded = z.decode(codec, 1705314600000);
|
|
253
|
+
expect(decoded).toBeInstanceOf(Date);
|
|
254
|
+
expect(decoded.getTime()).toBe(1705314600000);
|
|
255
|
+
|
|
256
|
+
// Test encode
|
|
257
|
+
const date = new Date(1705314600000);
|
|
258
|
+
expect(z.encode(codec, date)).toBe(1705314600000);
|
|
259
|
+
|
|
260
|
+
// Test round trip
|
|
261
|
+
const original = 1640995200123; // 2022-01-01 00:00:00.123 UTC
|
|
262
|
+
const roundTrip = z.encode(codec, z.decode(codec, original));
|
|
263
|
+
expect(roundTrip).toBe(1640995200123);
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
test("json codec", () => {
|
|
267
|
+
const codec = json(z.object({ name: z.string(), age: z.number() }));
|
|
268
|
+
|
|
269
|
+
// Test decode
|
|
270
|
+
const decoded = z.decode(codec, '{"name":"Alice","age":30}');
|
|
271
|
+
expect(decoded).toEqual({ name: "Alice", age: 30 });
|
|
272
|
+
|
|
273
|
+
// Test encode
|
|
274
|
+
const encoded = z.encode(codec, { name: "Bob", age: 25 });
|
|
275
|
+
expect(encoded).toBe('{"name":"Bob","age":25}');
|
|
276
|
+
|
|
277
|
+
// Test round trip
|
|
278
|
+
const original = '{"name":"Charlie","age":35}';
|
|
279
|
+
const parsed = z.decode(codec, original);
|
|
280
|
+
const roundTrip = z.encode(codec, parsed);
|
|
281
|
+
expect(JSON.parse(roundTrip)).toEqual(JSON.parse(original));
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
test("utf8ToBytes codec", () => {
|
|
285
|
+
const codec = utf8ToBytes();
|
|
286
|
+
|
|
287
|
+
// Test decode
|
|
288
|
+
const decoded = z.decode(codec, "Hello, 世界!");
|
|
289
|
+
expect(decoded).toBeInstanceOf(Uint8Array);
|
|
290
|
+
expect(Array.from(decoded)).toEqual([72, 101, 108, 108, 111, 44, 32, 228, 184, 150, 231, 149, 140, 33]);
|
|
291
|
+
|
|
292
|
+
// Test encode
|
|
293
|
+
const bytes = new Uint8Array([72, 101, 108, 108, 111]);
|
|
294
|
+
expect(z.encode(codec, bytes)).toBe("Hello");
|
|
295
|
+
|
|
296
|
+
// Test round trip
|
|
297
|
+
const original = "Hello, 世界! 🚀";
|
|
298
|
+
const roundTrip = z.encode(codec, z.decode(codec, original));
|
|
299
|
+
expect(roundTrip).toBe(original);
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
test("bytesToUtf8 codec", () => {
|
|
303
|
+
const codec = bytesToUtf8();
|
|
304
|
+
|
|
305
|
+
// Test decode
|
|
306
|
+
const bytes = new Uint8Array([72, 101, 108, 108, 111]);
|
|
307
|
+
const decoded = z.decode(codec, bytes);
|
|
308
|
+
expect(decoded).toBe("Hello");
|
|
309
|
+
|
|
310
|
+
// Test encode
|
|
311
|
+
const encoded = z.encode(codec, "Hello, 世界!");
|
|
312
|
+
expect(encoded).toBeInstanceOf(Uint8Array);
|
|
313
|
+
expect(Array.from(encoded)).toEqual([72, 101, 108, 108, 111, 44, 32, 228, 184, 150, 231, 149, 140, 33]);
|
|
314
|
+
|
|
315
|
+
// Test round trip
|
|
316
|
+
const original = new Uint8Array([72, 101, 108, 108, 111, 44, 32, 228, 184, 150, 231, 149, 140, 33]);
|
|
317
|
+
const roundTrip = z.encode(codec, z.decode(codec, original));
|
|
318
|
+
expect(roundTrip).toEqual(original);
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
test("base64 codec", () => {
|
|
322
|
+
const codec = base64();
|
|
323
|
+
|
|
324
|
+
// Test decode
|
|
325
|
+
const decoded = z.decode(codec, "SGVsbG8=");
|
|
326
|
+
expect(decoded).toBeInstanceOf(Uint8Array);
|
|
327
|
+
expect(Array.from(decoded)).toEqual([72, 101, 108, 108, 111]);
|
|
328
|
+
|
|
329
|
+
// Test encode
|
|
330
|
+
const bytes = new Uint8Array([72, 101, 108, 108, 111]);
|
|
331
|
+
expect(z.encode(codec, bytes)).toBe("SGVsbG8=");
|
|
332
|
+
|
|
333
|
+
// Test round trip
|
|
334
|
+
const original = "SGVsbG8gV29ybGQh";
|
|
335
|
+
const roundTrip = z.encode(codec, z.decode(codec, original));
|
|
336
|
+
expect(roundTrip).toBe(original);
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
test("base64urlToBytes codec", () => {
|
|
340
|
+
const codec = base64urlToBytes();
|
|
341
|
+
|
|
342
|
+
// Test decode
|
|
343
|
+
const decoded = z.decode(codec, "SGVsbG8");
|
|
344
|
+
expect(decoded).toBeInstanceOf(Uint8Array);
|
|
345
|
+
expect(Array.from(decoded)).toEqual([72, 101, 108, 108, 111]);
|
|
346
|
+
|
|
347
|
+
// Test encode
|
|
348
|
+
const bytes = new Uint8Array([72, 101, 108, 108, 111]);
|
|
349
|
+
expect(z.encode(codec, bytes)).toBe("SGVsbG8");
|
|
350
|
+
|
|
351
|
+
// Test round trip with padding case
|
|
352
|
+
const original = "SGVsbG9Xb3JsZA";
|
|
353
|
+
const roundTrip = z.encode(codec, z.decode(codec, original));
|
|
354
|
+
expect(roundTrip).toBe(original);
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
test("hexToBytes codec", () => {
|
|
358
|
+
const codec = hexToBytes();
|
|
359
|
+
|
|
360
|
+
// Test decode
|
|
361
|
+
const decoded = z.decode(codec, "48656c6c6f");
|
|
362
|
+
expect(decoded).toBeInstanceOf(Uint8Array);
|
|
363
|
+
expect(Array.from(decoded)).toEqual([72, 101, 108, 108, 111]);
|
|
364
|
+
|
|
365
|
+
// Note: z.hex() doesn't accept 0x prefix, but our utility function can handle it
|
|
366
|
+
// const decodedWithPrefix = z.decode(codec, "0x48656c6c6f");
|
|
367
|
+
// expect(Array.from(decodedWithPrefix)).toEqual([72, 101, 108, 108, 111]);
|
|
368
|
+
|
|
369
|
+
// Test encode
|
|
370
|
+
const bytes = new Uint8Array([72, 101, 108, 108, 111]);
|
|
371
|
+
expect(z.encode(codec, bytes)).toBe("48656c6c6f");
|
|
372
|
+
|
|
373
|
+
// Test round trip
|
|
374
|
+
const original = "deadbeef";
|
|
375
|
+
const roundTrip = z.encode(codec, z.decode(codec, original));
|
|
376
|
+
expect(roundTrip).toBe("deadbeef");
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
test("stringToURL codec", () => {
|
|
380
|
+
const codec = stringToURL();
|
|
381
|
+
|
|
382
|
+
// Test decode
|
|
383
|
+
const decoded = z.decode(codec, "https://example.com/path?query=value");
|
|
384
|
+
expect(decoded).toBeInstanceOf(URL);
|
|
385
|
+
expect(decoded.hostname).toBe("example.com");
|
|
386
|
+
expect(decoded.pathname).toBe("/path");
|
|
387
|
+
expect(decoded.search).toBe("?query=value");
|
|
388
|
+
|
|
389
|
+
// Test encode
|
|
390
|
+
const url = new URL("https://example.com/path?query=value");
|
|
391
|
+
expect(z.encode(codec, url)).toBe("https://example.com/path?query=value");
|
|
392
|
+
|
|
393
|
+
// Test round trip
|
|
394
|
+
const original = "https://test.com/api/v1?foo=bar&baz=qux";
|
|
395
|
+
const roundTrip = z.encode(codec, z.decode(codec, original));
|
|
396
|
+
expect(roundTrip).toBe(original);
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
test("stringToHttpURL codec", () => {
|
|
400
|
+
const codec = stringToHttpURL();
|
|
401
|
+
|
|
402
|
+
// Test decode HTTPS
|
|
403
|
+
const decodedHttps = z.decode(codec, "https://example.com/path");
|
|
404
|
+
expect(decodedHttps).toBeInstanceOf(URL);
|
|
405
|
+
expect(decodedHttps.protocol).toBe("https:");
|
|
406
|
+
|
|
407
|
+
// Test decode HTTP
|
|
408
|
+
const decodedHttp = z.decode(codec, "http://example.com/path");
|
|
409
|
+
expect(decodedHttp).toBeInstanceOf(URL);
|
|
410
|
+
expect(decodedHttp.protocol).toBe("http:");
|
|
411
|
+
|
|
412
|
+
// Test encode
|
|
413
|
+
const url = new URL("https://example.com/path");
|
|
414
|
+
expect(z.encode(codec, url)).toBe("https://example.com/path");
|
|
415
|
+
|
|
416
|
+
// Test round trip
|
|
417
|
+
const original = "http://api.example.com/v1/users";
|
|
418
|
+
const roundTrip = z.encode(codec, z.decode(codec, original));
|
|
419
|
+
expect(roundTrip).toBe(original);
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
test("uriComponent codec", () => {
|
|
423
|
+
const codec = uriComponent();
|
|
424
|
+
|
|
425
|
+
// Test decode
|
|
426
|
+
const decoded = z.decode(codec, "Hello%20World%21");
|
|
427
|
+
expect(decoded).toBe("Hello World!");
|
|
428
|
+
|
|
429
|
+
// Test encode
|
|
430
|
+
const encoded = z.encode(codec, "Hello World!");
|
|
431
|
+
expect(encoded).toBe("Hello%20World!");
|
|
432
|
+
|
|
433
|
+
// Test round trip
|
|
434
|
+
const original = "Hello%20World%21%20%26%20More";
|
|
435
|
+
const roundTrip = z.encode(codec, z.decode(codec, original));
|
|
436
|
+
expect(roundTrip).toBe("Hello%20World!%20%26%20More");
|
|
437
|
+
|
|
438
|
+
// Test complex characters
|
|
439
|
+
const complex = "café & résumé";
|
|
440
|
+
const encodedComplex = z.encode(codec, complex);
|
|
441
|
+
const decodedComplex = z.decode(codec, encodedComplex);
|
|
442
|
+
expect(decodedComplex).toBe(complex);
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
test("stringToBoolean codec", () => {
|
|
446
|
+
const codec = stringToBoolean();
|
|
447
|
+
|
|
448
|
+
// Test decode - default truthy values
|
|
449
|
+
expect(z.decode(codec, "true")).toBe(true);
|
|
450
|
+
expect(z.decode(codec, "yes")).toBe(true);
|
|
451
|
+
expect(z.decode(codec, "1")).toBe(true);
|
|
452
|
+
|
|
453
|
+
// Test decode - default falsy values
|
|
454
|
+
expect(z.decode(codec, "false")).toBe(false);
|
|
455
|
+
expect(z.decode(codec, "no")).toBe(false);
|
|
456
|
+
expect(z.decode(codec, "0")).toBe(false);
|
|
457
|
+
|
|
458
|
+
// Test encode - default behavior
|
|
459
|
+
expect(z.encode(codec, true)).toBe("true");
|
|
460
|
+
expect(z.encode(codec, false)).toBe("false");
|
|
461
|
+
|
|
462
|
+
// Test custom options
|
|
463
|
+
const customCodec = stringToBoolean({ truthy: ["yes", "y"], falsy: ["no", "n"] });
|
|
464
|
+
expect(z.decode(customCodec, "yes")).toBe(true);
|
|
465
|
+
expect(z.decode(customCodec, "y")).toBe(true);
|
|
466
|
+
expect(z.decode(customCodec, "no")).toBe(false);
|
|
467
|
+
expect(z.decode(customCodec, "n")).toBe(false);
|
|
468
|
+
expect(z.encode(customCodec, true)).toBe("yes");
|
|
469
|
+
expect(z.encode(customCodec, false)).toBe("no");
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
// Test error cases - these test input validation, not transform errors
|
|
473
|
+
test("codec input validation", () => {
|
|
474
|
+
// Test invalid base64 format
|
|
475
|
+
const base64Codec = base64();
|
|
476
|
+
const invalidBase64Result = z.safeDecode(base64Codec, "invalid!@#");
|
|
477
|
+
expect(invalidBase64Result.success).toBe(false);
|
|
478
|
+
|
|
479
|
+
// Test invalid hex format
|
|
480
|
+
const hexCodec = hexToBytes();
|
|
481
|
+
const invalidHexResult = z.safeDecode(hexCodec, "gg");
|
|
482
|
+
expect(invalidHexResult.success).toBe(false);
|
|
483
|
+
|
|
484
|
+
// Test invalid URL format
|
|
485
|
+
const urlCodec = stringToURL();
|
|
486
|
+
const invalidUrlResult = z.safeDecode(urlCodec, "not a url");
|
|
487
|
+
expect(invalidUrlResult.success).toBe(false);
|
|
488
|
+
|
|
489
|
+
// Test invalid HTTP URL format
|
|
490
|
+
const httpUrlCodec = stringToHttpURL();
|
|
491
|
+
const invalidHttpResult = z.safeDecode(httpUrlCodec, "ftp://example.com");
|
|
492
|
+
expect(invalidHttpResult.success).toBe(false);
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
// Test transform errors - these test errors added by transform functions
|
|
496
|
+
test("codec transform error handling", () => {
|
|
497
|
+
// JSON codec that can fail during transform
|
|
498
|
+
const jsonCodec = z.codec(z.string(), z.json(), {
|
|
499
|
+
decode: (jsonString, ctx) => {
|
|
500
|
+
try {
|
|
501
|
+
return JSON.parse(jsonString);
|
|
502
|
+
} catch (err: any) {
|
|
503
|
+
ctx.issues.push({
|
|
504
|
+
code: "invalid_format",
|
|
505
|
+
format: "json",
|
|
506
|
+
input: jsonString,
|
|
507
|
+
message: err.message,
|
|
508
|
+
});
|
|
509
|
+
return z.NEVER;
|
|
510
|
+
}
|
|
511
|
+
},
|
|
512
|
+
encode: (value) => JSON.stringify(value),
|
|
513
|
+
});
|
|
514
|
+
|
|
515
|
+
// Test successful JSON parsing
|
|
516
|
+
const validResult = z.safeDecode(jsonCodec, '{"valid": "json"}');
|
|
517
|
+
expect(validResult.success).toBe(true);
|
|
518
|
+
if (validResult.success) {
|
|
519
|
+
expect(validResult.data).toEqual({ valid: "json" });
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
// Test invalid JSON that should create a single "invalid_format" issue
|
|
523
|
+
// Verifies that the transform error aborts before reaching the output schema
|
|
524
|
+
const invalidResult = z.safeDecode(jsonCodec, '{"invalid":,}');
|
|
525
|
+
expect(invalidResult.success).toBe(false);
|
|
526
|
+
if (!invalidResult.success) {
|
|
527
|
+
expect(invalidResult.error.issues).toMatchInlineSnapshot(`
|
|
528
|
+
[
|
|
529
|
+
{
|
|
530
|
+
"code": "invalid_format",
|
|
531
|
+
"format": "json",
|
|
532
|
+
"message": "Unexpected token ',', "{"invalid":,}" is not valid JSON",
|
|
533
|
+
"path": [],
|
|
534
|
+
},
|
|
535
|
+
]
|
|
536
|
+
`);
|
|
537
|
+
}
|
|
538
|
+
});
|