zod 4.6.3 → 4.6.5
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/package.json +1 -1
- package/src/v4/classic/schemas.ts +11 -5
- package/src/v4/classic/tests/assignability.test.ts +1 -0
- package/src/v4/classic/tests/instance-footprint.test.ts +22 -0
- package/src/v4/classic/tests/number.test.ts +39 -0
- package/src/v4/classic/tests/properties.test.ts +19 -0
- package/src/v4/classic/tests/string.test.ts +70 -1
- package/src/v4/classic/tests/to-json-schema.test.ts +87 -35
- package/src/v4/core/api.ts +17 -22
- package/src/v4/core/compile.ts +27 -6
- package/src/v4/core/core.ts +1 -3
- package/src/v4/core/json-schema-processors.ts +9 -13
- package/src/v4/core/regexes.ts +4 -0
- package/src/v4/core/schemas.ts +35 -11
- package/src/v4/core/tests/compile-differential.test.ts +35 -0
- package/src/v4/core/tests/compile.test.ts +177 -0
- package/src/v4/core/tests/url-no-canparse.test.ts +118 -16
- package/src/v4/core/versions.ts +1 -1
- package/src/v4/locales/ar.ts +1 -0
- package/src/v4/locales/az.ts +1 -0
- package/src/v4/locales/be.ts +1 -0
- package/src/v4/locales/bg.ts +1 -0
- package/src/v4/locales/bn.ts +1 -0
- package/src/v4/locales/ca.ts +1 -0
- package/src/v4/locales/ckb.ts +1 -0
- package/src/v4/locales/cs.ts +1 -0
- package/src/v4/locales/da.ts +1 -0
- package/src/v4/locales/de.ts +1 -0
- package/src/v4/locales/el.ts +1 -0
- package/src/v4/locales/en.ts +1 -0
- package/src/v4/locales/eo.ts +1 -0
- package/src/v4/locales/es.ts +1 -0
- package/src/v4/locales/fa.ts +1 -0
- package/src/v4/locales/fi.ts +1 -0
- package/src/v4/locales/fr-CA.ts +1 -0
- package/src/v4/locales/fr.ts +1 -0
- package/src/v4/locales/gu.ts +1 -0
- package/src/v4/locales/he.ts +1 -0
- package/src/v4/locales/hi.ts +1 -0
- package/src/v4/locales/hr.ts +1 -0
- package/src/v4/locales/hu.ts +1 -0
- package/src/v4/locales/hy.ts +1 -0
- package/src/v4/locales/id.ts +1 -0
- package/src/v4/locales/is.ts +1 -0
- package/src/v4/locales/it.ts +1 -0
- package/src/v4/locales/ja.ts +1 -0
- package/src/v4/locales/ka.ts +1 -0
- package/src/v4/locales/km.ts +1 -0
- package/src/v4/locales/kn.ts +1 -0
- package/src/v4/locales/ko.ts +1 -0
- package/src/v4/locales/lt.ts +1 -0
- package/src/v4/locales/mk.ts +1 -0
- package/src/v4/locales/ms.ts +1 -0
- package/src/v4/locales/ne.ts +1 -0
- package/src/v4/locales/nl.ts +1 -0
- package/src/v4/locales/nn.ts +1 -0
- package/src/v4/locales/no.ts +1 -0
- package/src/v4/locales/ota.ts +1 -0
- package/src/v4/locales/pl.ts +1 -0
- package/src/v4/locales/ps.ts +1 -0
- package/src/v4/locales/pt-BR.ts +1 -0
- package/src/v4/locales/pt.ts +1 -0
- package/src/v4/locales/ro.ts +1 -0
- package/src/v4/locales/ru.ts +1 -0
- package/src/v4/locales/sk.ts +1 -0
- package/src/v4/locales/sl.ts +1 -0
- package/src/v4/locales/sv.ts +1 -0
- package/src/v4/locales/ta.ts +1 -0
- package/src/v4/locales/tg.ts +1 -0
- package/src/v4/locales/th.ts +1 -0
- package/src/v4/locales/tk.ts +1 -0
- package/src/v4/locales/tr.ts +1 -0
- package/src/v4/locales/uk.ts +1 -0
- package/src/v4/locales/ur.ts +1 -0
- package/src/v4/locales/uz.ts +1 -0
- package/src/v4/locales/vi.ts +1 -0
- package/src/v4/locales/yo.ts +1 -0
- package/src/v4/locales/zh-CN.ts +1 -0
- package/src/v4/locales/zh-TW.ts +1 -0
- package/src/v4/mini/schemas.ts +12 -4
- package/src/v4/mini/tests/index.test.ts +56 -0
- package/src/v4/mini/tests/string.test.ts +7 -0
- package/v4/classic/schemas.cjs +9 -4
- package/v4/classic/schemas.d.cts +4 -1
- package/v4/classic/schemas.d.ts +4 -1
- package/v4/classic/schemas.js +8 -4
- package/v4/core/api.cjs +9 -20
- package/v4/core/api.d.cts +6 -2
- package/v4/core/api.d.ts +6 -2
- package/v4/core/api.js +9 -20
- package/v4/core/compile.cjs +7 -6
- package/v4/core/compile.js +8 -7
- package/v4/core/core.cjs +1 -1
- package/v4/core/core.js +1 -1
- package/v4/core/json-schema-processors.cjs +7 -10
- package/v4/core/json-schema-processors.js +7 -10
- package/v4/core/regexes.cjs +4 -2
- package/v4/core/regexes.d.cts +1 -0
- package/v4/core/regexes.d.ts +1 -0
- package/v4/core/regexes.js +2 -0
- package/v4/core/schemas.cjs +32 -11
- package/v4/core/schemas.d.cts +5 -3
- package/v4/core/schemas.d.ts +5 -3
- package/v4/core/schemas.js +30 -11
- package/v4/core/versions.cjs +1 -1
- package/v4/core/versions.js +1 -1
- package/v4/locales/ar.cjs +1 -0
- package/v4/locales/ar.js +1 -0
- package/v4/locales/az.cjs +1 -0
- package/v4/locales/az.js +1 -0
- package/v4/locales/be.cjs +1 -0
- package/v4/locales/be.js +1 -0
- package/v4/locales/bg.cjs +1 -0
- package/v4/locales/bg.js +1 -0
- package/v4/locales/bn.cjs +1 -0
- package/v4/locales/bn.js +1 -0
- package/v4/locales/ca.cjs +1 -0
- package/v4/locales/ca.js +1 -0
- package/v4/locales/ckb.cjs +1 -0
- package/v4/locales/ckb.js +1 -0
- package/v4/locales/cs.cjs +1 -0
- package/v4/locales/cs.js +1 -0
- package/v4/locales/da.cjs +1 -0
- package/v4/locales/da.js +1 -0
- package/v4/locales/de.cjs +1 -0
- package/v4/locales/de.js +1 -0
- package/v4/locales/el.cjs +1 -0
- package/v4/locales/el.js +1 -0
- package/v4/locales/en.cjs +1 -0
- package/v4/locales/en.js +1 -0
- package/v4/locales/eo.cjs +1 -0
- package/v4/locales/eo.js +1 -0
- package/v4/locales/es.cjs +1 -0
- package/v4/locales/es.js +1 -0
- package/v4/locales/fa.cjs +1 -0
- package/v4/locales/fa.js +1 -0
- package/v4/locales/fi.cjs +1 -0
- package/v4/locales/fi.js +1 -0
- package/v4/locales/fr-CA.cjs +1 -0
- package/v4/locales/fr-CA.js +1 -0
- package/v4/locales/fr.cjs +1 -0
- package/v4/locales/fr.js +1 -0
- package/v4/locales/gu.cjs +1 -0
- package/v4/locales/gu.js +1 -0
- package/v4/locales/he.cjs +1 -0
- package/v4/locales/he.js +1 -0
- package/v4/locales/hi.cjs +1 -0
- package/v4/locales/hi.js +1 -0
- package/v4/locales/hr.cjs +1 -0
- package/v4/locales/hr.js +1 -0
- package/v4/locales/hu.cjs +1 -0
- package/v4/locales/hu.js +1 -0
- package/v4/locales/hy.cjs +1 -0
- package/v4/locales/hy.js +1 -0
- package/v4/locales/id.cjs +1 -0
- package/v4/locales/id.js +1 -0
- package/v4/locales/is.cjs +1 -0
- package/v4/locales/is.js +1 -0
- package/v4/locales/it.cjs +1 -0
- package/v4/locales/it.js +1 -0
- package/v4/locales/ja.cjs +1 -0
- package/v4/locales/ja.js +1 -0
- package/v4/locales/ka.cjs +1 -0
- package/v4/locales/ka.js +1 -0
- package/v4/locales/km.cjs +1 -0
- package/v4/locales/km.js +1 -0
- package/v4/locales/kn.cjs +1 -0
- package/v4/locales/kn.js +1 -0
- package/v4/locales/ko.cjs +1 -0
- package/v4/locales/ko.js +1 -0
- package/v4/locales/lt.cjs +1 -0
- package/v4/locales/lt.js +1 -0
- package/v4/locales/mk.cjs +1 -0
- package/v4/locales/mk.js +1 -0
- package/v4/locales/ms.cjs +1 -0
- package/v4/locales/ms.js +1 -0
- package/v4/locales/ne.cjs +1 -0
- package/v4/locales/ne.js +1 -0
- package/v4/locales/nl.cjs +1 -0
- package/v4/locales/nl.js +1 -0
- package/v4/locales/nn.cjs +1 -0
- package/v4/locales/nn.js +1 -0
- package/v4/locales/no.cjs +1 -0
- package/v4/locales/no.js +1 -0
- package/v4/locales/ota.cjs +1 -0
- package/v4/locales/ota.js +1 -0
- package/v4/locales/pl.cjs +1 -0
- package/v4/locales/pl.js +1 -0
- package/v4/locales/ps.cjs +1 -0
- package/v4/locales/ps.js +1 -0
- package/v4/locales/pt-BR.cjs +1 -0
- package/v4/locales/pt-BR.js +1 -0
- package/v4/locales/pt.cjs +1 -0
- package/v4/locales/pt.js +1 -0
- package/v4/locales/ro.cjs +1 -0
- package/v4/locales/ro.js +1 -0
- package/v4/locales/ru.cjs +1 -0
- package/v4/locales/ru.js +1 -0
- package/v4/locales/sk.cjs +1 -0
- package/v4/locales/sk.js +1 -0
- package/v4/locales/sl.cjs +1 -0
- package/v4/locales/sl.js +1 -0
- package/v4/locales/sv.cjs +1 -0
- package/v4/locales/sv.js +1 -0
- package/v4/locales/ta.cjs +1 -0
- package/v4/locales/ta.js +1 -0
- package/v4/locales/tg.cjs +1 -0
- package/v4/locales/tg.js +1 -0
- package/v4/locales/th.cjs +1 -0
- package/v4/locales/th.js +1 -0
- package/v4/locales/tk.cjs +1 -0
- package/v4/locales/tk.js +1 -0
- package/v4/locales/tr.cjs +1 -0
- package/v4/locales/tr.js +1 -0
- package/v4/locales/uk.cjs +1 -0
- package/v4/locales/uk.js +1 -0
- package/v4/locales/ur.cjs +1 -0
- package/v4/locales/ur.js +1 -0
- package/v4/locales/uz.cjs +1 -0
- package/v4/locales/uz.js +1 -0
- package/v4/locales/vi.cjs +1 -0
- package/v4/locales/vi.js +1 -0
- package/v4/locales/yo.cjs +1 -0
- package/v4/locales/yo.js +1 -0
- package/v4/locales/zh-CN.cjs +1 -0
- package/v4/locales/zh-CN.js +1 -0
- package/v4/locales/zh-TW.cjs +1 -0
- package/v4/locales/zh-TW.js +1 -0
- package/v4/mini/schemas.cjs +10 -4
- package/v4/mini/schemas.d.cts +1 -0
- package/v4/mini/schemas.d.ts +1 -0
- package/v4/mini/schemas.js +9 -4
package/package.json
CHANGED
|
@@ -2,6 +2,7 @@ import type { $ZodBigIntFormats } from "../core/checks.js";
|
|
|
2
2
|
import * as core from "../core/index.js";
|
|
3
3
|
import { util, type $ZodNumberFormats } from "../core/index.js";
|
|
4
4
|
import * as processors from "../core/json-schema-processors.js";
|
|
5
|
+
import * as regexes from "../core/regexes.js";
|
|
5
6
|
import type { StandardSchemaWithJSONProps } from "../core/standard-schema.js";
|
|
6
7
|
import { createStandardJSONSchemaMethod, createToJSONSchemaMethod } from "../core/to-json-schema.js";
|
|
7
8
|
import en from "../locales/en.js";
|
|
@@ -770,8 +771,8 @@ export function url(params?: string | core.$ZodURLParams): ZodURL {
|
|
|
770
771
|
|
|
771
772
|
export function httpUrl(params?: string | Omit<core.$ZodURLParams, "protocol" | "hostname">): ZodURL {
|
|
772
773
|
return core._url(ZodURL, {
|
|
773
|
-
protocol:
|
|
774
|
-
hostname:
|
|
774
|
+
protocol: regexes.httpProtocol,
|
|
775
|
+
hostname: regexes.domain,
|
|
775
776
|
...util.normalizeParams(params),
|
|
776
777
|
});
|
|
777
778
|
}
|
|
@@ -1081,11 +1082,15 @@ export function stringFormat<Format extends string>(
|
|
|
1081
1082
|
}
|
|
1082
1083
|
|
|
1083
1084
|
export function hostname(_params?: string | core.$ZodStringFormatParams): ZodCustomStringFormat<"hostname"> {
|
|
1084
|
-
return core._stringFormat(ZodCustomStringFormat, "hostname",
|
|
1085
|
+
return core._stringFormat(ZodCustomStringFormat, "hostname", regexes.hostname, _params) as any;
|
|
1085
1086
|
}
|
|
1086
1087
|
|
|
1087
1088
|
export function hex(_params?: string | core.$ZodStringFormatParams): ZodCustomStringFormat<"hex"> {
|
|
1088
|
-
return core._stringFormat(ZodCustomStringFormat, "hex",
|
|
1089
|
+
return core._stringFormat(ZodCustomStringFormat, "hex", regexes.hex, _params) as any;
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
export function currencyCode(_params?: string | core.$ZodStringFormatParams): ZodCustomStringFormat<"currency_code"> {
|
|
1093
|
+
return core._stringFormat(ZodCustomStringFormat, "currency_code", regexes.currencyCode, _params) as any;
|
|
1089
1094
|
}
|
|
1090
1095
|
|
|
1091
1096
|
export function hash<Alg extends util.HashAlgorithm, Enc extends util.HashEncoding = "hex">(
|
|
@@ -2836,7 +2841,8 @@ type ZodInstanceOfParams = core.Params<
|
|
|
2836
2841
|
|
|
2837
2842
|
// ZodInstanceOf
|
|
2838
2843
|
export interface ZodInstanceOf<T = unknown> extends ZodCustom<T, T> {
|
|
2839
|
-
|
|
2844
|
+
// the shape is keyed off the instance type, so keys autocomplete and a schema that can't accept the property's type is an error. Methods are excluded: every object literal inherits Object.prototype.toString, which would otherwise collide with the constraint's own toString entry and reject every shape.
|
|
2845
|
+
properties<Shape extends { [k in keyof T as T[k] extends Function ? never : k]?: core.$ZodType<unknown, T[k]> }>(
|
|
2840
2846
|
shape: Shape,
|
|
2841
2847
|
params?: string | core.$ZodCheckPropertiesParams
|
|
2842
2848
|
): ZodInstanceOf<T & core.$InferObjectInput<Shape, {}>>;
|
|
@@ -352,6 +352,7 @@ test("checks", () => {
|
|
|
352
352
|
const _b: z.core.$ZodCheck = {} as any as z.core.$ZodStringFormatChecks;
|
|
353
353
|
const _c: z.core.$ZodType = {} as any as z.core.$ZodTypes;
|
|
354
354
|
const _d: z.core.$ZodType = {} as any as z.core.$ZodStringFormatTypes;
|
|
355
|
+
z.currencyCode() satisfies z.core.$ZodStringFormatTypes;
|
|
355
356
|
});
|
|
356
357
|
|
|
357
358
|
test("assignability to $ZodType", () => {
|
|
@@ -105,6 +105,28 @@ test("deferred initializers are released after construction", () => {
|
|
|
105
105
|
expect(z.object({ a: z.string() })._zod.deferred).toEqual(undefined);
|
|
106
106
|
});
|
|
107
107
|
|
|
108
|
+
test("trait initializers run once per instance across repeated entry", () => {
|
|
109
|
+
const calls: string[] = [];
|
|
110
|
+
const Base: core.$constructor<any> = core.$constructor<any>("CountedBase", () => calls.push("base"));
|
|
111
|
+
const Child: core.$constructor<any> = core.$constructor<any>("CountedChild", (inst, def) => {
|
|
112
|
+
calls.push("child");
|
|
113
|
+
Base.init(inst, def);
|
|
114
|
+
Base.init(inst, def);
|
|
115
|
+
});
|
|
116
|
+
const constructed = new Child({});
|
|
117
|
+
Child.init(constructed, {});
|
|
118
|
+
const direct = Object.create({});
|
|
119
|
+
Child.init(direct, {});
|
|
120
|
+
Child.init(direct, {});
|
|
121
|
+
expect(calls).toEqual(["child", "base", "child", "base"]);
|
|
122
|
+
for (const inst of [constructed, direct]) {
|
|
123
|
+
expect([...inst._zod.traits]).toEqual(["CountedChild", "CountedBase"]);
|
|
124
|
+
expect(inst instanceof Base).toBe(true);
|
|
125
|
+
expect(inst instanceof Child).toBe(true);
|
|
126
|
+
}
|
|
127
|
+
expect(constructed._zod.traits).not.toBe(direct._zod.traits);
|
|
128
|
+
});
|
|
129
|
+
|
|
108
130
|
test("a trait initializer called directly still installs its members", () => {
|
|
109
131
|
// a direct `init` installs onto the receiver's own prototype, since it is not below the constructor's
|
|
110
132
|
z.string();
|
|
@@ -2,6 +2,45 @@ import { expect, expectTypeOf, test } from "vitest";
|
|
|
2
2
|
|
|
3
3
|
import * as z from "zod/v4";
|
|
4
4
|
|
|
5
|
+
test("number factory checks", () => {
|
|
6
|
+
const schema = z.number({ checks: [z.gte(1), z.lte(3)] as const });
|
|
7
|
+
const chained = z.number().min(1).max(3);
|
|
8
|
+
expectTypeOf<z.output<typeof schema>>().toEqualTypeOf<number>();
|
|
9
|
+
for (const value of [0, 1, 3, 4, Number.NaN, "2", undefined]) {
|
|
10
|
+
expect(z.validate(schema, value)).toBe(z.validate(chained, value));
|
|
11
|
+
expect(schema.safeParse(value)).toEqual(chained.safeParse(value));
|
|
12
|
+
}
|
|
13
|
+
expect(z.toJSONSchema(schema)).toEqual(z.toJSONSchema(chained));
|
|
14
|
+
expect(schema.minValue).toBe(1);
|
|
15
|
+
expect(schema.maxValue).toBe(3);
|
|
16
|
+
expect(z.coerce.number({ checks: [z.gte(1)] }).parse("2")).toBe(2);
|
|
17
|
+
const overwritten = z.number({ checks: [z.overwrite<number>((value) => value + 1), z.gte(2)] });
|
|
18
|
+
expect(overwritten.parse(1)).toBe(2);
|
|
19
|
+
expect(z.validate(overwritten, 0)).toBe(false);
|
|
20
|
+
// @ts-expect-error length checks cannot validate numbers
|
|
21
|
+
z.number({ checks: [z.minLength(1)] });
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test("number factory checks snapshot caller arrays", () => {
|
|
25
|
+
for (const factory of [z.number, z.coerce.number]) {
|
|
26
|
+
const checks = [z.gte(1)];
|
|
27
|
+
const schema = factory({ checks });
|
|
28
|
+
let reads = 0;
|
|
29
|
+
factory({
|
|
30
|
+
get checks() {
|
|
31
|
+
if (++reads > 1) throw new Error("checks read twice");
|
|
32
|
+
return checks;
|
|
33
|
+
},
|
|
34
|
+
});
|
|
35
|
+
expect(reads).toBe(1);
|
|
36
|
+
const before = z.toJSONSchema(schema);
|
|
37
|
+
checks.push(z.gte(10));
|
|
38
|
+
expect(z.validate(schema, 3)).toBe(true);
|
|
39
|
+
expect(z.validate(z.compile(schema), 3)).toBe(true);
|
|
40
|
+
expect(z.toJSONSchema(schema)).toEqual(before);
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
|
|
5
44
|
test("z.number() basic validation", () => {
|
|
6
45
|
const schema = z.number();
|
|
7
46
|
expect(schema.parse(1234)).toEqual(1234);
|
|
@@ -51,6 +51,25 @@ test("z.instanceof().properties()", () => {
|
|
|
51
51
|
expectTypeOf<z.infer<typeof W>>().toEqualTypeOf<URL & { search: string }>();
|
|
52
52
|
});
|
|
53
53
|
|
|
54
|
+
test("z.instanceof().properties() constrains the shape to the instance type", () => {
|
|
55
|
+
const file = z.instanceof(File).properties({ name: z.string(), size: z.number().max(1024) });
|
|
56
|
+
expectTypeOf<z.infer<typeof file>>().toEqualTypeOf<File & { name: string; size: number }>();
|
|
57
|
+
|
|
58
|
+
// @ts-expect-error no such property on File
|
|
59
|
+
z.instanceof(File).properties({ nmae: z.string() });
|
|
60
|
+
|
|
61
|
+
// @ts-expect-error File["size"] is a number
|
|
62
|
+
z.instanceof(File).properties({ size: z.string() });
|
|
63
|
+
|
|
64
|
+
// methods are out of the shape, so an object literal's inherited toString can't collide with the constraint
|
|
65
|
+
// @ts-expect-error
|
|
66
|
+
z.instanceof(File).properties({ text: z.any() });
|
|
67
|
+
|
|
68
|
+
// a literal still narrows a wider property
|
|
69
|
+
const png = z.instanceof(File).properties({ type: z.literal("image/png") });
|
|
70
|
+
expectTypeOf<z.infer<typeof png>>().toEqualTypeOf<File & { type: "image/png" }>();
|
|
71
|
+
});
|
|
72
|
+
|
|
54
73
|
test("z.properties spreads into .check()", () => {
|
|
55
74
|
// the 4.5 array call sites: the check yields itself from Symbol.iterator
|
|
56
75
|
const p = z.properties({ a: z.literal("x") });
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { randomBytes } from "node:crypto";
|
|
2
|
-
import { expect, test } from "vitest";
|
|
2
|
+
import { expect, expectTypeOf, test } from "vitest";
|
|
3
3
|
|
|
4
4
|
import * as z from "zod/v4";
|
|
5
5
|
|
|
@@ -12,6 +12,63 @@ const includesFromIndex2 = z.string().includes("includes", { position: 2 });
|
|
|
12
12
|
const startsWith = z.string().startsWith("startsWith");
|
|
13
13
|
const endsWith = z.string().endsWith("endsWith");
|
|
14
14
|
|
|
15
|
+
test("string factory checks", async () => {
|
|
16
|
+
const schema = z.string({ checks: [z.minLength(1), z.maxLength(3)] as const });
|
|
17
|
+
const chained = z.string().min(1).max(3);
|
|
18
|
+
expectTypeOf<z.output<typeof schema>>().toEqualTypeOf<string>();
|
|
19
|
+
for (const value of ["", "ab", "abcd", 1, undefined]) {
|
|
20
|
+
expect(z.validate(schema, value)).toBe(z.validate(chained, value));
|
|
21
|
+
expect(schema.safeParse(value)).toEqual(chained.safeParse(value));
|
|
22
|
+
}
|
|
23
|
+
expect(z.toJSONSchema(schema)).toEqual(z.toJSONSchema(chained));
|
|
24
|
+
expect(schema.minLength).toBe(1);
|
|
25
|
+
expect(schema.maxLength).toBe(3);
|
|
26
|
+
expect(schema._zod.parent).toBeUndefined();
|
|
27
|
+
expect([...schema._zod.traits]).toEqual([...chained._zod.traits]);
|
|
28
|
+
const extended = schema.max(2);
|
|
29
|
+
expect(extended._zod.parent).toBe(schema);
|
|
30
|
+
expect(z.validate(schema, "abc")).toBe(true);
|
|
31
|
+
expect(z.validate(extended, "abc")).toBe(false);
|
|
32
|
+
const coerce = z.coerce.string({ checks: [z.minLength(2)] });
|
|
33
|
+
expect(coerce.parse(12)).toBe("12");
|
|
34
|
+
expect(z.validate(coerce, 1)).toBe(false);
|
|
35
|
+
const asyncSchema = z.string({
|
|
36
|
+
checks: [
|
|
37
|
+
z.check<string>(async (ctx) => {
|
|
38
|
+
if (ctx.value !== "ok") ctx.issues.push({ code: "custom", input: ctx.value });
|
|
39
|
+
}),
|
|
40
|
+
],
|
|
41
|
+
});
|
|
42
|
+
expect(await asyncSchema.safeParseAsync("ok")).toEqual({ success: true, data: "ok" });
|
|
43
|
+
expect((await asyncSchema.safeParseAsync("bad")).success).toBe(false);
|
|
44
|
+
// @ts-expect-error numeric checks cannot validate strings
|
|
45
|
+
z.string({ checks: [z.gte(1)] });
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test("factory checks snapshot caller arrays", () => {
|
|
49
|
+
for (const factory of [z.string, z.coerce.string]) {
|
|
50
|
+
for (const initial of [[], [z.minLength(1)]]) {
|
|
51
|
+
const schema = factory({ checks: initial });
|
|
52
|
+
let reads = 0;
|
|
53
|
+
const getterSchema = factory({
|
|
54
|
+
get checks() {
|
|
55
|
+
if (++reads > 1) throw new Error("checks read twice");
|
|
56
|
+
return initial;
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
expect(reads).toBe(1);
|
|
60
|
+
expect(z.validate(getterSchema, "long")).toBe(true);
|
|
61
|
+
const before = z.toJSONSchema(schema);
|
|
62
|
+
initial.push(z.minLength(10));
|
|
63
|
+
expect(schema._zod.def.checks).not.toBe(initial);
|
|
64
|
+
expect(z.validate(schema, "long")).toBe(true);
|
|
65
|
+
expect(z.validate(z.compile(schema), "long")).toBe(true);
|
|
66
|
+
expect(z.toJSONSchema(schema)).toEqual(before);
|
|
67
|
+
expect(z.validate(schema.check(z.minLength(2)), "long")).toBe(true);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
|
|
15
72
|
test("length checks", () => {
|
|
16
73
|
minFive.parse("12345");
|
|
17
74
|
minFive.parse("123456");
|
|
@@ -1289,6 +1346,18 @@ test("hostname", () => {
|
|
|
1289
1346
|
expect(() => hostname.parse("example..com")).toThrow();
|
|
1290
1347
|
});
|
|
1291
1348
|
|
|
1349
|
+
test("currency code", () => {
|
|
1350
|
+
const currency = z.currencyCode();
|
|
1351
|
+
for (const code of ["USD", "EUR", "XXX", "XCG", "XAD"]) expect(currency.parse(code)).toBe(code);
|
|
1352
|
+
for (const input of ["", "usd", "US", "USDX", " USD", "ANG", "BGN"])
|
|
1353
|
+
expect(currency.safeParse(input).success).toBe(false);
|
|
1354
|
+
expect(currency.safeParse("usd").error!.issues[0]).toMatchObject({
|
|
1355
|
+
code: "invalid_format",
|
|
1356
|
+
format: "currency_code",
|
|
1357
|
+
message: "Invalid currency code",
|
|
1358
|
+
});
|
|
1359
|
+
});
|
|
1360
|
+
|
|
1292
1361
|
test("hash validation", () => {
|
|
1293
1362
|
// MD5 tests
|
|
1294
1363
|
const md5hex = z.hash("md5");
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Validator } from "@seriousme/openapi-schema-validator";
|
|
2
|
-
import { describe, expect, test } from "vitest";
|
|
2
|
+
import { describe, expect, test, vi } from "vitest";
|
|
3
3
|
import * as z from "zod";
|
|
4
4
|
// import * as zCore from "zod/v4/core";
|
|
5
5
|
|
|
@@ -3235,11 +3235,31 @@ test("basic registry", () => {
|
|
|
3235
3235
|
`);
|
|
3236
3236
|
});
|
|
3237
3237
|
|
|
3238
|
-
|
|
3239
|
-
const
|
|
3240
|
-
const
|
|
3238
|
+
function countMapScanWork<T>(convert: () => T): { result: T; work: number } {
|
|
3239
|
+
const entries = Map.prototype.entries;
|
|
3240
|
+
const values = Map.prototype.values;
|
|
3241
|
+
let work = 0;
|
|
3242
|
+
const entriesSpy = vi.spyOn(Map.prototype, "entries").mockImplementation(function (this: Map<unknown, unknown>) {
|
|
3243
|
+
work += this.size;
|
|
3244
|
+
return entries.call(this);
|
|
3245
|
+
} as typeof Map.prototype.entries);
|
|
3246
|
+
const valuesSpy = vi.spyOn(Map.prototype, "values").mockImplementation(function (this: Map<unknown, unknown>) {
|
|
3247
|
+
work += this.size;
|
|
3248
|
+
return values.call(this);
|
|
3249
|
+
} as typeof Map.prototype.values);
|
|
3250
|
+
try {
|
|
3251
|
+
return { result: convert(), work };
|
|
3252
|
+
} finally {
|
|
3253
|
+
entriesSpy.mockRestore();
|
|
3254
|
+
valuesSpy.mockRestore();
|
|
3255
|
+
}
|
|
3256
|
+
}
|
|
3257
|
+
|
|
3258
|
+
test("large registry conversion performs linear map-scan work", () => {
|
|
3259
|
+
const count = 64;
|
|
3260
|
+
const convert = (size: number, withIntersection: boolean) => {
|
|
3241
3261
|
const registry = z.registry<{ id: string }>();
|
|
3242
|
-
for (let i = 0; i <
|
|
3262
|
+
for (let i = 0; i < size; i++) {
|
|
3243
3263
|
registry.add(
|
|
3244
3264
|
z.object({ id: z.string(), name: z.string(), count: z.number(), nested: z.object({ a: z.boolean() }) }),
|
|
3245
3265
|
{ id: `Type${i}` }
|
|
@@ -3247,55 +3267,53 @@ test("large registry converts in linear time", () => {
|
|
|
3247
3267
|
}
|
|
3248
3268
|
if (withIntersection) registry.add(z.object({ a: z.string() }).and(z.object({ b: z.string() })), { id: "Inter" });
|
|
3249
3269
|
|
|
3250
|
-
|
|
3251
|
-
const { schemas } = z.toJSONSchema(registry, { uri: (id) => `https://example.com/${id}.json` });
|
|
3252
|
-
return { schemas, elapsed: performance.now() - start };
|
|
3270
|
+
return countMapScanWork(() => z.toJSONSchema(registry, { uri: (id) => `https://example.com/${id}.json` }));
|
|
3253
3271
|
};
|
|
3254
3272
|
|
|
3255
|
-
const plain = convert(false);
|
|
3256
|
-
|
|
3257
|
-
expect(plain.schemas
|
|
3273
|
+
const plain = convert(count, false);
|
|
3274
|
+
const plainDouble = convert(count * 2, false);
|
|
3275
|
+
expect(Object.keys(plain.result.schemas)).toHaveLength(count);
|
|
3276
|
+
expect(plain.result.schemas.Type0).toMatchObject({
|
|
3258
3277
|
$id: "https://example.com/Type0.json",
|
|
3259
3278
|
type: "object",
|
|
3260
3279
|
properties: { nested: { type: "object" } },
|
|
3261
3280
|
});
|
|
3262
|
-
expect(plain.schemas[`Type${count - 1}`]!.$id).toBe(`https://example.com/Type${count - 1}.json`);
|
|
3263
|
-
|
|
3264
|
-
|
|
3265
|
-
expect(plain.elapsed).toBeLessThan(5000);
|
|
3281
|
+
expect(plain.result.schemas[`Type${count - 1}`]!.$id).toBe(`https://example.com/Type${count - 1}.json`);
|
|
3282
|
+
expect(plain.work).toBeGreaterThan(0);
|
|
3283
|
+
expect(plainDouble.work).toBeLessThanOrEqual(plain.work * 2);
|
|
3266
3284
|
|
|
3267
|
-
|
|
3268
|
-
const
|
|
3269
|
-
expect(folded.schemas.Inter).toMatchObject({ type: "object", properties: { a: {}, b: {} } });
|
|
3270
|
-
expect(folded.
|
|
3285
|
+
const folded = convert(count, true);
|
|
3286
|
+
const foldedDouble = convert(count * 2, true);
|
|
3287
|
+
expect(folded.result.schemas.Inter).toMatchObject({ type: "object", properties: { a: {}, b: {} } });
|
|
3288
|
+
expect(folded.work).toBeGreaterThan(0);
|
|
3289
|
+
expect(foldedDouble.work).toBeLessThanOrEqual(folded.work * 2);
|
|
3271
3290
|
});
|
|
3272
3291
|
|
|
3273
|
-
test("a registry of records with numeric keys
|
|
3274
|
-
const count =
|
|
3275
|
-
const convert = (key: (i: number) => z.core.$ZodType) => {
|
|
3292
|
+
test("a registry of records with numeric keys performs linear map-scan work", () => {
|
|
3293
|
+
const count = 64;
|
|
3294
|
+
const convert = (size: number, key: (i: number) => z.core.$ZodType) => {
|
|
3276
3295
|
const registry = z.registry<{ id: string }>();
|
|
3277
|
-
for (let i = 0; i <
|
|
3296
|
+
for (let i = 0; i < size; i++) {
|
|
3278
3297
|
registry.add(z.object({ m: z.record(key(i) as z.core.$ZodRecordKey, z.boolean()) }), { id: `Type${i}` });
|
|
3279
3298
|
}
|
|
3280
|
-
|
|
3281
|
-
const { schemas } = z.toJSONSchema(registry, { uri: (id) => `https://example.com/${id}.json` });
|
|
3282
|
-
return { schemas, elapsed: performance.now() - start };
|
|
3299
|
+
return countMapScanWork(() => z.toJSONSchema(registry, { uri: (id) => `https://example.com/${id}.json` }));
|
|
3283
3300
|
};
|
|
3284
3301
|
|
|
3285
|
-
const
|
|
3286
|
-
const
|
|
3287
|
-
expect(numeric.schemas.Type0).toMatchObject({
|
|
3302
|
+
const numeric = convert(count, () => z.number());
|
|
3303
|
+
const numericDouble = convert(count * 2, () => z.number());
|
|
3304
|
+
expect(numeric.result.schemas.Type0).toMatchObject({
|
|
3288
3305
|
properties: { m: { propertyNames: { type: "string", pattern: "^-?\\d+(?:\\.\\d+)?$" } } },
|
|
3289
3306
|
});
|
|
3307
|
+
expect(numeric.work).toBeGreaterThan(0);
|
|
3308
|
+
expect(numericDouble.work).toBeLessThanOrEqual(numeric.work * 2);
|
|
3290
3309
|
|
|
3291
|
-
|
|
3292
|
-
|
|
3293
|
-
|
|
3294
|
-
// An extracted key resolves through a map built once per conversion rather than a search per reference. There is no stable timing control for that — extraction has its own $defs cost, which swamps the difference — so this only pins the shape.
|
|
3295
|
-
const extracted = convert((i) => z.number().meta({ id: `Key${i}` }));
|
|
3296
|
-
expect(extracted.schemas.Type0).toMatchObject({
|
|
3310
|
+
const extracted = convert(count, (i) => z.number().meta({ id: `Key${i}` }));
|
|
3311
|
+
const extractedDouble = convert(count * 2, (i) => z.number().meta({ id: `Key${i}` }));
|
|
3312
|
+
expect(extracted.result.schemas.Type0).toMatchObject({
|
|
3297
3313
|
properties: { m: { propertyNames: { type: "string", pattern: "^-?\\d+(?:\\.\\d+)?$" } } },
|
|
3298
3314
|
});
|
|
3315
|
+
expect(extracted.work).toBeGreaterThan(0);
|
|
3316
|
+
expect(extractedDouble.work).toBeLessThanOrEqual(extracted.work * 2);
|
|
3299
3317
|
});
|
|
3300
3318
|
|
|
3301
3319
|
test("registry extracts unregistered subschemas into __shared", () => {
|
|
@@ -3849,6 +3867,31 @@ test("input JSON schema resolves requiredness past transform and catch wrappers"
|
|
|
3849
3867
|
expect(required(z.object({ a: z.transform((v: unknown) => String(v)).pipe(z.string()) }))).toEqual(["a"]);
|
|
3850
3868
|
});
|
|
3851
3869
|
|
|
3870
|
+
test("object required keys preserve shape order and direction", () => {
|
|
3871
|
+
const symbol = Symbol("symbol");
|
|
3872
|
+
const shape: z.ZodRawShape = {
|
|
3873
|
+
10: z.string(),
|
|
3874
|
+
2: z.string().optional(),
|
|
3875
|
+
plain: z.string(),
|
|
3876
|
+
optional: z.string().optional(),
|
|
3877
|
+
defaulted: z.string().default("x"),
|
|
3878
|
+
caught: z.string().catch("x"),
|
|
3879
|
+
[symbol]: z.string(),
|
|
3880
|
+
};
|
|
3881
|
+
Object.defineProperty(shape, "__proto__", { value: z.string(), enumerable: true });
|
|
3882
|
+
Object.defineProperty(shape, "hidden", { value: z.string(), enumerable: false });
|
|
3883
|
+
|
|
3884
|
+
const input = z.toJSONSchema(z.object(shape), { io: "input", unrepresentable: "any" });
|
|
3885
|
+
const output = z.toJSONSchema(z.object(shape), { io: "output", unrepresentable: "any" });
|
|
3886
|
+
|
|
3887
|
+
expect(Object.keys(input.properties!)).toEqual(["2", "10", "plain", "optional", "defaulted", "caught", "__proto__"]);
|
|
3888
|
+
expect(input.required).toEqual(["10", "plain", "caught", "__proto__"]);
|
|
3889
|
+
expect(output.required).toEqual(["10", "plain", "defaulted", "caught", "__proto__"]);
|
|
3890
|
+
expect(Object.prototype.hasOwnProperty.call(input.properties!, "__proto__")).toBe(true);
|
|
3891
|
+
expect(input.properties).not.toHaveProperty("hidden");
|
|
3892
|
+
expect(Object.getOwnPropertySymbols(input.properties!)).toEqual([]);
|
|
3893
|
+
});
|
|
3894
|
+
|
|
3852
3895
|
test("strip output-side examples from input JSON schema for codec", () => {
|
|
3853
3896
|
const codec = z
|
|
3854
3897
|
.codec(z.string(), z.number(), { decode: (s) => Number(s), encode: (n) => String(n) })
|
|
@@ -4679,3 +4722,12 @@ describe("intersection folding declines", () => {
|
|
|
4679
4722
|
expect(allOf(schema)).toHaveLength(3);
|
|
4680
4723
|
});
|
|
4681
4724
|
});
|
|
4725
|
+
|
|
4726
|
+
test("currency code", () => {
|
|
4727
|
+
expect(z.toJSONSchema(z.currencyCode())).toEqual({
|
|
4728
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
4729
|
+
type: "string",
|
|
4730
|
+
format: "currency_code",
|
|
4731
|
+
pattern: z.regexes.currencyCode.source,
|
|
4732
|
+
});
|
|
4733
|
+
});
|
package/src/v4/core/api.ts
CHANGED
|
@@ -58,16 +58,20 @@ export type CheckTypeParams<
|
|
|
58
58
|
> = Params<T, NonNullable<T["_zod"]["isst"] | T["_zod"]["issc"]>, "type" | "checks" | "error" | "check" | AlsoOmit>;
|
|
59
59
|
|
|
60
60
|
// String
|
|
61
|
-
export type $ZodStringParams = TypeParams<schemas.$ZodString<string>, "coerce"
|
|
61
|
+
export type $ZodStringParams = TypeParams<schemas.$ZodString<string>, "coerce"> & {
|
|
62
|
+
checks?: readonly checks.$ZodCheck<string>[];
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
function snapshotChecks<T extends { checks?: readonly checks.$ZodCheck<never>[] }>(def: T) {
|
|
66
|
+
if (def.checks) def.checks = [...def.checks];
|
|
67
|
+
return def as T & { checks?: NonNullable<T["checks"]>[number][] };
|
|
68
|
+
}
|
|
62
69
|
// @__NO_SIDE_EFFECTS__
|
|
63
70
|
export function _string<T extends schemas.$ZodString>(
|
|
64
71
|
Class: util.SchemaClass<T>,
|
|
65
72
|
params?: string | $ZodStringParams
|
|
66
73
|
): T {
|
|
67
|
-
return new Class({
|
|
68
|
-
type: "string",
|
|
69
|
-
...util.normalizeParams(params),
|
|
70
|
-
});
|
|
74
|
+
return new Class(snapshotChecks({ type: "string" as const, ...util.normalizeParams(params) }));
|
|
71
75
|
}
|
|
72
76
|
|
|
73
77
|
// @__NO_SIDE_EFFECTS__
|
|
@@ -75,11 +79,7 @@ export function _coercedString<T extends schemas.$ZodString>(
|
|
|
75
79
|
Class: util.SchemaClass<T>,
|
|
76
80
|
params?: string | $ZodStringParams
|
|
77
81
|
): T {
|
|
78
|
-
return new Class({
|
|
79
|
-
type: "string",
|
|
80
|
-
coerce: true,
|
|
81
|
-
...util.normalizeParams(params),
|
|
82
|
-
});
|
|
82
|
+
return new Class(snapshotChecks({ type: "string" as const, coerce: true, ...util.normalizeParams(params) }));
|
|
83
83
|
}
|
|
84
84
|
|
|
85
85
|
export type $ZodStringFormatParams = CheckTypeParams<
|
|
@@ -608,7 +608,9 @@ export function _isoDuration<T extends schemas.$ZodISODuration>(
|
|
|
608
608
|
}
|
|
609
609
|
|
|
610
610
|
// Number
|
|
611
|
-
export type $ZodNumberParams = TypeParams<schemas.$ZodNumber<number>, "coerce"
|
|
611
|
+
export type $ZodNumberParams = TypeParams<schemas.$ZodNumber<number>, "coerce"> & {
|
|
612
|
+
checks?: readonly checks.$ZodCheck<number>[];
|
|
613
|
+
};
|
|
612
614
|
export type $ZodNumberFormatParams = CheckTypeParams<schemas.$ZodNumberFormat, "format" | "coerce">;
|
|
613
615
|
export type $ZodCheckNumberFormatParams = CheckParams<checks.$ZodCheckNumberFormat, "format" | "when">;
|
|
614
616
|
// @__NO_SIDE_EFFECTS__
|
|
@@ -616,11 +618,7 @@ export function _number<T extends schemas.$ZodNumber>(
|
|
|
616
618
|
Class: util.SchemaClass<T>,
|
|
617
619
|
params?: string | $ZodNumberParams
|
|
618
620
|
): T {
|
|
619
|
-
return new Class({
|
|
620
|
-
type: "number",
|
|
621
|
-
checks: [],
|
|
622
|
-
...util.normalizeParams(params),
|
|
623
|
-
});
|
|
621
|
+
return new Class(snapshotChecks({ type: "number" as const, checks: [], ...util.normalizeParams(params) }));
|
|
624
622
|
}
|
|
625
623
|
|
|
626
624
|
// @__NO_SIDE_EFFECTS__
|
|
@@ -628,12 +626,9 @@ export function _coercedNumber<T extends schemas.$ZodNumber>(
|
|
|
628
626
|
Class: util.SchemaClass<T>,
|
|
629
627
|
params?: string | $ZodNumberParams
|
|
630
628
|
): T {
|
|
631
|
-
return new Class(
|
|
632
|
-
type: "number",
|
|
633
|
-
|
|
634
|
-
checks: [],
|
|
635
|
-
...util.normalizeParams(params),
|
|
636
|
-
});
|
|
629
|
+
return new Class(
|
|
630
|
+
snapshotChecks({ type: "number" as const, coerce: true, checks: [], ...util.normalizeParams(params) })
|
|
631
|
+
);
|
|
637
632
|
}
|
|
638
633
|
|
|
639
634
|
// @__NO_SIDE_EFFECTS__
|
package/src/v4/core/compile.ts
CHANGED
|
@@ -13,10 +13,10 @@ import {
|
|
|
13
13
|
isValidIPv6,
|
|
14
14
|
isValidJWT,
|
|
15
15
|
mergeValues,
|
|
16
|
-
parseURLObject,
|
|
17
16
|
stripTabAndNewline,
|
|
18
17
|
urlHostnameOk,
|
|
19
18
|
urlProtocolOk,
|
|
19
|
+
validateURL,
|
|
20
20
|
} from "./schemas.js";
|
|
21
21
|
import type { ParseContextInternal, ParsePayload, SomeType } from "./schemas.js";
|
|
22
22
|
import * as util from "./util.js";
|
|
@@ -772,7 +772,21 @@ const PATTERN_IS_COMPLETE: Set<string> = new Set([
|
|
|
772
772
|
]);
|
|
773
773
|
|
|
774
774
|
// Returns the accessor holding the (possibly normalized) value after the check — url/normalize formats produce a new value like overwrite does. Never assigns to the incoming accessor: it may be a `const` or a property expression on user input.
|
|
775
|
-
function generateStringFormatCheck(doc: Doc, ctx: CompileContext, def: StringFormatDef, accessor: string): string
|
|
775
|
+
function generateStringFormatCheck(doc: Doc, ctx: CompileContext, def: StringFormatDef, accessor: string): string;
|
|
776
|
+
function generateStringFormatCheck(
|
|
777
|
+
doc: Doc,
|
|
778
|
+
ctx: CompileContext,
|
|
779
|
+
def: StringFormatDef,
|
|
780
|
+
accessor: string,
|
|
781
|
+
needsValue: boolean
|
|
782
|
+
): string | null;
|
|
783
|
+
function generateStringFormatCheck(
|
|
784
|
+
doc: Doc,
|
|
785
|
+
ctx: CompileContext,
|
|
786
|
+
def: StringFormatDef,
|
|
787
|
+
accessor: string,
|
|
788
|
+
needsValue = true
|
|
789
|
+
): string | null {
|
|
776
790
|
// Some string formats do runtime validation beyond their advertised pattern. For cheap pure utility checks, hoist the runtime function and call it so the fast path stays correct without cloning the utility logic into codegen.
|
|
777
791
|
const fmt = def.format;
|
|
778
792
|
if (fmt === "base64") {
|
|
@@ -820,7 +834,7 @@ function generateStringFormatCheck(doc: Doc, ctx: CompileContext, def: StringFor
|
|
|
820
834
|
formatDef.protocol !== undefined
|
|
821
835
|
) {
|
|
822
836
|
// Same three predicates the runtime calls, in the same order, so there is no second URL implementation to drift. Which options exist is known now, so the calls the runtime makes conditionally are emitted conditionally instead.
|
|
823
|
-
const parseConst = addConstant(ctx,
|
|
837
|
+
const parseConst = addConstant(ctx, validateURL);
|
|
824
838
|
const defConst = addConstant(ctx, def);
|
|
825
839
|
const trimVar = newVar(ctx);
|
|
826
840
|
const urlVar = newVar(ctx);
|
|
@@ -835,6 +849,7 @@ function generateStringFormatCheck(doc: Doc, ctx: CompileContext, def: StringFor
|
|
|
835
849
|
const protocolConst = addConstant(ctx, urlProtocolOk);
|
|
836
850
|
doc.write(`if (!${protocolConst}(${urlVar}, ${defConst}.protocol)) return INVALID;`);
|
|
837
851
|
}
|
|
852
|
+
if (!needsValue) return null;
|
|
838
853
|
const outputVar = newVar(ctx);
|
|
839
854
|
const outputExpr = formatDef.normalize ? `${urlVar}.href` : `${addConstant(ctx, stripTabAndNewline)}(${trimVar})`;
|
|
840
855
|
doc.write(`const ${outputVar} = ${outputExpr};`);
|
|
@@ -961,7 +976,7 @@ function generateCheck(
|
|
|
961
976
|
|
|
962
977
|
switch (type) {
|
|
963
978
|
case "string":
|
|
964
|
-
typeAccessor = generateStringCheck(doc, ctx, schema, accessor);
|
|
979
|
+
typeAccessor = generateStringCheck(doc, ctx, schema, accessor, buildsValue);
|
|
965
980
|
break;
|
|
966
981
|
case "number":
|
|
967
982
|
typeAccessor = generateNumberCheck(doc, schema, accessor);
|
|
@@ -1092,13 +1107,19 @@ function generateCheck(
|
|
|
1092
1107
|
return generateChecks(doc, ctx, schema, typeAccessor);
|
|
1093
1108
|
}
|
|
1094
1109
|
|
|
1095
|
-
function generateStringCheck(
|
|
1110
|
+
function generateStringCheck(
|
|
1111
|
+
doc: Doc,
|
|
1112
|
+
ctx: CompileContext,
|
|
1113
|
+
schema: SomeType,
|
|
1114
|
+
accessor: string,
|
|
1115
|
+
needsValue = true
|
|
1116
|
+
): string | null {
|
|
1096
1117
|
doc.write(`if (typeof ${accessor} !== "string") return INVALID;`);
|
|
1097
1118
|
|
|
1098
1119
|
// z.email() carries its format on the def, z.string().email() in def.checks; both route here so the format table has no second copy to drift from.
|
|
1099
1120
|
const def = schema._zod.def as unknown as StringFormatDef & { format?: string };
|
|
1100
1121
|
if (def.format === undefined) return accessor;
|
|
1101
|
-
return generateStringFormatCheck(doc, ctx, def, accessor);
|
|
1122
|
+
return generateStringFormatCheck(doc, ctx, def, accessor, needsValue);
|
|
1102
1123
|
}
|
|
1103
1124
|
|
|
1104
1125
|
function generateNumberCheck(doc: Doc, schema: SomeType, accessor: string): string {
|
package/src/v4/core/core.ts
CHANGED
|
@@ -79,9 +79,7 @@ export /*@__NO_SIDE_EFFECTS__*/ function $constructor<T extends ZodTrait, D = T[
|
|
|
79
79
|
// Cleared even on throw, so the shared descriptor never leaks one instance's internals into the next.
|
|
80
80
|
_zodDesc.value = undefined;
|
|
81
81
|
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
if (inst._zod.traits.has(name)) {
|
|
82
|
+
} else if (inst._zod.traits.has(name)) {
|
|
85
83
|
return;
|
|
86
84
|
}
|
|
87
85
|
|
|
@@ -449,20 +449,16 @@ export const objectProcessor: Processor<schemas.$ZodObject> = (schema, ctx, _jso
|
|
|
449
449
|
}
|
|
450
450
|
|
|
451
451
|
// required keys
|
|
452
|
-
const
|
|
453
|
-
const
|
|
454
|
-
[
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
return field._zod.optout === undefined;
|
|
460
|
-
}
|
|
461
|
-
})
|
|
462
|
-
);
|
|
452
|
+
const requiredKeys: string[] = [];
|
|
453
|
+
for (const key of Object.keys(shape)) {
|
|
454
|
+
const field = def.shape[key]!;
|
|
455
|
+
if (ctx.io === "input" ? inputOptin(field) === undefined : field._zod.optout === undefined) {
|
|
456
|
+
requiredKeys.push(key);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
463
459
|
|
|
464
|
-
if (requiredKeys.
|
|
465
|
-
json.required =
|
|
460
|
+
if (requiredKeys.length > 0) {
|
|
461
|
+
json.required = requiredKeys;
|
|
466
462
|
}
|
|
467
463
|
|
|
468
464
|
// catchall
|
package/src/v4/core/regexes.ts
CHANGED
|
@@ -98,6 +98,10 @@ export const e164: RegExp = /^\+[1-9]\d{6,14}$/;
|
|
|
98
98
|
// Credit card shape: 12–19 digits, optionally separated by single spaces or single hyphens. ISO/IEC 7812 caps the PAN at 19 digits; 12 is the shortest issued length (Maestro).
|
|
99
99
|
export const creditCard: RegExp = /^\d(?:[ -]?\d){11,18}$/;
|
|
100
100
|
|
|
101
|
+
// ISO 4217 alpha codes from the SIX list, regenerated by scripts/update-iso-4217.ts
|
|
102
|
+
export const currencyCode: RegExp =
|
|
103
|
+
/^(?:AED|AFN|ALL|AMD|AOA|ARS|AUD|AWG|AZN|BAM|BBD|BDT|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|XAD|XAF|XAG|XAU|XBA|XBB|XBC|XBD|XCD|XCG|XDR|XOF|XPD|XPF|XPT|XSU|XTS|XUA|XXX|YER|ZAR|ZMW|ZWG)$/;
|
|
104
|
+
|
|
101
105
|
// iban electronic format: 2-letter country, check digits 02-98 (the only values `98 - remainder` can produce), 11-30 bban characters
|
|
102
106
|
export const iban: RegExp = /^[A-Z]{2}(?!00|01|99)\d{2}[A-Z0-9]{11,30}$/;
|
|
103
107
|
|