zod 4.5.3 → 4.6.0
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/compile.cjs +19 -0
- package/index.cjs +19 -0
- package/locales/index.cjs +19 -0
- package/mini/index.cjs +19 -0
- package/package.json +8 -7
- package/src/v3/helpers/errorUtil.ts +1 -1
- package/src/v4/classic/checks.ts +0 -1
- package/src/v4/classic/external.ts +2 -0
- package/src/v4/classic/from-json-schema.ts +248 -25
- package/src/v4/classic/schemas.ts +255 -164
- package/src/v4/classic/tests/assignability.test.ts +59 -9
- package/src/v4/classic/tests/bigint.test.ts +6 -0
- package/src/v4/classic/tests/continuability.test.ts +2 -2
- package/src/v4/classic/tests/cyclic-data.test.ts +316 -0
- package/src/v4/classic/tests/default.test.ts +46 -0
- package/src/v4/classic/tests/enum.test.ts +9 -0
- package/src/v4/classic/tests/error.test.ts +130 -1
- package/src/v4/classic/tests/firstparty.test.ts +4 -0
- package/src/v4/classic/tests/from-json-schema.test.ts +157 -0
- package/src/v4/classic/tests/iban.test.ts +81 -0
- package/src/v4/classic/tests/instance-footprint.test.ts +18 -2
- package/src/v4/classic/tests/instanceof.test.ts +27 -7
- package/src/v4/classic/tests/issue-schema.test.ts +88 -56
- package/src/v4/classic/tests/object.test.ts +56 -0
- package/src/v4/classic/tests/optin-ladder.test.ts +3 -2
- package/src/v4/classic/tests/properties.test.ts +225 -0
- package/src/v4/classic/tests/recursive-types.test.ts +1 -1
- package/src/v4/classic/tests/standard-schema.test.ts +19 -0
- package/src/v4/classic/tests/string.test.ts +25 -0
- package/src/v4/classic/tests/template-literal.test.ts +33 -1
- package/src/v4/classic/tests/to-json-schema.test.ts +147 -3
- package/src/v4/classic/tests/validate.test.ts +569 -0
- package/src/v4/core/api.ts +30 -6
- package/src/v4/core/checks.ts +1 -101
- package/src/v4/core/compile.ts +193 -81
- package/src/v4/core/doc.ts +6 -2
- package/src/v4/core/errors.ts +3 -6
- package/src/v4/core/json-schema-generator.ts +2 -2
- package/src/v4/core/json-schema-processors.ts +226 -68
- package/src/v4/core/memoizer.ts +162 -36
- package/src/v4/core/parse.ts +37 -16
- package/src/v4/core/regexes.ts +10 -4
- package/src/v4/core/schemas.ts +314 -83
- package/src/v4/core/tests/cached.test.ts +54 -0
- package/src/v4/core/tests/compile-generative.test.ts +445 -0
- package/src/v4/core/tests/compile.test.ts +222 -2
- package/src/v4/core/tests/email-regex.test.ts +140 -0
- package/src/v4/core/tests/polyfill-collision.test.ts +46 -0
- package/src/v4/core/to-json-schema.ts +10 -8
- package/src/v4/core/util.ts +222 -147
- package/src/v4/core/versions.ts +2 -2
- package/src/v4/core/visit.ts +11 -0
- 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/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/checks.ts +0 -1
- package/src/v4/mini/external.ts +2 -0
- package/src/v4/mini/schemas.ts +38 -2
- package/src/v4/mini/tests/checks.test.ts +1 -1
- package/src/v4/mini/tests/computed.test.ts +11 -17
- package/src/v4/mini/tests/index.test.ts +8 -0
- package/src/v4/mini/tests/string.test.ts +16 -0
- package/v3/ZodError.cjs +19 -0
- package/v3/errors.cjs +19 -0
- package/v3/external.cjs +19 -0
- package/v3/helpers/enumUtil.cjs +19 -0
- package/v3/helpers/errorUtil.cjs +20 -1
- package/v3/helpers/errorUtil.js +1 -1
- package/v3/helpers/parseUtil.cjs +19 -0
- package/v3/helpers/partialUtil.cjs +19 -0
- package/v3/helpers/typeAliases.cjs +19 -0
- package/v3/helpers/util.cjs +19 -0
- package/v3/index.cjs +19 -0
- package/v3/standard-schema.cjs +19 -0
- package/v3/types.cjs +19 -0
- package/v4/classic/checks.cjs +20 -2
- package/v4/classic/checks.d.cts +1 -1
- package/v4/classic/checks.d.ts +1 -1
- package/v4/classic/checks.js +1 -1
- package/v4/classic/coerce.cjs +19 -0
- package/v4/classic/compat.cjs +19 -0
- package/v4/classic/deep-partial.cjs +19 -0
- package/v4/classic/errors.cjs +19 -0
- package/v4/classic/external.cjs +22 -1
- 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/from-json-schema.cjs +264 -24
- package/v4/classic/from-json-schema.js +245 -24
- package/v4/classic/in-out.cjs +19 -0
- package/v4/classic/index.cjs +19 -0
- package/v4/classic/iso.cjs +19 -0
- package/v4/classic/parse.cjs +19 -0
- package/v4/classic/schemas.cjs +100 -31
- package/v4/classic/schemas.d.cts +17 -1
- package/v4/classic/schemas.d.ts +17 -1
- package/v4/classic/schemas.js +77 -29
- package/v4/core/api.cjs +37 -2
- package/v4/core/api.d.cts +6 -4
- package/v4/core/api.d.ts +6 -4
- package/v4/core/api.js +17 -2
- package/v4/core/checks.cjs +19 -96
- package/v4/core/checks.d.cts +1 -1
- package/v4/core/checks.d.ts +1 -1
- package/v4/core/checks.js +0 -96
- package/v4/core/compile.cjs +164 -65
- package/v4/core/compile.d.cts +18 -4
- package/v4/core/compile.d.ts +18 -4
- package/v4/core/compile.js +145 -66
- package/v4/core/core.cjs +19 -0
- package/v4/core/doc.cjs +26 -2
- package/v4/core/doc.js +7 -2
- package/v4/core/errors.cjs +21 -5
- package/v4/core/errors.js +2 -5
- package/v4/core/index.cjs +19 -0
- package/v4/core/json-schema-generator.cjs +20 -1
- package/v4/core/json-schema-generator.js +2 -2
- package/v4/core/json-schema-processors.cjs +215 -63
- package/v4/core/json-schema-processors.d.cts +17 -0
- package/v4/core/json-schema-processors.d.ts +17 -0
- package/v4/core/json-schema-processors.js +195 -64
- package/v4/core/json-schema.cjs +19 -0
- package/v4/core/memoizer.cjs +204 -36
- package/v4/core/memoizer.js +162 -36
- package/v4/core/parse.cjs +56 -16
- package/v4/core/parse.js +37 -16
- package/v4/core/regexes.cjs +29 -6
- package/v4/core/regexes.d.cts +2 -0
- package/v4/core/regexes.d.ts +2 -0
- package/v4/core/regexes.js +8 -4
- package/v4/core/registries.cjs +19 -0
- package/v4/core/schemas.cjs +285 -66
- package/v4/core/schemas.d.cts +34 -3
- package/v4/core/schemas.d.ts +34 -3
- package/v4/core/schemas.js +264 -65
- package/v4/core/standard-schema.cjs +19 -0
- package/v4/core/to-json-schema.cjs +29 -7
- package/v4/core/to-json-schema.d.cts +7 -3
- package/v4/core/to-json-schema.d.ts +7 -3
- package/v4/core/to-json-schema.js +8 -7
- package/v4/core/util.cjs +181 -143
- package/v4/core/util.d.cts +25 -4
- package/v4/core/util.d.ts +25 -4
- package/v4/core/util.js +160 -143
- package/v4/core/versions.cjs +21 -2
- package/v4/core/versions.d.cts +1 -1
- package/v4/core/versions.d.ts +1 -1
- package/v4/core/versions.js +2 -2
- package/v4/core/visit.cjs +31 -0
- package/v4/core/visit.js +12 -0
- package/v4/index.cjs +19 -0
- 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/index.cjs +19 -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/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/checks.cjs +20 -2
- package/v4/mini/checks.d.cts +1 -1
- package/v4/mini/checks.d.ts +1 -1
- package/v4/mini/checks.js +1 -1
- package/v4/mini/coerce.cjs +19 -0
- package/v4/mini/deep-partial.cjs +19 -0
- package/v4/mini/external.cjs +22 -1
- 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/in-out.cjs +19 -0
- package/v4/mini/index.cjs +19 -0
- package/v4/mini/iso.cjs +19 -0
- package/v4/mini/parse.cjs +19 -0
- package/v4/mini/schemas.cjs +42 -4
- package/v4/mini/schemas.d.cts +8 -0
- package/v4/mini/schemas.d.ts +8 -0
- package/v4/mini/schemas.js +19 -2
- package/v4-mini/index.cjs +19 -0
|
@@ -149,12 +149,13 @@ function resolveRef(ref, ctx) {
|
|
|
149
149
|
throw new Error(`Reference not found: ${ref}`);
|
|
150
150
|
}
|
|
151
151
|
/**
|
|
152
|
-
*
|
|
153
|
-
*
|
|
154
|
-
*
|
|
155
|
-
*
|
|
152
|
+
* Enforces the keywords that constrain an object's own keys — `propertyNames`,
|
|
153
|
+
* `minProperties`, `maxProperties` — before `objectSchema` runs. The guard has
|
|
154
|
+
* to see the raw input: an object parse drops `__proto__` and can add keys from
|
|
155
|
+
* a property `default`, so its output is not the set of names the instance
|
|
156
|
+
* actually carried.
|
|
156
157
|
*/
|
|
157
|
-
function
|
|
158
|
+
function checkObjectGuards(objectSchema, guards) {
|
|
158
159
|
// An identity transform, not z.any(), so `toJSONSchema` reports the object on both the input and the output side of the pipe.
|
|
159
160
|
const guard = z
|
|
160
161
|
.transform((value) => value)
|
|
@@ -162,22 +163,209 @@ function checkPropertyNames(objectSchema, keySchema) {
|
|
|
162
163
|
const value = payload.value;
|
|
163
164
|
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
164
165
|
return;
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
if (result.success)
|
|
168
|
-
continue;
|
|
166
|
+
const keys = Object.getOwnPropertyNames(value);
|
|
167
|
+
if (guards.minProperties !== undefined && keys.length < guards.minProperties) {
|
|
169
168
|
payload.issues.push({
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
169
|
+
origin: "object",
|
|
170
|
+
code: "too_small",
|
|
171
|
+
minimum: guards.minProperties,
|
|
172
|
+
inclusive: true,
|
|
173
|
+
message: `Too small: expected object to have >=${guards.minProperties} properties`,
|
|
174
|
+
input: value,
|
|
175
|
+
inst: objectSchema,
|
|
175
176
|
continue: true,
|
|
176
177
|
});
|
|
177
178
|
}
|
|
179
|
+
if (guards.maxProperties !== undefined && keys.length > guards.maxProperties) {
|
|
180
|
+
payload.issues.push({
|
|
181
|
+
origin: "object",
|
|
182
|
+
code: "too_big",
|
|
183
|
+
maximum: guards.maxProperties,
|
|
184
|
+
inclusive: true,
|
|
185
|
+
message: `Too big: expected object to have <=${guards.maxProperties} properties`,
|
|
186
|
+
input: value,
|
|
187
|
+
inst: objectSchema,
|
|
188
|
+
continue: true,
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
if (guards.keySchema) {
|
|
192
|
+
for (const key of keys) {
|
|
193
|
+
const result = guards.keySchema.safeParse(key);
|
|
194
|
+
if (result.success)
|
|
195
|
+
continue;
|
|
196
|
+
payload.issues.push({
|
|
197
|
+
code: "invalid_key",
|
|
198
|
+
origin: "record",
|
|
199
|
+
issues: result.error.issues,
|
|
200
|
+
input: key,
|
|
201
|
+
path: [key],
|
|
202
|
+
continue: true,
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
}
|
|
178
206
|
});
|
|
179
207
|
return guard.pipe(objectSchema);
|
|
180
208
|
}
|
|
209
|
+
/**
|
|
210
|
+
* An injective string for JSON data: two values share a key exactly when `uniqueItems` calls them
|
|
211
|
+
* equal, so duplicate detection is a Map lookup rather than a pairwise walk. Strings and keys are
|
|
212
|
+
* length-prefixed so no value can spell another one. `null` means "never equal to anything" — a
|
|
213
|
+
* cycle or a NaN, neither of which JSON can express.
|
|
214
|
+
*/
|
|
215
|
+
function canonicalKey(value, seen) {
|
|
216
|
+
if (value === null)
|
|
217
|
+
return "z";
|
|
218
|
+
const type = typeof value;
|
|
219
|
+
if (type !== "object") {
|
|
220
|
+
if (type === "number" && Number.isNaN(value))
|
|
221
|
+
return null;
|
|
222
|
+
const raw = String(value);
|
|
223
|
+
return `${type[0]}${raw.length}:${raw}`;
|
|
224
|
+
}
|
|
225
|
+
if (seen.has(value))
|
|
226
|
+
return null;
|
|
227
|
+
seen.add(value);
|
|
228
|
+
try {
|
|
229
|
+
if (Array.isArray(value)) {
|
|
230
|
+
const parts = [];
|
|
231
|
+
for (const item of value) {
|
|
232
|
+
const key = canonicalKey(item, seen);
|
|
233
|
+
if (key === null)
|
|
234
|
+
return null;
|
|
235
|
+
parts.push(key);
|
|
236
|
+
}
|
|
237
|
+
return `a${parts.length}:[${parts.join(",")}]`;
|
|
238
|
+
}
|
|
239
|
+
const keys = Object.keys(value).sort();
|
|
240
|
+
const parts = [];
|
|
241
|
+
for (const k of keys) {
|
|
242
|
+
const key = canonicalKey(value[k], seen);
|
|
243
|
+
if (key === null)
|
|
244
|
+
return null;
|
|
245
|
+
parts.push(`${k.length}:${k}=${key}`);
|
|
246
|
+
}
|
|
247
|
+
return `o${parts.length}:{${parts.join(",")}}`;
|
|
248
|
+
}
|
|
249
|
+
finally {
|
|
250
|
+
seen.delete(value);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
// keywords whose value is a schema or an array of them
|
|
254
|
+
const SCHEMA_KEYWORDS = /*@__PURE__*/ new Set([
|
|
255
|
+
"items",
|
|
256
|
+
"prefixItems",
|
|
257
|
+
"additionalItems",
|
|
258
|
+
"additionalProperties",
|
|
259
|
+
"contains",
|
|
260
|
+
"propertyNames",
|
|
261
|
+
"not",
|
|
262
|
+
"if",
|
|
263
|
+
"then",
|
|
264
|
+
"else",
|
|
265
|
+
"allOf",
|
|
266
|
+
"anyOf",
|
|
267
|
+
"oneOf",
|
|
268
|
+
"unevaluatedItems",
|
|
269
|
+
"unevaluatedProperties",
|
|
270
|
+
"contentSchema",
|
|
271
|
+
]);
|
|
272
|
+
// keywords whose value maps names to schemas. draft-7 `dependencies` also allows an array of property names, which holds no schema to walk
|
|
273
|
+
const SCHEMA_MAP_KEYWORDS = /*@__PURE__*/ new Set([
|
|
274
|
+
"properties",
|
|
275
|
+
"patternProperties",
|
|
276
|
+
"dependentSchemas",
|
|
277
|
+
"dependencies",
|
|
278
|
+
"$defs",
|
|
279
|
+
"definitions",
|
|
280
|
+
]);
|
|
281
|
+
/**
|
|
282
|
+
* True when a subschema holds a `$ref` anywhere a schema can appear. Only those positions are
|
|
283
|
+
* walked: `$ref` under `default` or an `x-` annotation is instance data with an ordinary key, and
|
|
284
|
+
* reading it as a reference would drop a constraint that resolves perfectly well.
|
|
285
|
+
*/
|
|
286
|
+
function containsRef(value) {
|
|
287
|
+
if (typeof value !== "object" || value === null)
|
|
288
|
+
return false;
|
|
289
|
+
if (Array.isArray(value))
|
|
290
|
+
return value.some(containsRef);
|
|
291
|
+
if (typeof value.$ref === "string")
|
|
292
|
+
return true;
|
|
293
|
+
return Object.entries(value).some(([key, sub]) => {
|
|
294
|
+
if (SCHEMA_KEYWORDS.has(key))
|
|
295
|
+
return containsRef(sub);
|
|
296
|
+
if (!SCHEMA_MAP_KEYWORDS.has(key) || typeof sub !== "object" || sub === null)
|
|
297
|
+
return false;
|
|
298
|
+
return Object.values(sub).some(containsRef);
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
function plural(n) {
|
|
302
|
+
return n === 1 ? "element" : "elements";
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* Enforces `uniqueItems` and the `contains` family before `arraySchema` runs, for the same reason
|
|
306
|
+
* `checkObjectGuards` runs pre-pipe: an item parse can apply a nested `default`, so the parsed
|
|
307
|
+
* array is not the instance the keywords are defined over. A guard issue aborts the pipe, so
|
|
308
|
+
* `minItems`/`maxItems` are not also reported on the same parse — instance correctness is worth
|
|
309
|
+
* more than co-reporting two issues.
|
|
310
|
+
*/
|
|
311
|
+
function checkArrayGuards(arraySchema, guards) {
|
|
312
|
+
// An identity transform, not z.any(), so `toJSONSchema` reports the array on both the input and the output side of the pipe.
|
|
313
|
+
const guard = z
|
|
314
|
+
.transform((value) => value)
|
|
315
|
+
.check((payload) => {
|
|
316
|
+
const items = payload.value;
|
|
317
|
+
if (!Array.isArray(items))
|
|
318
|
+
return;
|
|
319
|
+
if (guards.uniqueItems === true) {
|
|
320
|
+
const firstSeen = new Map();
|
|
321
|
+
for (let i = 0; i < items.length; i++) {
|
|
322
|
+
const key = canonicalKey(items[i], new Set());
|
|
323
|
+
if (key === null)
|
|
324
|
+
continue;
|
|
325
|
+
const first = firstSeen.get(key);
|
|
326
|
+
if (first === undefined) {
|
|
327
|
+
firstSeen.set(key, i);
|
|
328
|
+
continue;
|
|
329
|
+
}
|
|
330
|
+
payload.issues.push({
|
|
331
|
+
code: "custom",
|
|
332
|
+
message: `Array items must be unique: element at index ${i} duplicates the one at index ${first}`,
|
|
333
|
+
input: items,
|
|
334
|
+
path: [i],
|
|
335
|
+
continue: true,
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
if (guards.containsSchema) {
|
|
340
|
+
const minContains = guards.minContains ?? 1;
|
|
341
|
+
// one past the ceiling already proves the failure, and nothing above it changes the verdict
|
|
342
|
+
const ceiling = guards.maxContains !== undefined ? guards.maxContains + 1 : Number.POSITIVE_INFINITY;
|
|
343
|
+
let matches = 0;
|
|
344
|
+
for (const item of items) {
|
|
345
|
+
if (guards.containsSchema.safeParse(item).success && ++matches >= ceiling)
|
|
346
|
+
break;
|
|
347
|
+
}
|
|
348
|
+
if (matches < minContains) {
|
|
349
|
+
// the scan ran to the end, so this count is exact
|
|
350
|
+
payload.issues.push({
|
|
351
|
+
code: "custom",
|
|
352
|
+
message: `Array must contain at least ${minContains} matching ${plural(minContains)}; found ${matches}`,
|
|
353
|
+
input: items,
|
|
354
|
+
continue: true,
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
if (guards.maxContains !== undefined && matches > guards.maxContains) {
|
|
358
|
+
payload.issues.push({
|
|
359
|
+
code: "custom",
|
|
360
|
+
message: `Array must contain at most ${guards.maxContains} matching ${plural(guards.maxContains)}`,
|
|
361
|
+
input: items,
|
|
362
|
+
continue: true,
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
});
|
|
367
|
+
return guard.pipe(arraySchema);
|
|
368
|
+
}
|
|
181
369
|
function getTupleRest(restSchema, ctx) {
|
|
182
370
|
if (restSchema === false) {
|
|
183
371
|
return undefined;
|
|
@@ -342,6 +530,9 @@ function convertBaseSchema(schema, ctx) {
|
|
|
342
530
|
else if (format === "credit_card") {
|
|
343
531
|
stringSchema = stringSchema.check(z.creditCard());
|
|
344
532
|
}
|
|
533
|
+
else if (format === "iban") {
|
|
534
|
+
stringSchema = stringSchema.check(z.iban());
|
|
535
|
+
}
|
|
345
536
|
else if (format === "jwt") {
|
|
346
537
|
stringSchema = stringSchema.check(z.jwt());
|
|
347
538
|
}
|
|
@@ -509,17 +700,23 @@ function convertBaseSchema(schema, ctx) {
|
|
|
509
700
|
}
|
|
510
701
|
}
|
|
511
702
|
// propertyNames constrains key *names* only, and says nothing about which keys are required or how their values validate. Layering it on top of the result keeps properties/patternProperties/additionalProperties composing underneath. `true` allows every name, so it needs no guard.
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
703
|
+
const hasKeyGuard = schema.propertyNames !== undefined && schema.propertyNames !== true;
|
|
704
|
+
const minProperties = typeof schema.minProperties === "number" ? schema.minProperties : undefined;
|
|
705
|
+
const maxProperties = typeof schema.maxProperties === "number" ? schema.maxProperties : undefined;
|
|
706
|
+
if (hasKeyGuard || minProperties !== undefined || maxProperties !== undefined) {
|
|
707
|
+
let keySchema;
|
|
708
|
+
if (hasKeyGuard) {
|
|
709
|
+
// Keys are always strings, so a propertyNames subschema that omits `type` still constrains them — without this it would convert to z.any().
|
|
710
|
+
const keyJSONSchema = typeof schema.propertyNames === "object" && schema.propertyNames.type === undefined
|
|
711
|
+
? { type: "string", ...schema.propertyNames }
|
|
712
|
+
: schema.propertyNames;
|
|
713
|
+
keySchema = convertSchema(keyJSONSchema, ctx);
|
|
714
|
+
}
|
|
715
|
+
zodSchema = checkObjectGuards(zodSchema, { keySchema, minProperties, maxProperties });
|
|
518
716
|
}
|
|
519
717
|
break;
|
|
520
718
|
}
|
|
521
719
|
case "array": {
|
|
522
|
-
// TODO: uniqueItems and contains/minContains/maxContains are not supported
|
|
523
720
|
// Check if this is a tuple (prefixItems or items as array)
|
|
524
721
|
const prefixItems = schema.prefixItems;
|
|
525
722
|
const items = schema.items;
|
|
@@ -572,6 +769,15 @@ function convertBaseSchema(schema, ctx) {
|
|
|
572
769
|
// No items specified - array of any
|
|
573
770
|
zodSchema = z.array(z.any());
|
|
574
771
|
}
|
|
772
|
+
// minContains/maxContains only constrain anything when `contains` itself is present
|
|
773
|
+
if (schema.uniqueItems === true || schema.contains !== undefined) {
|
|
774
|
+
zodSchema = checkArrayGuards(zodSchema, {
|
|
775
|
+
uniqueItems: schema.uniqueItems === true,
|
|
776
|
+
containsSchema: schema.contains !== undefined ? convertSchema(schema.contains, ctx) : undefined,
|
|
777
|
+
minContains: typeof schema.minContains === "number" ? schema.minContains : undefined,
|
|
778
|
+
maxContains: typeof schema.maxContains === "number" ? schema.maxContains : undefined,
|
|
779
|
+
});
|
|
780
|
+
}
|
|
575
781
|
break;
|
|
576
782
|
}
|
|
577
783
|
default:
|
|
@@ -639,9 +845,24 @@ function convertSchema(schema, ctx) {
|
|
|
639
845
|
extraMeta[key] = schema[key];
|
|
640
846
|
}
|
|
641
847
|
}
|
|
642
|
-
// `propertyNames`
|
|
643
|
-
if (schema.
|
|
644
|
-
|
|
848
|
+
// `propertyNames` and `contains` are enforced by a guard, which `toJSONSchema` cannot infer, so the original keyword is carried as metadata to keep the round-trip lossless. Only where it was actually applied: on any other type it is inert, and on a `$ref` the metadata would land on the target every reference shares. A carried subschema is copied verbatim while the root `$defs` stay behind, so one holding a `$ref` is dropped rather than emitted as a dangling reference.
|
|
849
|
+
if (schema.type === "object" && schema.$ref === undefined) {
|
|
850
|
+
if (schema.propertyNames !== undefined && !containsRef(schema.propertyNames)) {
|
|
851
|
+
extraMeta.propertyNames = schema.propertyNames;
|
|
852
|
+
}
|
|
853
|
+
for (const key of ["minProperties", "maxProperties"]) {
|
|
854
|
+
if (schema[key] !== undefined)
|
|
855
|
+
extraMeta[key] = schema[key];
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
if (schema.type === "array" && schema.$ref === undefined) {
|
|
859
|
+
if (schema.contains !== undefined && !containsRef(schema.contains)) {
|
|
860
|
+
extraMeta.contains = schema.contains;
|
|
861
|
+
}
|
|
862
|
+
for (const key of ["uniqueItems", "minContains", "maxContains"]) {
|
|
863
|
+
if (schema[key] !== undefined)
|
|
864
|
+
extraMeta[key] = schema[key];
|
|
865
|
+
}
|
|
645
866
|
}
|
|
646
867
|
for (const key of Object.keys(schema)) {
|
|
647
868
|
if (!RECOGNIZED_KEYS.has(key)) {
|
|
@@ -684,3 +905,22 @@ function fromJSONSchema(schema, params) {
|
|
|
684
905
|
};
|
|
685
906
|
return convertSchema(normalized, ctx);
|
|
686
907
|
}
|
|
908
|
+
|
|
909
|
+
// seal-cjs-exports
|
|
910
|
+
(function () {
|
|
911
|
+
var keys = Object.getOwnPropertyNames(exports);
|
|
912
|
+
for (var i = 0; i < keys.length; i++) {
|
|
913
|
+
var desc = Object.getOwnPropertyDescriptor(exports, keys[i]);
|
|
914
|
+
if (!desc || !desc.get || !desc.configurable) continue;
|
|
915
|
+
var value;
|
|
916
|
+
try {
|
|
917
|
+
value = desc.get();
|
|
918
|
+
} catch (e) {
|
|
919
|
+
continue;
|
|
920
|
+
}
|
|
921
|
+
// a circular require may not have settled this one yet, so leave it live
|
|
922
|
+
if (value === undefined) continue;
|
|
923
|
+
Object.defineProperty(exports, keys[i], { value: value, writable: false, enumerable: desc.enumerable, configurable: false });
|
|
924
|
+
}
|
|
925
|
+
Object.freeze(exports);
|
|
926
|
+
})();
|
|
@@ -123,12 +123,13 @@ function resolveRef(ref, ctx) {
|
|
|
123
123
|
throw new Error(`Reference not found: ${ref}`);
|
|
124
124
|
}
|
|
125
125
|
/**
|
|
126
|
-
*
|
|
127
|
-
*
|
|
128
|
-
*
|
|
129
|
-
*
|
|
126
|
+
* Enforces the keywords that constrain an object's own keys — `propertyNames`,
|
|
127
|
+
* `minProperties`, `maxProperties` — before `objectSchema` runs. The guard has
|
|
128
|
+
* to see the raw input: an object parse drops `__proto__` and can add keys from
|
|
129
|
+
* a property `default`, so its output is not the set of names the instance
|
|
130
|
+
* actually carried.
|
|
130
131
|
*/
|
|
131
|
-
function
|
|
132
|
+
function checkObjectGuards(objectSchema, guards) {
|
|
132
133
|
// An identity transform, not z.any(), so `toJSONSchema` reports the object on both the input and the output side of the pipe.
|
|
133
134
|
const guard = z
|
|
134
135
|
.transform((value) => value)
|
|
@@ -136,22 +137,209 @@ function checkPropertyNames(objectSchema, keySchema) {
|
|
|
136
137
|
const value = payload.value;
|
|
137
138
|
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
138
139
|
return;
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
if (result.success)
|
|
142
|
-
continue;
|
|
140
|
+
const keys = Object.getOwnPropertyNames(value);
|
|
141
|
+
if (guards.minProperties !== undefined && keys.length < guards.minProperties) {
|
|
143
142
|
payload.issues.push({
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
143
|
+
origin: "object",
|
|
144
|
+
code: "too_small",
|
|
145
|
+
minimum: guards.minProperties,
|
|
146
|
+
inclusive: true,
|
|
147
|
+
message: `Too small: expected object to have >=${guards.minProperties} properties`,
|
|
148
|
+
input: value,
|
|
149
|
+
inst: objectSchema,
|
|
149
150
|
continue: true,
|
|
150
151
|
});
|
|
151
152
|
}
|
|
153
|
+
if (guards.maxProperties !== undefined && keys.length > guards.maxProperties) {
|
|
154
|
+
payload.issues.push({
|
|
155
|
+
origin: "object",
|
|
156
|
+
code: "too_big",
|
|
157
|
+
maximum: guards.maxProperties,
|
|
158
|
+
inclusive: true,
|
|
159
|
+
message: `Too big: expected object to have <=${guards.maxProperties} properties`,
|
|
160
|
+
input: value,
|
|
161
|
+
inst: objectSchema,
|
|
162
|
+
continue: true,
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
if (guards.keySchema) {
|
|
166
|
+
for (const key of keys) {
|
|
167
|
+
const result = guards.keySchema.safeParse(key);
|
|
168
|
+
if (result.success)
|
|
169
|
+
continue;
|
|
170
|
+
payload.issues.push({
|
|
171
|
+
code: "invalid_key",
|
|
172
|
+
origin: "record",
|
|
173
|
+
issues: result.error.issues,
|
|
174
|
+
input: key,
|
|
175
|
+
path: [key],
|
|
176
|
+
continue: true,
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
}
|
|
152
180
|
});
|
|
153
181
|
return guard.pipe(objectSchema);
|
|
154
182
|
}
|
|
183
|
+
/**
|
|
184
|
+
* An injective string for JSON data: two values share a key exactly when `uniqueItems` calls them
|
|
185
|
+
* equal, so duplicate detection is a Map lookup rather than a pairwise walk. Strings and keys are
|
|
186
|
+
* length-prefixed so no value can spell another one. `null` means "never equal to anything" — a
|
|
187
|
+
* cycle or a NaN, neither of which JSON can express.
|
|
188
|
+
*/
|
|
189
|
+
function canonicalKey(value, seen) {
|
|
190
|
+
if (value === null)
|
|
191
|
+
return "z";
|
|
192
|
+
const type = typeof value;
|
|
193
|
+
if (type !== "object") {
|
|
194
|
+
if (type === "number" && Number.isNaN(value))
|
|
195
|
+
return null;
|
|
196
|
+
const raw = String(value);
|
|
197
|
+
return `${type[0]}${raw.length}:${raw}`;
|
|
198
|
+
}
|
|
199
|
+
if (seen.has(value))
|
|
200
|
+
return null;
|
|
201
|
+
seen.add(value);
|
|
202
|
+
try {
|
|
203
|
+
if (Array.isArray(value)) {
|
|
204
|
+
const parts = [];
|
|
205
|
+
for (const item of value) {
|
|
206
|
+
const key = canonicalKey(item, seen);
|
|
207
|
+
if (key === null)
|
|
208
|
+
return null;
|
|
209
|
+
parts.push(key);
|
|
210
|
+
}
|
|
211
|
+
return `a${parts.length}:[${parts.join(",")}]`;
|
|
212
|
+
}
|
|
213
|
+
const keys = Object.keys(value).sort();
|
|
214
|
+
const parts = [];
|
|
215
|
+
for (const k of keys) {
|
|
216
|
+
const key = canonicalKey(value[k], seen);
|
|
217
|
+
if (key === null)
|
|
218
|
+
return null;
|
|
219
|
+
parts.push(`${k.length}:${k}=${key}`);
|
|
220
|
+
}
|
|
221
|
+
return `o${parts.length}:{${parts.join(",")}}`;
|
|
222
|
+
}
|
|
223
|
+
finally {
|
|
224
|
+
seen.delete(value);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
// keywords whose value is a schema or an array of them
|
|
228
|
+
const SCHEMA_KEYWORDS = /*@__PURE__*/ new Set([
|
|
229
|
+
"items",
|
|
230
|
+
"prefixItems",
|
|
231
|
+
"additionalItems",
|
|
232
|
+
"additionalProperties",
|
|
233
|
+
"contains",
|
|
234
|
+
"propertyNames",
|
|
235
|
+
"not",
|
|
236
|
+
"if",
|
|
237
|
+
"then",
|
|
238
|
+
"else",
|
|
239
|
+
"allOf",
|
|
240
|
+
"anyOf",
|
|
241
|
+
"oneOf",
|
|
242
|
+
"unevaluatedItems",
|
|
243
|
+
"unevaluatedProperties",
|
|
244
|
+
"contentSchema",
|
|
245
|
+
]);
|
|
246
|
+
// keywords whose value maps names to schemas. draft-7 `dependencies` also allows an array of property names, which holds no schema to walk
|
|
247
|
+
const SCHEMA_MAP_KEYWORDS = /*@__PURE__*/ new Set([
|
|
248
|
+
"properties",
|
|
249
|
+
"patternProperties",
|
|
250
|
+
"dependentSchemas",
|
|
251
|
+
"dependencies",
|
|
252
|
+
"$defs",
|
|
253
|
+
"definitions",
|
|
254
|
+
]);
|
|
255
|
+
/**
|
|
256
|
+
* True when a subschema holds a `$ref` anywhere a schema can appear. Only those positions are
|
|
257
|
+
* walked: `$ref` under `default` or an `x-` annotation is instance data with an ordinary key, and
|
|
258
|
+
* reading it as a reference would drop a constraint that resolves perfectly well.
|
|
259
|
+
*/
|
|
260
|
+
function containsRef(value) {
|
|
261
|
+
if (typeof value !== "object" || value === null)
|
|
262
|
+
return false;
|
|
263
|
+
if (Array.isArray(value))
|
|
264
|
+
return value.some(containsRef);
|
|
265
|
+
if (typeof value.$ref === "string")
|
|
266
|
+
return true;
|
|
267
|
+
return Object.entries(value).some(([key, sub]) => {
|
|
268
|
+
if (SCHEMA_KEYWORDS.has(key))
|
|
269
|
+
return containsRef(sub);
|
|
270
|
+
if (!SCHEMA_MAP_KEYWORDS.has(key) || typeof sub !== "object" || sub === null)
|
|
271
|
+
return false;
|
|
272
|
+
return Object.values(sub).some(containsRef);
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
function plural(n) {
|
|
276
|
+
return n === 1 ? "element" : "elements";
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Enforces `uniqueItems` and the `contains` family before `arraySchema` runs, for the same reason
|
|
280
|
+
* `checkObjectGuards` runs pre-pipe: an item parse can apply a nested `default`, so the parsed
|
|
281
|
+
* array is not the instance the keywords are defined over. A guard issue aborts the pipe, so
|
|
282
|
+
* `minItems`/`maxItems` are not also reported on the same parse — instance correctness is worth
|
|
283
|
+
* more than co-reporting two issues.
|
|
284
|
+
*/
|
|
285
|
+
function checkArrayGuards(arraySchema, guards) {
|
|
286
|
+
// An identity transform, not z.any(), so `toJSONSchema` reports the array on both the input and the output side of the pipe.
|
|
287
|
+
const guard = z
|
|
288
|
+
.transform((value) => value)
|
|
289
|
+
.check((payload) => {
|
|
290
|
+
const items = payload.value;
|
|
291
|
+
if (!Array.isArray(items))
|
|
292
|
+
return;
|
|
293
|
+
if (guards.uniqueItems === true) {
|
|
294
|
+
const firstSeen = new Map();
|
|
295
|
+
for (let i = 0; i < items.length; i++) {
|
|
296
|
+
const key = canonicalKey(items[i], new Set());
|
|
297
|
+
if (key === null)
|
|
298
|
+
continue;
|
|
299
|
+
const first = firstSeen.get(key);
|
|
300
|
+
if (first === undefined) {
|
|
301
|
+
firstSeen.set(key, i);
|
|
302
|
+
continue;
|
|
303
|
+
}
|
|
304
|
+
payload.issues.push({
|
|
305
|
+
code: "custom",
|
|
306
|
+
message: `Array items must be unique: element at index ${i} duplicates the one at index ${first}`,
|
|
307
|
+
input: items,
|
|
308
|
+
path: [i],
|
|
309
|
+
continue: true,
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
if (guards.containsSchema) {
|
|
314
|
+
const minContains = guards.minContains ?? 1;
|
|
315
|
+
// one past the ceiling already proves the failure, and nothing above it changes the verdict
|
|
316
|
+
const ceiling = guards.maxContains !== undefined ? guards.maxContains + 1 : Number.POSITIVE_INFINITY;
|
|
317
|
+
let matches = 0;
|
|
318
|
+
for (const item of items) {
|
|
319
|
+
if (guards.containsSchema.safeParse(item).success && ++matches >= ceiling)
|
|
320
|
+
break;
|
|
321
|
+
}
|
|
322
|
+
if (matches < minContains) {
|
|
323
|
+
// the scan ran to the end, so this count is exact
|
|
324
|
+
payload.issues.push({
|
|
325
|
+
code: "custom",
|
|
326
|
+
message: `Array must contain at least ${minContains} matching ${plural(minContains)}; found ${matches}`,
|
|
327
|
+
input: items,
|
|
328
|
+
continue: true,
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
if (guards.maxContains !== undefined && matches > guards.maxContains) {
|
|
332
|
+
payload.issues.push({
|
|
333
|
+
code: "custom",
|
|
334
|
+
message: `Array must contain at most ${guards.maxContains} matching ${plural(guards.maxContains)}`,
|
|
335
|
+
input: items,
|
|
336
|
+
continue: true,
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
});
|
|
341
|
+
return guard.pipe(arraySchema);
|
|
342
|
+
}
|
|
155
343
|
function getTupleRest(restSchema, ctx) {
|
|
156
344
|
if (restSchema === false) {
|
|
157
345
|
return undefined;
|
|
@@ -316,6 +504,9 @@ function convertBaseSchema(schema, ctx) {
|
|
|
316
504
|
else if (format === "credit_card") {
|
|
317
505
|
stringSchema = stringSchema.check(z.creditCard());
|
|
318
506
|
}
|
|
507
|
+
else if (format === "iban") {
|
|
508
|
+
stringSchema = stringSchema.check(z.iban());
|
|
509
|
+
}
|
|
319
510
|
else if (format === "jwt") {
|
|
320
511
|
stringSchema = stringSchema.check(z.jwt());
|
|
321
512
|
}
|
|
@@ -483,17 +674,23 @@ function convertBaseSchema(schema, ctx) {
|
|
|
483
674
|
}
|
|
484
675
|
}
|
|
485
676
|
// propertyNames constrains key *names* only, and says nothing about which keys are required or how their values validate. Layering it on top of the result keeps properties/patternProperties/additionalProperties composing underneath. `true` allows every name, so it needs no guard.
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
677
|
+
const hasKeyGuard = schema.propertyNames !== undefined && schema.propertyNames !== true;
|
|
678
|
+
const minProperties = typeof schema.minProperties === "number" ? schema.minProperties : undefined;
|
|
679
|
+
const maxProperties = typeof schema.maxProperties === "number" ? schema.maxProperties : undefined;
|
|
680
|
+
if (hasKeyGuard || minProperties !== undefined || maxProperties !== undefined) {
|
|
681
|
+
let keySchema;
|
|
682
|
+
if (hasKeyGuard) {
|
|
683
|
+
// Keys are always strings, so a propertyNames subschema that omits `type` still constrains them — without this it would convert to z.any().
|
|
684
|
+
const keyJSONSchema = typeof schema.propertyNames === "object" && schema.propertyNames.type === undefined
|
|
685
|
+
? { type: "string", ...schema.propertyNames }
|
|
686
|
+
: schema.propertyNames;
|
|
687
|
+
keySchema = convertSchema(keyJSONSchema, ctx);
|
|
688
|
+
}
|
|
689
|
+
zodSchema = checkObjectGuards(zodSchema, { keySchema, minProperties, maxProperties });
|
|
492
690
|
}
|
|
493
691
|
break;
|
|
494
692
|
}
|
|
495
693
|
case "array": {
|
|
496
|
-
// TODO: uniqueItems and contains/minContains/maxContains are not supported
|
|
497
694
|
// Check if this is a tuple (prefixItems or items as array)
|
|
498
695
|
const prefixItems = schema.prefixItems;
|
|
499
696
|
const items = schema.items;
|
|
@@ -546,6 +743,15 @@ function convertBaseSchema(schema, ctx) {
|
|
|
546
743
|
// No items specified - array of any
|
|
547
744
|
zodSchema = z.array(z.any());
|
|
548
745
|
}
|
|
746
|
+
// minContains/maxContains only constrain anything when `contains` itself is present
|
|
747
|
+
if (schema.uniqueItems === true || schema.contains !== undefined) {
|
|
748
|
+
zodSchema = checkArrayGuards(zodSchema, {
|
|
749
|
+
uniqueItems: schema.uniqueItems === true,
|
|
750
|
+
containsSchema: schema.contains !== undefined ? convertSchema(schema.contains, ctx) : undefined,
|
|
751
|
+
minContains: typeof schema.minContains === "number" ? schema.minContains : undefined,
|
|
752
|
+
maxContains: typeof schema.maxContains === "number" ? schema.maxContains : undefined,
|
|
753
|
+
});
|
|
754
|
+
}
|
|
549
755
|
break;
|
|
550
756
|
}
|
|
551
757
|
default:
|
|
@@ -613,9 +819,24 @@ function convertSchema(schema, ctx) {
|
|
|
613
819
|
extraMeta[key] = schema[key];
|
|
614
820
|
}
|
|
615
821
|
}
|
|
616
|
-
// `propertyNames`
|
|
617
|
-
if (schema.
|
|
618
|
-
|
|
822
|
+
// `propertyNames` and `contains` are enforced by a guard, which `toJSONSchema` cannot infer, so the original keyword is carried as metadata to keep the round-trip lossless. Only where it was actually applied: on any other type it is inert, and on a `$ref` the metadata would land on the target every reference shares. A carried subschema is copied verbatim while the root `$defs` stay behind, so one holding a `$ref` is dropped rather than emitted as a dangling reference.
|
|
823
|
+
if (schema.type === "object" && schema.$ref === undefined) {
|
|
824
|
+
if (schema.propertyNames !== undefined && !containsRef(schema.propertyNames)) {
|
|
825
|
+
extraMeta.propertyNames = schema.propertyNames;
|
|
826
|
+
}
|
|
827
|
+
for (const key of ["minProperties", "maxProperties"]) {
|
|
828
|
+
if (schema[key] !== undefined)
|
|
829
|
+
extraMeta[key] = schema[key];
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
if (schema.type === "array" && schema.$ref === undefined) {
|
|
833
|
+
if (schema.contains !== undefined && !containsRef(schema.contains)) {
|
|
834
|
+
extraMeta.contains = schema.contains;
|
|
835
|
+
}
|
|
836
|
+
for (const key of ["uniqueItems", "minContains", "maxContains"]) {
|
|
837
|
+
if (schema[key] !== undefined)
|
|
838
|
+
extraMeta[key] = schema[key];
|
|
839
|
+
}
|
|
619
840
|
}
|
|
620
841
|
for (const key of Object.keys(schema)) {
|
|
621
842
|
if (!RECOGNIZED_KEYS.has(key)) {
|