@willaimg/yatsp 4.3.6
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/README.md +22 -0
- package/compile.cjs +96 -0
- package/compile.d.cts +1 -0
- package/compile.d.ts +1 -0
- package/compile.js +52 -0
- package/index.cjs +52 -0
- package/index.d.cts +3 -0
- package/index.d.ts +3 -0
- package/index.js +7 -0
- package/locales/index.cjs +36 -0
- package/locales/index.d.cts +1 -0
- package/locales/index.d.ts +1 -0
- package/locales/index.js +1 -0
- package/mini/index.cjs +51 -0
- package/mini/index.d.cts +3 -0
- package/mini/index.d.ts +3 -0
- package/mini/index.js +3 -0
- package/package.json +142 -0
- package/src/compile.ts +56 -0
- package/src/index.ts +7 -0
- package/src/locales/index.ts +1 -0
- package/src/mini/index.ts +3 -0
- package/src/v3/YatspError.ts +340 -0
- package/src/v3/benchmarks/datetime.ts +58 -0
- package/src/v3/benchmarks/discriminatedUnion.ts +80 -0
- package/src/v3/benchmarks/index.ts +59 -0
- package/src/v3/benchmarks/ipv4.ts +57 -0
- package/src/v3/benchmarks/object.ts +69 -0
- package/src/v3/benchmarks/primitives.ts +162 -0
- package/src/v3/benchmarks/realworld.ts +63 -0
- package/src/v3/benchmarks/string.ts +55 -0
- package/src/v3/benchmarks/union.ts +80 -0
- package/src/v3/errors.ts +13 -0
- package/src/v3/external.ts +6 -0
- package/src/v3/helpers/enumUtil.ts +17 -0
- package/src/v3/helpers/errorUtil.ts +8 -0
- package/src/v3/helpers/parseUtil.ts +176 -0
- package/src/v3/helpers/partialUtil.ts +34 -0
- package/src/v3/helpers/typeAliases.ts +2 -0
- package/src/v3/helpers/util.ts +224 -0
- package/src/v3/index.ts +4 -0
- package/src/v3/locales/en.ts +124 -0
- package/src/v3/standard-schema.ts +113 -0
- package/src/v3/tests/Mocker.ts +54 -0
- package/src/v3/tests/all-errors.test.ts +157 -0
- package/src/v3/tests/anyunknown.test.ts +28 -0
- package/src/v3/tests/array.test.ts +71 -0
- package/src/v3/tests/async-parsing.test.ts +388 -0
- package/src/v3/tests/async-refinements.test.ts +46 -0
- package/src/v3/tests/base.test.ts +29 -0
- package/src/v3/tests/bigint.test.ts +55 -0
- package/src/v3/tests/branded.test.ts +53 -0
- package/src/v3/tests/catch.test.ts +220 -0
- package/src/v3/tests/coerce.test.ts +133 -0
- package/src/v3/tests/complex.test.ts +70 -0
- package/src/v3/tests/custom.test.ts +31 -0
- package/src/v3/tests/date.test.ts +32 -0
- package/src/v3/tests/deepmasking.test.ts +186 -0
- package/src/v3/tests/default.test.ts +112 -0
- package/src/v3/tests/description.test.ts +33 -0
- package/src/v3/tests/discriminated-unions.test.ts +315 -0
- package/src/v3/tests/enum.test.ts +80 -0
- package/src/v3/tests/error.test.ts +586 -0
- package/src/v3/tests/firstparty.test.ts +87 -0
- package/src/v3/tests/firstpartyschematypes.test.ts +21 -0
- package/src/v3/tests/function.test.ts +261 -0
- package/src/v3/tests/generics.test.ts +48 -0
- package/src/v3/tests/instanceof.test.ts +37 -0
- package/src/v3/tests/intersection.test.ts +128 -0
- package/src/v3/tests/language-server.source.ts +76 -0
- package/src/v3/tests/language-server.test.ts +206 -0
- package/src/v3/tests/literal.test.ts +36 -0
- package/src/v3/tests/map.test.ts +110 -0
- package/src/v3/tests/masking.test.ts +4 -0
- package/src/v3/tests/mocker.test.ts +19 -0
- package/src/v3/tests/nan.test.ts +24 -0
- package/src/v3/tests/nativeEnum.test.ts +87 -0
- package/src/v3/tests/nullable.test.ts +42 -0
- package/src/v3/tests/number.test.ts +176 -0
- package/src/v3/tests/object-augmentation.test.ts +29 -0
- package/src/v3/tests/object-in-es5-env.test.ts +29 -0
- package/src/v3/tests/object.test.ts +431 -0
- package/src/v3/tests/optional.test.ts +42 -0
- package/src/v3/tests/parseUtil.test.ts +23 -0
- package/src/v3/tests/parser.test.ts +41 -0
- package/src/v3/tests/partials.test.ts +243 -0
- package/src/v3/tests/pickomit.test.ts +111 -0
- package/src/v3/tests/pipeline.test.ts +29 -0
- package/src/v3/tests/preprocess.test.ts +186 -0
- package/src/v3/tests/primitive.test.ts +440 -0
- package/src/v3/tests/promise.test.ts +90 -0
- package/src/v3/tests/readonly.test.ts +114 -0
- package/src/v3/tests/record.test.ts +171 -0
- package/src/v3/tests/recursive.test.ts +197 -0
- package/src/v3/tests/refine.test.ts +313 -0
- package/src/v3/tests/safeparse.test.ts +27 -0
- package/src/v3/tests/set.test.ts +142 -0
- package/src/v3/tests/standard-schema.test.ts +83 -0
- package/src/v3/tests/string.test.ts +922 -0
- package/src/v3/tests/transformer.test.ts +233 -0
- package/src/v3/tests/tuple.test.ts +90 -0
- package/src/v3/tests/unions.test.ts +57 -0
- package/src/v3/tests/validations.test.ts +133 -0
- package/src/v3/tests/void.test.ts +15 -0
- package/src/v3/types.ts +5162 -0
- package/src/v4/classic/checks.ts +33 -0
- package/src/v4/classic/coerce.ts +27 -0
- package/src/v4/classic/compat.ts +78 -0
- package/src/v4/classic/deep-partial.ts +76 -0
- package/src/v4/classic/errors.ts +97 -0
- package/src/v4/classic/external.ts +57 -0
- package/src/v4/classic/from-json-schema.ts +944 -0
- package/src/v4/classic/in-out.ts +45 -0
- package/src/v4/classic/index.ts +5 -0
- package/src/v4/classic/iso.ts +20 -0
- package/src/v4/classic/parse.ts +88 -0
- package/src/v4/classic/schemas.ts +2994 -0
- package/src/v4/classic/tests/anyunknown.test.ts +26 -0
- package/src/v4/classic/tests/apply.test.ts +86 -0
- package/src/v4/classic/tests/array.test.ts +264 -0
- package/src/v4/classic/tests/assignability.test.ts +402 -0
- package/src/v4/classic/tests/async-parsing.test.ts +381 -0
- package/src/v4/classic/tests/async-refinements.test.ts +68 -0
- package/src/v4/classic/tests/base.test.ts +7 -0
- package/src/v4/classic/tests/bigint.test.ts +89 -0
- package/src/v4/classic/tests/brand.test.ts +106 -0
- package/src/v4/classic/tests/catch.test.ts +604 -0
- package/src/v4/classic/tests/coalesce.test.ts +20 -0
- package/src/v4/classic/tests/codec-examples.test.ts +572 -0
- package/src/v4/classic/tests/codec.test.ts +704 -0
- package/src/v4/classic/tests/coerce.test.ts +160 -0
- package/src/v4/classic/tests/continuability.test.ts +374 -0
- package/src/v4/classic/tests/credit-card.test.ts +76 -0
- package/src/v4/classic/tests/custom.test.ts +40 -0
- package/src/v4/classic/tests/cyclic-data.test.ts +985 -0
- package/src/v4/classic/tests/date.test.ts +92 -0
- package/src/v4/classic/tests/datetime.test.ts +316 -0
- package/src/v4/classic/tests/deep-partial.test.ts +380 -0
- package/src/v4/classic/tests/default-locale.test.ts +32 -0
- package/src/v4/classic/tests/default.test.ts +455 -0
- package/src/v4/classic/tests/describe-meta-checks.test.ts +27 -0
- package/src/v4/classic/tests/description.test.ts +32 -0
- package/src/v4/classic/tests/detached-methods.test.ts +226 -0
- package/src/v4/classic/tests/discriminated-unions.test.ts +942 -0
- package/src/v4/classic/tests/enum.test.ts +305 -0
- package/src/v4/classic/tests/error-utils.test.ts +917 -0
- package/src/v4/classic/tests/error.test.ts +1108 -0
- package/src/v4/classic/tests/file.test.ts +96 -0
- package/src/v4/classic/tests/firstparty.test.ts +179 -0
- package/src/v4/classic/tests/fix-json-issue.test.ts +26 -0
- package/src/v4/classic/tests/from-json-schema.test.ts +1467 -0
- package/src/v4/classic/tests/function.test.ts +391 -0
- package/src/v4/classic/tests/generics.test.ts +72 -0
- package/src/v4/classic/tests/global-config.test.ts +32 -0
- package/src/v4/classic/tests/hash.test.ts +67 -0
- package/src/v4/classic/tests/iban.test.ts +81 -0
- package/src/v4/classic/tests/in-out.test.ts +159 -0
- package/src/v4/classic/tests/index.test.ts +958 -0
- package/src/v4/classic/tests/instance-footprint.test.ts +319 -0
- package/src/v4/classic/tests/instanceof.test.ts +127 -0
- package/src/v4/classic/tests/intersection.test.ts +334 -0
- package/src/v4/classic/tests/issue-schema.test.ts +215 -0
- package/src/v4/classic/tests/jitless-allows-eval.test.ts +39 -0
- package/src/v4/classic/tests/json.test.ts +109 -0
- package/src/v4/classic/tests/lazy.test.ts +262 -0
- package/src/v4/classic/tests/literal.test.ts +128 -0
- package/src/v4/classic/tests/locales_ka.test.ts +29 -0
- package/src/v4/classic/tests/locales_ro.test.ts +24 -0
- package/src/v4/classic/tests/map.test.ts +342 -0
- package/src/v4/classic/tests/nan.test.ts +21 -0
- package/src/v4/classic/tests/nested-refine.test.ts +168 -0
- package/src/v4/classic/tests/nonoptional.test.ts +101 -0
- package/src/v4/classic/tests/nullable.test.ts +22 -0
- package/src/v4/classic/tests/number.test.ts +401 -0
- package/src/v4/classic/tests/object.test.ts +1006 -0
- package/src/v4/classic/tests/optin-ladder.test.ts +235 -0
- package/src/v4/classic/tests/optional.test.ts +366 -0
- package/src/v4/classic/tests/partial.test.ts +497 -0
- package/src/v4/classic/tests/pickomit.test.ts +211 -0
- package/src/v4/classic/tests/pipe.test.ts +101 -0
- package/src/v4/classic/tests/prefault.test.ts +144 -0
- package/src/v4/classic/tests/preprocess-types.test.ts +44 -0
- package/src/v4/classic/tests/preprocess.test.ts +351 -0
- package/src/v4/classic/tests/primitive.test.ts +175 -0
- package/src/v4/classic/tests/promise.test.ts +81 -0
- package/src/v4/classic/tests/properties.test.ts +141 -0
- package/src/v4/classic/tests/prototypes.test.ts +23 -0
- package/src/v4/classic/tests/readonly.test.ts +184 -0
- package/src/v4/classic/tests/record.test.ts +885 -0
- package/src/v4/classic/tests/recursive-types.test.ts +707 -0
- package/src/v4/classic/tests/redos.test.ts +68 -0
- package/src/v4/classic/tests/refine.test.ts +693 -0
- package/src/v4/classic/tests/registries.test.ts +243 -0
- package/src/v4/classic/tests/set.test.ts +190 -0
- package/src/v4/classic/tests/standard-schema.test.ts +153 -0
- package/src/v4/classic/tests/string-formats.test.ts +125 -0
- package/src/v4/classic/tests/string.test.ts +1440 -0
- package/src/v4/classic/tests/stringbool.test.ts +124 -0
- package/src/v4/classic/tests/template-literal.test.ts +814 -0
- package/src/v4/classic/tests/to-json-schema-methods.test.ts +438 -0
- package/src/v4/classic/tests/to-json-schema.test.ts +4733 -0
- package/src/v4/classic/tests/transform.test.ts +378 -0
- package/src/v4/classic/tests/tuple.test.ts +511 -0
- package/src/v4/classic/tests/union.test.ts +310 -0
- package/src/v4/classic/tests/url.test.ts +13 -0
- package/src/v4/classic/tests/validate.test.ts +645 -0
- package/src/v4/classic/tests/validations.test.ts +283 -0
- package/src/v4/classic/tests/void.test.ts +12 -0
- package/src/v4/core/api.ts +1913 -0
- package/src/v4/core/checks.ts +1274 -0
- package/src/v4/core/compile.ts +2315 -0
- package/src/v4/core/core.ts +230 -0
- package/src/v4/core/doc.ts +52 -0
- package/src/v4/core/errors.ts +546 -0
- package/src/v4/core/index.ts +19 -0
- package/src/v4/core/json-schema-generator.ts +131 -0
- package/src/v4/core/json-schema-processors.ts +934 -0
- package/src/v4/core/json-schema.ts +148 -0
- package/src/v4/core/memoizer.ts +331 -0
- package/src/v4/core/parse.ts +309 -0
- package/src/v4/core/regexes.ts +217 -0
- package/src/v4/core/registries.ts +109 -0
- package/src/v4/core/schemas.ts +5295 -0
- package/src/v4/core/standard-schema.ts +159 -0
- package/src/v4/core/tests/cached.test.ts +54 -0
- package/src/v4/core/tests/compile-differential.test.ts +1004 -0
- package/src/v4/core/tests/compile-generative.test.ts +445 -0
- package/src/v4/core/tests/compile-global.test.ts +89 -0
- package/src/v4/core/tests/compile-off-by-default.test.ts +26 -0
- package/src/v4/core/tests/compile-stats.test.ts +34 -0
- package/src/v4/core/tests/compile.test.ts +2040 -0
- package/src/v4/core/tests/email-regex.test.ts +140 -0
- package/src/v4/core/tests/extend.test.ts +59 -0
- package/src/v4/core/tests/index.test.ts +46 -0
- package/src/v4/core/tests/locales/be.test.ts +124 -0
- package/src/v4/core/tests/locales/el.test.ts +214 -0
- package/src/v4/core/tests/locales/en.test.ts +22 -0
- package/src/v4/core/tests/locales/es.test.ts +180 -0
- package/src/v4/core/tests/locales/fr.test.ts +72 -0
- package/src/v4/core/tests/locales/he.test.ts +379 -0
- package/src/v4/core/tests/locales/hr.test.ts +163 -0
- package/src/v4/core/tests/locales/nl.test.ts +46 -0
- package/src/v4/core/tests/locales/parity.test.ts +42 -0
- package/src/v4/core/tests/locales/ru.test.ts +128 -0
- package/src/v4/core/tests/locales/tg.test.ts +85 -0
- package/src/v4/core/tests/locales/tk.test.ts +69 -0
- package/src/v4/core/tests/locales/tr.test.ts +69 -0
- package/src/v4/core/tests/locales/uz.test.ts +105 -0
- package/src/v4/core/tests/polyfill-collision.test.ts +46 -0
- package/src/v4/core/tests/record-constructor.test.ts +125 -0
- package/src/v4/core/tests/recursive-tuples.test.ts +45 -0
- package/src/v4/core/tests/treeshake.test.ts +51 -0
- package/src/v4/core/tests/url-no-canparse.test.ts +133 -0
- package/src/v4/core/to-json-schema.ts +857 -0
- package/src/v4/core/util.ts +1284 -0
- package/src/v4/core/versions.ts +5 -0
- package/src/v4/core/visit.ts +193 -0
- package/src/v4/core/zsf.ts +323 -0
- package/src/v4/index.ts +4 -0
- package/src/v4/locales/ar.ts +120 -0
- package/src/v4/locales/az.ts +116 -0
- package/src/v4/locales/be.ts +188 -0
- package/src/v4/locales/bg.ts +133 -0
- package/src/v4/locales/bn.ts +123 -0
- package/src/v4/locales/ca.ts +121 -0
- package/src/v4/locales/ckb.ts +140 -0
- package/src/v4/locales/cs.ts +123 -0
- package/src/v4/locales/da.ts +128 -0
- package/src/v4/locales/de.ts +121 -0
- package/src/v4/locales/el.ts +124 -0
- package/src/v4/locales/en.ts +136 -0
- package/src/v4/locales/eo.ts +123 -0
- package/src/v4/locales/es.ts +145 -0
- package/src/v4/locales/fa.ts +131 -0
- package/src/v4/locales/fi.ts +126 -0
- package/src/v4/locales/fr-CA.ts +121 -0
- package/src/v4/locales/fr.ts +139 -0
- package/src/v4/locales/gu.ts +123 -0
- package/src/v4/locales/he.ts +253 -0
- package/src/v4/locales/hi.ts +126 -0
- package/src/v4/locales/hr.ts +135 -0
- package/src/v4/locales/hu.ts +122 -0
- package/src/v4/locales/hy.ts +175 -0
- package/src/v4/locales/id.ts +120 -0
- package/src/v4/locales/index.ts +63 -0
- package/src/v4/locales/is.ts +124 -0
- package/src/v4/locales/it.ts +121 -0
- package/src/v4/locales/ja.ts +119 -0
- package/src/v4/locales/ka.ts +128 -0
- package/src/v4/locales/kh.ts +7 -0
- package/src/v4/locales/km.ts +124 -0
- package/src/v4/locales/kn.ts +126 -0
- package/src/v4/locales/ko.ts +126 -0
- package/src/v4/locales/lt.ts +243 -0
- package/src/v4/locales/mk.ts +123 -0
- package/src/v4/locales/ms.ts +120 -0
- package/src/v4/locales/ne.ts +128 -0
- package/src/v4/locales/nl.ts +126 -0
- package/src/v4/locales/nn.ts +121 -0
- package/src/v4/locales/no.ts +121 -0
- package/src/v4/locales/ota.ts +122 -0
- package/src/v4/locales/pl.ts +123 -0
- package/src/v4/locales/ps.ts +131 -0
- package/src/v4/locales/pt-BR.ts +161 -0
- package/src/v4/locales/pt.ts +159 -0
- package/src/v4/locales/ro.ts +132 -0
- package/src/v4/locales/ru.ts +188 -0
- package/src/v4/locales/sk.ts +123 -0
- package/src/v4/locales/sl.ts +123 -0
- package/src/v4/locales/sv.ts +124 -0
- package/src/v4/locales/ta.ts +123 -0
- package/src/v4/locales/tg.ts +135 -0
- package/src/v4/locales/th.ts +124 -0
- package/src/v4/locales/tk.ts +113 -0
- package/src/v4/locales/tr.ts +116 -0
- package/src/v4/locales/ua.ts +7 -0
- package/src/v4/locales/uk.ts +122 -0
- package/src/v4/locales/ur.ts +124 -0
- package/src/v4/locales/uz.ts +120 -0
- package/src/v4/locales/vi.ts +122 -0
- package/src/v4/locales/yo.ts +129 -0
- package/src/v4/locales/zh-CN.ts +121 -0
- package/src/v4/locales/zh-TW.ts +120 -0
- package/src/v4/mini/checks.ts +33 -0
- package/src/v4/mini/coerce.ts +27 -0
- package/src/v4/mini/deep-partial.ts +73 -0
- package/src/v4/mini/external.ts +52 -0
- package/src/v4/mini/in-out.ts +45 -0
- package/src/v4/mini/index.ts +3 -0
- package/src/v4/mini/iso.ts +66 -0
- package/src/v4/mini/parse.ts +16 -0
- package/src/v4/mini/schemas.ts +2093 -0
- package/src/v4/mini/tests/apply.test.ts +57 -0
- package/src/v4/mini/tests/assignability.test.ts +190 -0
- package/src/v4/mini/tests/brand.test.ts +94 -0
- package/src/v4/mini/tests/checks.test.ts +158 -0
- package/src/v4/mini/tests/codec.test.ts +582 -0
- package/src/v4/mini/tests/computed.test.ts +30 -0
- package/src/v4/mini/tests/cyclic-data.test.ts +84 -0
- package/src/v4/mini/tests/deep-partial.test.ts +54 -0
- package/src/v4/mini/tests/error.test.ts +41 -0
- package/src/v4/mini/tests/functions.test.ts +14 -0
- package/src/v4/mini/tests/index.test.ts +1148 -0
- package/src/v4/mini/tests/number.test.ts +109 -0
- package/src/v4/mini/tests/object.test.ts +292 -0
- package/src/v4/mini/tests/prototypes.test.ts +43 -0
- package/src/v4/mini/tests/recursive-types.test.ts +341 -0
- package/src/v4/mini/tests/standard-schema.test.ts +50 -0
- package/src/v4/mini/tests/string.test.ts +396 -0
- package/src/v4-mini/index.ts +3 -0
- package/v3/YatspError.cjs +167 -0
- package/v3/YatspError.d.cts +164 -0
- package/v3/YatspError.d.ts +164 -0
- package/v3/YatspError.js +143 -0
- package/v3/errors.cjs +36 -0
- package/v3/errors.d.cts +5 -0
- package/v3/errors.d.ts +5 -0
- package/v3/errors.js +9 -0
- package/v3/external.cjs +41 -0
- package/v3/external.d.cts +6 -0
- package/v3/external.d.ts +6 -0
- package/v3/external.js +6 -0
- package/v3/helpers/enumUtil.cjs +21 -0
- package/v3/helpers/enumUtil.d.cts +8 -0
- package/v3/helpers/enumUtil.d.ts +8 -0
- package/v3/helpers/enumUtil.js +1 -0
- package/v3/helpers/errorUtil.cjs +28 -0
- package/v3/helpers/errorUtil.d.cts +9 -0
- package/v3/helpers/errorUtil.d.ts +9 -0
- package/v3/helpers/errorUtil.js +6 -0
- package/v3/helpers/parseUtil.cjs +143 -0
- package/v3/helpers/parseUtil.d.cts +78 -0
- package/v3/helpers/parseUtil.d.ts +78 -0
- package/v3/helpers/parseUtil.js +109 -0
- package/v3/helpers/partialUtil.cjs +21 -0
- package/v3/helpers/partialUtil.d.cts +8 -0
- package/v3/helpers/partialUtil.d.ts +8 -0
- package/v3/helpers/partialUtil.js +1 -0
- package/v3/helpers/typeAliases.cjs +21 -0
- package/v3/helpers/typeAliases.d.cts +2 -0
- package/v3/helpers/typeAliases.d.ts +2 -0
- package/v3/helpers/typeAliases.js +1 -0
- package/v3/helpers/util.cjs +156 -0
- package/v3/helpers/util.d.cts +85 -0
- package/v3/helpers/util.d.ts +85 -0
- package/v3/helpers/util.js +133 -0
- package/v3/index.cjs +52 -0
- package/v3/index.d.cts +4 -0
- package/v3/index.d.ts +4 -0
- package/v3/index.js +4 -0
- package/v3/locales/en.cjs +112 -0
- package/v3/locales/en.d.cts +3 -0
- package/v3/locales/en.d.ts +3 -0
- package/v3/locales/en.js +109 -0
- package/v3/standard-schema.cjs +21 -0
- package/v3/standard-schema.d.cts +102 -0
- package/v3/standard-schema.d.ts +102 -0
- package/v3/standard-schema.js +1 -0
- package/v3/types.cjs +3798 -0
- package/v3/types.d.cts +1034 -0
- package/v3/types.d.ts +1034 -0
- package/v3/types.js +3697 -0
- package/v4/classic/checks.cjs +53 -0
- package/v4/classic/checks.d.cts +1 -0
- package/v4/classic/checks.d.ts +1 -0
- package/v4/classic/checks.js +1 -0
- package/v4/classic/coerce.cjs +66 -0
- package/v4/classic/coerce.d.cts +17 -0
- package/v4/classic/coerce.d.ts +17 -0
- package/v4/classic/coerce.js +17 -0
- package/v4/classic/compat.cjs +80 -0
- package/v4/classic/compat.d.cts +48 -0
- package/v4/classic/compat.d.ts +48 -0
- package/v4/classic/compat.js +31 -0
- package/v4/classic/deep-partial.cjs +58 -0
- package/v4/classic/deep-partial.d.cts +14 -0
- package/v4/classic/deep-partial.d.ts +14 -0
- package/v4/classic/deep-partial.js +13 -0
- package/v4/classic/errors.cjs +102 -0
- package/v4/classic/errors.d.cts +29 -0
- package/v4/classic/errors.d.ts +29 -0
- package/v4/classic/errors.js +57 -0
- package/v4/classic/external.cjs +97 -0
- package/v4/classic/external.d.cts +19 -0
- package/v4/classic/external.d.ts +19 -0
- package/v4/classic/external.js +17 -0
- package/v4/classic/from-json-schema.cjs +926 -0
- package/v4/classic/from-json-schema.d.cts +12 -0
- package/v4/classic/from-json-schema.d.ts +12 -0
- package/v4/classic/from-json-schema.js +881 -0
- package/v4/classic/in-out.cjs +82 -0
- package/v4/classic/in-out.d.cts +8 -0
- package/v4/classic/in-out.d.ts +8 -0
- package/v4/classic/in-out.js +36 -0
- package/v4/classic/index.cjs +52 -0
- package/v4/classic/index.d.cts +3 -0
- package/v4/classic/index.d.ts +3 -0
- package/v4/classic/index.js +4 -0
- package/v4/classic/iso.cjs +68 -0
- package/v4/classic/iso.d.cts +7 -0
- package/v4/classic/iso.d.ts +7 -0
- package/v4/classic/iso.js +15 -0
- package/v4/classic/parse.cjs +63 -0
- package/v4/classic/parse.d.cts +44 -0
- package/v4/classic/parse.d.ts +44 -0
- package/v4/classic/parse.js +16 -0
- package/v4/classic/schemas.cjs +1676 -0
- package/v4/classic/schemas.d.cts +816 -0
- package/v4/classic/schemas.d.ts +816 -0
- package/v4/classic/schemas.js +1535 -0
- package/v4/core/api.cjs +1269 -0
- package/v4/core/api.d.cts +335 -0
- package/v4/core/api.d.ts +335 -0
- package/v4/core/api.js +1107 -0
- package/v4/core/checks.cjs +561 -0
- package/v4/core/checks.d.cts +294 -0
- package/v4/core/checks.d.ts +294 -0
- package/v4/core/checks.js +516 -0
- package/v4/core/compile.cjs +1908 -0
- package/v4/core/compile.d.cts +65 -0
- package/v4/core/compile.d.ts +65 -0
- package/v4/core/compile.js +1858 -0
- package/v4/core/core.cjs +167 -0
- package/v4/core/core.d.cts +82 -0
- package/v4/core/core.d.ts +82 -0
- package/v4/core/core.js +141 -0
- package/v4/core/doc.cjs +61 -0
- package/v4/core/doc.d.cts +16 -0
- package/v4/core/doc.d.ts +16 -0
- package/v4/core/doc.js +38 -0
- package/v4/core/errors.cjs +317 -0
- package/v4/core/errors.d.cts +225 -0
- package/v4/core/errors.d.ts +225 -0
- package/v4/core/errors.js +267 -0
- package/v4/core/index.cjs +70 -0
- package/v4/core/index.d.cts +19 -0
- package/v4/core/index.d.ts +19 -0
- package/v4/core/index.js +19 -0
- package/v4/core/json-schema-generator.cjs +122 -0
- package/v4/core/json-schema-generator.d.cts +65 -0
- package/v4/core/json-schema-generator.d.ts +65 -0
- package/v4/core/json-schema-generator.js +99 -0
- package/v4/core/json-schema-processors.cjs +929 -0
- package/v4/core/json-schema-processors.d.cts +65 -0
- package/v4/core/json-schema-processors.d.ts +65 -0
- package/v4/core/json-schema-processors.js +843 -0
- package/v4/core/json-schema.cjs +21 -0
- package/v4/core/json-schema.d.cts +89 -0
- package/v4/core/json-schema.d.ts +89 -0
- package/v4/core/json-schema.js +1 -0
- package/v4/core/memoizer.cjs +360 -0
- package/v4/core/memoizer.d.cts +15 -0
- package/v4/core/memoizer.d.ts +15 -0
- package/v4/core/memoizer.js +311 -0
- package/v4/core/parse.cjs +233 -0
- package/v4/core/parse.d.cts +52 -0
- package/v4/core/parse.d.ts +52 -0
- package/v4/core/parse.js +175 -0
- package/v4/core/regexes.cjs +209 -0
- package/v4/core/regexes.d.cts +90 -0
- package/v4/core/regexes.d.ts +90 -0
- package/v4/core/regexes.js +156 -0
- package/v4/core/registries.cjs +76 -0
- package/v4/core/registries.d.cts +35 -0
- package/v4/core/registries.d.ts +35 -0
- package/v4/core/registries.js +52 -0
- package/v4/core/schemas.cjs +2669 -0
- package/v4/core/schemas.d.cts +1273 -0
- package/v4/core/schemas.d.ts +1273 -0
- package/v4/core/schemas.js +2606 -0
- package/v4/core/standard-schema.cjs +21 -0
- package/v4/core/standard-schema.d.cts +126 -0
- package/v4/core/standard-schema.d.ts +126 -0
- package/v4/core/standard-schema.js +1 -0
- package/v4/core/to-json-schema.cjs +681 -0
- package/v4/core/to-json-schema.d.cts +154 -0
- package/v4/core/to-json-schema.d.ts +154 -0
- package/v4/core/to-json-schema.js +651 -0
- package/v4/core/util.cjs +949 -0
- package/v4/core/util.d.cts +278 -0
- package/v4/core/util.d.ts +278 -0
- package/v4/core/util.js +859 -0
- package/v4/core/versions.cjs +27 -0
- package/v4/core/versions.d.cts +5 -0
- package/v4/core/versions.d.ts +5 -0
- package/v4/core/versions.js +5 -0
- package/v4/core/visit.cjs +214 -0
- package/v4/core/visit.d.cts +30 -0
- package/v4/core/visit.d.ts +30 -0
- package/v4/core/visit.js +169 -0
- package/v4/index.cjs +51 -0
- package/v4/index.d.cts +3 -0
- package/v4/index.d.ts +3 -0
- package/v4/index.js +4 -0
- package/v4/locales/ar.cjs +138 -0
- package/v4/locales/ar.d.cts +5 -0
- package/v4/locales/ar.d.ts +4 -0
- package/v4/locales/ar.js +111 -0
- package/v4/locales/az.cjs +137 -0
- package/v4/locales/az.d.cts +5 -0
- package/v4/locales/az.d.ts +4 -0
- package/v4/locales/az.js +110 -0
- package/v4/locales/be.cjs +195 -0
- package/v4/locales/be.d.cts +5 -0
- package/v4/locales/be.d.ts +4 -0
- package/v4/locales/be.js +168 -0
- package/v4/locales/bg.cjs +152 -0
- package/v4/locales/bg.d.cts +5 -0
- package/v4/locales/bg.d.ts +4 -0
- package/v4/locales/bg.js +125 -0
- package/v4/locales/bn.cjs +140 -0
- package/v4/locales/bn.d.cts +5 -0
- package/v4/locales/bn.d.ts +4 -0
- package/v4/locales/bn.js +113 -0
- package/v4/locales/ca.cjs +139 -0
- package/v4/locales/ca.d.cts +5 -0
- package/v4/locales/ca.d.ts +4 -0
- package/v4/locales/ca.js +112 -0
- package/v4/locales/ckb.cjs +160 -0
- package/v4/locales/ckb.d.cts +5 -0
- package/v4/locales/ckb.d.ts +4 -0
- package/v4/locales/ckb.js +133 -0
- package/v4/locales/cs.cjs +143 -0
- package/v4/locales/cs.d.cts +5 -0
- package/v4/locales/cs.d.ts +4 -0
- package/v4/locales/cs.js +116 -0
- package/v4/locales/da.cjs +147 -0
- package/v4/locales/da.d.cts +5 -0
- package/v4/locales/da.d.ts +4 -0
- package/v4/locales/da.js +120 -0
- package/v4/locales/de.cjs +140 -0
- package/v4/locales/de.d.cts +5 -0
- package/v4/locales/de.d.ts +4 -0
- package/v4/locales/de.js +113 -0
- package/v4/locales/el.cjs +139 -0
- package/v4/locales/el.d.cts +5 -0
- package/v4/locales/el.d.ts +4 -0
- package/v4/locales/el.js +112 -0
- package/v4/locales/en.cjs +152 -0
- package/v4/locales/en.d.cts +5 -0
- package/v4/locales/en.d.ts +4 -0
- package/v4/locales/en.js +125 -0
- package/v4/locales/eo.cjs +141 -0
- package/v4/locales/eo.d.cts +5 -0
- package/v4/locales/eo.d.ts +4 -0
- package/v4/locales/eo.js +114 -0
- package/v4/locales/es.cjs +163 -0
- package/v4/locales/es.d.cts +5 -0
- package/v4/locales/es.d.ts +4 -0
- package/v4/locales/es.js +136 -0
- package/v4/locales/fa.cjs +146 -0
- package/v4/locales/fa.d.cts +5 -0
- package/v4/locales/fa.d.ts +4 -0
- package/v4/locales/fa.js +119 -0
- package/v4/locales/fi.cjs +144 -0
- package/v4/locales/fi.d.cts +5 -0
- package/v4/locales/fi.d.ts +4 -0
- package/v4/locales/fi.js +117 -0
- package/v4/locales/fr-CA.cjs +139 -0
- package/v4/locales/fr-CA.d.cts +5 -0
- package/v4/locales/fr-CA.d.ts +4 -0
- package/v4/locales/fr-CA.js +112 -0
- package/v4/locales/fr.cjs +156 -0
- package/v4/locales/fr.d.cts +5 -0
- package/v4/locales/fr.d.ts +4 -0
- package/v4/locales/fr.js +129 -0
- package/v4/locales/gu.cjs +140 -0
- package/v4/locales/gu.d.cts +5 -0
- package/v4/locales/gu.d.ts +4 -0
- package/v4/locales/gu.js +113 -0
- package/v4/locales/he.cjs +248 -0
- package/v4/locales/he.d.cts +5 -0
- package/v4/locales/he.d.ts +4 -0
- package/v4/locales/he.js +221 -0
- package/v4/locales/hi.cjs +138 -0
- package/v4/locales/hi.d.cts +5 -0
- package/v4/locales/hi.d.ts +4 -0
- package/v4/locales/hi.js +111 -0
- package/v4/locales/hr.cjs +153 -0
- package/v4/locales/hr.d.cts +5 -0
- package/v4/locales/hr.d.ts +4 -0
- package/v4/locales/hr.js +126 -0
- package/v4/locales/hu.cjs +140 -0
- package/v4/locales/hu.d.cts +5 -0
- package/v4/locales/hu.d.ts +4 -0
- package/v4/locales/hu.js +113 -0
- package/v4/locales/hy.cjs +185 -0
- package/v4/locales/hy.d.cts +5 -0
- package/v4/locales/hy.d.ts +4 -0
- package/v4/locales/hy.js +158 -0
- package/v4/locales/id.cjs +138 -0
- package/v4/locales/id.d.cts +5 -0
- package/v4/locales/id.d.ts +4 -0
- package/v4/locales/id.js +111 -0
- package/v4/locales/index.cjs +152 -0
- package/v4/locales/index.d.cts +63 -0
- package/v4/locales/index.d.ts +63 -0
- package/v4/locales/index.js +63 -0
- package/v4/locales/is.cjs +141 -0
- package/v4/locales/is.d.cts +5 -0
- package/v4/locales/is.d.ts +4 -0
- package/v4/locales/is.js +114 -0
- package/v4/locales/it.cjs +140 -0
- package/v4/locales/it.d.cts +5 -0
- package/v4/locales/it.d.ts +4 -0
- package/v4/locales/it.js +113 -0
- package/v4/locales/ja.cjs +139 -0
- package/v4/locales/ja.d.cts +5 -0
- package/v4/locales/ja.d.ts +4 -0
- package/v4/locales/ja.js +112 -0
- package/v4/locales/ka.cjs +144 -0
- package/v4/locales/ka.d.cts +5 -0
- package/v4/locales/ka.d.ts +4 -0
- package/v4/locales/ka.js +117 -0
- package/v4/locales/kh.cjs +12 -0
- package/v4/locales/kh.d.cts +5 -0
- package/v4/locales/kh.d.ts +5 -0
- package/v4/locales/kh.js +5 -0
- package/v4/locales/km.cjs +142 -0
- package/v4/locales/km.d.cts +5 -0
- package/v4/locales/km.d.ts +4 -0
- package/v4/locales/km.js +115 -0
- package/v4/locales/kn.cjs +143 -0
- package/v4/locales/kn.d.cts +5 -0
- package/v4/locales/kn.d.ts +4 -0
- package/v4/locales/kn.js +116 -0
- package/v4/locales/ko.cjs +143 -0
- package/v4/locales/ko.d.cts +5 -0
- package/v4/locales/ko.d.ts +4 -0
- package/v4/locales/ko.js +116 -0
- package/v4/locales/lt.cjs +234 -0
- package/v4/locales/lt.d.cts +5 -0
- package/v4/locales/lt.d.ts +4 -0
- package/v4/locales/lt.js +207 -0
- package/v4/locales/mk.cjs +141 -0
- package/v4/locales/mk.d.cts +5 -0
- package/v4/locales/mk.d.ts +4 -0
- package/v4/locales/mk.js +114 -0
- package/v4/locales/ms.cjs +139 -0
- package/v4/locales/ms.d.cts +5 -0
- package/v4/locales/ms.d.ts +4 -0
- package/v4/locales/ms.js +112 -0
- package/v4/locales/ne.cjs +138 -0
- package/v4/locales/ne.d.cts +5 -0
- package/v4/locales/ne.d.ts +4 -0
- package/v4/locales/ne.js +111 -0
- package/v4/locales/nl.cjs +142 -0
- package/v4/locales/nl.d.cts +5 -0
- package/v4/locales/nl.d.ts +4 -0
- package/v4/locales/nl.js +115 -0
- package/v4/locales/nn.cjs +140 -0
- package/v4/locales/nn.d.cts +5 -0
- package/v4/locales/nn.d.ts +4 -0
- package/v4/locales/nn.js +113 -0
- package/v4/locales/no.cjs +140 -0
- package/v4/locales/no.d.cts +5 -0
- package/v4/locales/no.d.ts +4 -0
- package/v4/locales/no.js +113 -0
- package/v4/locales/ota.cjs +141 -0
- package/v4/locales/ota.d.cts +5 -0
- package/v4/locales/ota.d.ts +4 -0
- package/v4/locales/ota.js +114 -0
- package/v4/locales/pl.cjs +141 -0
- package/v4/locales/pl.d.cts +5 -0
- package/v4/locales/pl.d.ts +4 -0
- package/v4/locales/pl.js +114 -0
- package/v4/locales/ps.cjs +146 -0
- package/v4/locales/ps.d.cts +5 -0
- package/v4/locales/ps.d.ts +4 -0
- package/v4/locales/ps.js +119 -0
- package/v4/locales/pt-BR.cjs +170 -0
- package/v4/locales/pt-BR.d.cts +5 -0
- package/v4/locales/pt-BR.d.ts +4 -0
- package/v4/locales/pt-BR.js +143 -0
- package/v4/locales/pt.cjs +169 -0
- package/v4/locales/pt.d.cts +5 -0
- package/v4/locales/pt.d.ts +4 -0
- package/v4/locales/pt.js +142 -0
- package/v4/locales/ro.cjs +149 -0
- package/v4/locales/ro.d.cts +5 -0
- package/v4/locales/ro.d.ts +4 -0
- package/v4/locales/ro.js +122 -0
- package/v4/locales/ru.cjs +195 -0
- package/v4/locales/ru.d.cts +5 -0
- package/v4/locales/ru.d.ts +4 -0
- package/v4/locales/ru.js +168 -0
- package/v4/locales/sk.cjs +143 -0
- package/v4/locales/sk.d.cts +5 -0
- package/v4/locales/sk.d.ts +4 -0
- package/v4/locales/sk.js +116 -0
- package/v4/locales/sl.cjs +141 -0
- package/v4/locales/sl.d.cts +5 -0
- package/v4/locales/sl.d.ts +4 -0
- package/v4/locales/sl.js +114 -0
- package/v4/locales/sv.cjs +142 -0
- package/v4/locales/sv.d.cts +5 -0
- package/v4/locales/sv.d.ts +4 -0
- package/v4/locales/sv.js +115 -0
- package/v4/locales/ta.cjs +142 -0
- package/v4/locales/ta.d.cts +5 -0
- package/v4/locales/ta.d.ts +4 -0
- package/v4/locales/ta.js +115 -0
- package/v4/locales/tg.cjs +144 -0
- package/v4/locales/tg.d.cts +5 -0
- package/v4/locales/tg.d.ts +4 -0
- package/v4/locales/tg.js +117 -0
- package/v4/locales/th.cjs +142 -0
- package/v4/locales/th.d.cts +5 -0
- package/v4/locales/th.d.ts +4 -0
- package/v4/locales/th.js +115 -0
- package/v4/locales/tk.cjs +134 -0
- package/v4/locales/tk.d.cts +5 -0
- package/v4/locales/tk.d.ts +4 -0
- package/v4/locales/tk.js +107 -0
- package/v4/locales/tr.cjs +137 -0
- package/v4/locales/tr.d.cts +5 -0
- package/v4/locales/tr.d.ts +4 -0
- package/v4/locales/tr.js +110 -0
- package/v4/locales/ua.cjs +12 -0
- package/v4/locales/ua.d.cts +5 -0
- package/v4/locales/ua.d.ts +5 -0
- package/v4/locales/ua.js +5 -0
- package/v4/locales/uk.cjs +140 -0
- package/v4/locales/uk.d.cts +5 -0
- package/v4/locales/uk.d.ts +4 -0
- package/v4/locales/uk.js +113 -0
- package/v4/locales/ur.cjs +142 -0
- package/v4/locales/ur.d.cts +5 -0
- package/v4/locales/ur.d.ts +4 -0
- package/v4/locales/ur.js +115 -0
- package/v4/locales/uz.cjs +140 -0
- package/v4/locales/uz.d.cts +5 -0
- package/v4/locales/uz.d.ts +4 -0
- package/v4/locales/uz.js +113 -0
- package/v4/locales/vi.cjs +140 -0
- package/v4/locales/vi.d.cts +5 -0
- package/v4/locales/vi.d.ts +4 -0
- package/v4/locales/vi.js +113 -0
- package/v4/locales/yo.cjs +139 -0
- package/v4/locales/yo.d.cts +5 -0
- package/v4/locales/yo.d.ts +4 -0
- package/v4/locales/yo.js +112 -0
- package/v4/locales/zh-CN.cjs +141 -0
- package/v4/locales/zh-CN.d.cts +5 -0
- package/v4/locales/zh-CN.d.ts +4 -0
- package/v4/locales/zh-CN.js +114 -0
- package/v4/locales/zh-TW.cjs +139 -0
- package/v4/locales/zh-TW.d.cts +5 -0
- package/v4/locales/zh-TW.d.ts +4 -0
- package/v4/locales/zh-TW.js +112 -0
- package/v4/mini/checks.cjs +54 -0
- package/v4/mini/checks.d.cts +1 -0
- package/v4/mini/checks.d.ts +1 -0
- package/v4/mini/checks.js +1 -0
- package/v4/mini/coerce.cjs +71 -0
- package/v4/mini/coerce.d.cts +7 -0
- package/v4/mini/coerce.d.ts +7 -0
- package/v4/mini/coerce.js +22 -0
- package/v4/mini/deep-partial.cjs +58 -0
- package/v4/mini/deep-partial.d.cts +14 -0
- package/v4/mini/deep-partial.d.ts +14 -0
- package/v4/mini/deep-partial.js +13 -0
- package/v4/mini/external.cjs +95 -0
- package/v4/mini/external.d.cts +16 -0
- package/v4/mini/external.d.ts +16 -0
- package/v4/mini/external.js +16 -0
- package/v4/mini/in-out.cjs +82 -0
- package/v4/mini/in-out.d.cts +8 -0
- package/v4/mini/in-out.d.ts +8 -0
- package/v4/mini/in-out.js +36 -0
- package/v4/mini/index.cjs +51 -0
- package/v4/mini/index.d.cts +3 -0
- package/v4/mini/index.d.ts +3 -0
- package/v4/mini/index.js +3 -0
- package/v4/mini/iso.cjs +83 -0
- package/v4/mini/iso.d.cts +22 -0
- package/v4/mini/iso.d.ts +22 -0
- package/v4/mini/iso.js +34 -0
- package/v4/mini/parse.cjs +37 -0
- package/v4/mini/parse.d.cts +1 -0
- package/v4/mini/parse.d.ts +1 -0
- package/v4/mini/parse.js +1 -0
- package/v4/mini/schemas.cjs +1149 -0
- package/v4/mini/schemas.d.cts +480 -0
- package/v4/mini/schemas.d.ts +480 -0
- package/v4/mini/schemas.js +1004 -0
- package/v4-mini/index.cjs +51 -0
- package/v4-mini/index.d.cts +3 -0
- package/v4-mini/index.d.ts +3 -0
- package/v4-mini/index.js +3 -0
|
@@ -0,0 +1,1467 @@
|
|
|
1
|
+
import { expect, test } from "vitest";
|
|
2
|
+
import { fromJSONSchema } from "../from-json-schema.js";
|
|
3
|
+
import * as z from "../index.js";
|
|
4
|
+
|
|
5
|
+
test("basic string schema", () => {
|
|
6
|
+
const schema = fromJSONSchema({ type: "string" });
|
|
7
|
+
expect(schema.parse("hello")).toBe("hello");
|
|
8
|
+
expect(() => schema.parse(123)).toThrow();
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
test("string with constraints", () => {
|
|
12
|
+
const schema = fromJSONSchema({
|
|
13
|
+
type: "string",
|
|
14
|
+
minLength: 3,
|
|
15
|
+
maxLength: 10,
|
|
16
|
+
pattern: "^[a-z]+$",
|
|
17
|
+
});
|
|
18
|
+
expect(schema.parse("hello")).toBe("hello");
|
|
19
|
+
expect(schema.parse("helloworld")).toBe("helloworld"); // exactly 10 chars - valid
|
|
20
|
+
expect(() => schema.parse("hi")).toThrow(); // too short
|
|
21
|
+
expect(() => schema.parse("helloworld1")).toThrow(); // too long (11 chars)
|
|
22
|
+
expect(() => schema.parse("Hello")).toThrow(); // pattern mismatch
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test("pattern is not implicitly anchored", () => {
|
|
26
|
+
// JSON Schema patterns match anywhere in the string, not just the full string
|
|
27
|
+
const schema = fromJSONSchema({
|
|
28
|
+
type: "string",
|
|
29
|
+
pattern: "foo",
|
|
30
|
+
});
|
|
31
|
+
expect(schema.parse("foo")).toBe("foo");
|
|
32
|
+
expect(schema.parse("foobar")).toBe("foobar"); // matches at start
|
|
33
|
+
expect(schema.parse("barfoo")).toBe("barfoo"); // matches at end
|
|
34
|
+
expect(schema.parse("barfoobar")).toBe("barfoobar"); // matches in middle
|
|
35
|
+
expect(() => schema.parse("bar")).toThrow(); // no match
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test("number schema", () => {
|
|
39
|
+
const schema = fromJSONSchema({ type: "number" });
|
|
40
|
+
expect(schema.parse(42)).toBe(42);
|
|
41
|
+
expect(() => schema.parse("42")).toThrow();
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test("number with constraints", () => {
|
|
45
|
+
const schema = fromJSONSchema({
|
|
46
|
+
type: "number",
|
|
47
|
+
minimum: 0,
|
|
48
|
+
maximum: 100,
|
|
49
|
+
multipleOf: 5,
|
|
50
|
+
});
|
|
51
|
+
expect(schema.parse(50)).toBe(50);
|
|
52
|
+
expect(() => schema.parse(-1)).toThrow();
|
|
53
|
+
expect(() => schema.parse(101)).toThrow();
|
|
54
|
+
expect(() => schema.parse(47)).toThrow(); // not multiple of 5
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test("draft-04 boolean exclusive bounds do not emit a redundant inclusive bound", () => {
|
|
58
|
+
const schema = fromJSONSchema({
|
|
59
|
+
type: "number",
|
|
60
|
+
minimum: 5,
|
|
61
|
+
maximum: 10,
|
|
62
|
+
exclusiveMinimum: true,
|
|
63
|
+
exclusiveMaximum: true,
|
|
64
|
+
});
|
|
65
|
+
// exclusive on both ends: boundaries rejected, interior accepted
|
|
66
|
+
expect(() => schema.parse(5)).toThrow();
|
|
67
|
+
expect(() => schema.parse(10)).toThrow();
|
|
68
|
+
expect(schema.parse(7)).toBe(7);
|
|
69
|
+
// the dominated inclusive bound would otherwise report a second, weaker issue alongside the exclusive one
|
|
70
|
+
expect(schema.safeParse(4).error!.issues).toHaveLength(1);
|
|
71
|
+
expect(schema.safeParse(11).error!.issues).toHaveLength(1);
|
|
72
|
+
// the inclusive .min()/.max() must be skipped so only the exclusive checks remain
|
|
73
|
+
const checks = (schema as any)._yatsp.def.checks.map((c: any) => [
|
|
74
|
+
c._yatsp.def.check,
|
|
75
|
+
c._yatsp.def.value,
|
|
76
|
+
c._yatsp.def.inclusive,
|
|
77
|
+
]);
|
|
78
|
+
expect(checks).toEqual([
|
|
79
|
+
["greater_than", 5, false],
|
|
80
|
+
["less_than", 10, false],
|
|
81
|
+
]);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test("integer schema", () => {
|
|
85
|
+
const schema = fromJSONSchema({ type: "integer" });
|
|
86
|
+
expect(schema.parse(42)).toBe(42);
|
|
87
|
+
expect(() => schema.parse(42.5)).toThrow();
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test("boolean schema", () => {
|
|
91
|
+
const schema = fromJSONSchema({ type: "boolean" });
|
|
92
|
+
expect(schema.parse(true)).toBe(true);
|
|
93
|
+
expect(schema.parse(false)).toBe(false);
|
|
94
|
+
expect(() => schema.parse("true")).toThrow();
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test("null schema", () => {
|
|
98
|
+
const schema = fromJSONSchema({ type: "null" });
|
|
99
|
+
expect(schema.parse(null)).toBe(null);
|
|
100
|
+
expect(() => schema.parse(undefined)).toThrow();
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
test("object schema", () => {
|
|
104
|
+
const schema = fromJSONSchema({
|
|
105
|
+
type: "object",
|
|
106
|
+
properties: {
|
|
107
|
+
name: { type: "string" },
|
|
108
|
+
age: { type: "number" },
|
|
109
|
+
},
|
|
110
|
+
required: ["name"],
|
|
111
|
+
});
|
|
112
|
+
expect(schema.parse({ name: "John", age: 30 })).toEqual({ name: "John", age: 30 });
|
|
113
|
+
expect(schema.parse({ name: "John" })).toEqual({ name: "John" });
|
|
114
|
+
expect(() => schema.parse({ age: 30 })).toThrow(); // missing required
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
test("object with additionalProperties false", () => {
|
|
118
|
+
const schema = fromJSONSchema({
|
|
119
|
+
type: "object",
|
|
120
|
+
properties: {
|
|
121
|
+
name: { type: "string" },
|
|
122
|
+
},
|
|
123
|
+
additionalProperties: false,
|
|
124
|
+
});
|
|
125
|
+
expect(schema.parse({ name: "John" })).toEqual({ name: "John" });
|
|
126
|
+
expect(() => schema.parse({ name: "John", extra: "field" })).toThrow();
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
test("array schema", () => {
|
|
130
|
+
const schema = fromJSONSchema({
|
|
131
|
+
type: "array",
|
|
132
|
+
items: { type: "string" },
|
|
133
|
+
});
|
|
134
|
+
expect(schema.parse(["a", "b", "c"])).toEqual(["a", "b", "c"]);
|
|
135
|
+
expect(() => schema.parse([1, 2, 3])).toThrow();
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
test("array with constraints", () => {
|
|
139
|
+
const schema = fromJSONSchema({
|
|
140
|
+
type: "array",
|
|
141
|
+
items: { type: "number" },
|
|
142
|
+
minItems: 2,
|
|
143
|
+
maxItems: 4,
|
|
144
|
+
});
|
|
145
|
+
expect(schema.parse([1, 2])).toEqual([1, 2]);
|
|
146
|
+
expect(schema.parse([1, 2, 3, 4])).toEqual([1, 2, 3, 4]);
|
|
147
|
+
expect(() => schema.parse([1])).toThrow();
|
|
148
|
+
expect(() => schema.parse([1, 2, 3, 4, 5])).toThrow();
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
test("tuple with prefixItems (draft-2020-12)", () => {
|
|
152
|
+
const schema = fromJSONSchema({
|
|
153
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
154
|
+
type: "array",
|
|
155
|
+
prefixItems: [{ type: "string" }, { type: "number" }],
|
|
156
|
+
});
|
|
157
|
+
expect(schema.parse([])).toEqual([]);
|
|
158
|
+
expect(schema.parse(["hello"])).toEqual(["hello"]);
|
|
159
|
+
expect(schema.parse(["hello", 42])).toEqual(["hello", 42]);
|
|
160
|
+
// no `items`, so the tail is unconstrained
|
|
161
|
+
expect(schema.parse(["hello", 42, "extra"])).toEqual(["hello", 42, "extra"]);
|
|
162
|
+
expect(() => schema.parse([1])).toThrow();
|
|
163
|
+
expect(() => schema.parse(["hello", "world"])).toThrow();
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
test("tuple with prefixItems and minItems (draft-2020-12)", () => {
|
|
167
|
+
const partialItems = fromJSONSchema({
|
|
168
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
169
|
+
type: "array",
|
|
170
|
+
prefixItems: [{ type: "string" }, { type: "number" }],
|
|
171
|
+
minItems: 1,
|
|
172
|
+
});
|
|
173
|
+
expect(() => partialItems.parse([])).toThrow();
|
|
174
|
+
expect(partialItems.parse(["hello"])).toEqual(["hello"]);
|
|
175
|
+
|
|
176
|
+
const allRequired = fromJSONSchema({
|
|
177
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
178
|
+
type: "array",
|
|
179
|
+
prefixItems: [{ type: "string" }, { type: "number" }],
|
|
180
|
+
minItems: 2,
|
|
181
|
+
});
|
|
182
|
+
expect(() => allRequired.parse(["hello"])).toThrow();
|
|
183
|
+
expect(allRequired.parse(["hello", 42])).toEqual(["hello", 42]);
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
test("tuple with prefixItems allows extra items by default (draft-2020-12)", () => {
|
|
187
|
+
const schema = fromJSONSchema({
|
|
188
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
189
|
+
type: "array",
|
|
190
|
+
prefixItems: [{ type: "string" }],
|
|
191
|
+
});
|
|
192
|
+
expect(schema.parse(["hello", 42, true])).toEqual(["hello", 42, true]);
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
test("tuple with prefixItems respects items schema (draft-2020-12)", () => {
|
|
196
|
+
const schema = fromJSONSchema({
|
|
197
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
198
|
+
type: "array",
|
|
199
|
+
prefixItems: [{ type: "string" }],
|
|
200
|
+
items: { type: "number" },
|
|
201
|
+
});
|
|
202
|
+
expect(schema.parse(["hello", 1, 2])).toEqual(["hello", 1, 2]);
|
|
203
|
+
expect(() => schema.parse(["hello", 1, "extra"])).toThrow();
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
test("tuple with prefixItems and items true allows extra items (draft-2020-12)", () => {
|
|
207
|
+
const schema = fromJSONSchema({
|
|
208
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
209
|
+
type: "array",
|
|
210
|
+
prefixItems: [{ type: "string" }, { type: "number" }],
|
|
211
|
+
items: true,
|
|
212
|
+
});
|
|
213
|
+
expect(schema.parse(["hello", 42, "extra", true])).toEqual(["hello", 42, "extra", true]);
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
test("tuple with prefixItems and items false rejects extra items (draft-2020-12)", () => {
|
|
217
|
+
const schema = fromJSONSchema({
|
|
218
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
219
|
+
type: "array",
|
|
220
|
+
prefixItems: [{ type: "string" }],
|
|
221
|
+
items: false,
|
|
222
|
+
});
|
|
223
|
+
expect(schema.parse(["hello"])).toEqual(["hello"]);
|
|
224
|
+
expect(() => schema.parse(["hello", 42])).toThrow();
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
test("tuple with items array (draft-7)", () => {
|
|
228
|
+
const schema = fromJSONSchema({
|
|
229
|
+
$schema: "http://json-schema.org/draft-07/schema#",
|
|
230
|
+
type: "array",
|
|
231
|
+
items: [{ type: "string" }, { type: "number" }],
|
|
232
|
+
additionalItems: false,
|
|
233
|
+
});
|
|
234
|
+
expect(schema.parse([])).toEqual([]);
|
|
235
|
+
expect(schema.parse(["hello"])).toEqual(["hello"]);
|
|
236
|
+
expect(schema.parse(["hello", 42])).toEqual(["hello", 42]);
|
|
237
|
+
expect(() => schema.parse([1])).toThrow();
|
|
238
|
+
expect(() => schema.parse(["hello", "world"])).toThrow();
|
|
239
|
+
expect(() => schema.parse(["hello", 42, "extra"])).toThrow();
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
test("tuple with items array and minItems (draft-7)", () => {
|
|
243
|
+
const partialItems = fromJSONSchema({
|
|
244
|
+
$schema: "http://json-schema.org/draft-07/schema#",
|
|
245
|
+
type: "array",
|
|
246
|
+
items: [{ type: "string" }, { type: "number" }],
|
|
247
|
+
additionalItems: false,
|
|
248
|
+
minItems: 1,
|
|
249
|
+
});
|
|
250
|
+
expect(() => partialItems.parse([])).toThrow();
|
|
251
|
+
expect(partialItems.parse(["hello"])).toEqual(["hello"]);
|
|
252
|
+
|
|
253
|
+
const allRequired = fromJSONSchema({
|
|
254
|
+
$schema: "http://json-schema.org/draft-07/schema#",
|
|
255
|
+
type: "array",
|
|
256
|
+
items: [{ type: "string" }, { type: "number" }],
|
|
257
|
+
additionalItems: false,
|
|
258
|
+
minItems: 2,
|
|
259
|
+
});
|
|
260
|
+
expect(() => allRequired.parse(["hello"])).toThrow();
|
|
261
|
+
expect(allRequired.parse(["hello", 42])).toEqual(["hello", 42]);
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
test("tuple with items array allows extra items by default (draft-7)", () => {
|
|
265
|
+
const schema = fromJSONSchema({
|
|
266
|
+
$schema: "http://json-schema.org/draft-07/schema#",
|
|
267
|
+
type: "array",
|
|
268
|
+
items: [{ type: "string" }],
|
|
269
|
+
});
|
|
270
|
+
expect(schema.parse(["hello", 42, true])).toEqual(["hello", 42, true]);
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
test("tuple with items array and additionalItems true allows extra items (draft-7)", () => {
|
|
274
|
+
const schema = fromJSONSchema({
|
|
275
|
+
$schema: "http://json-schema.org/draft-07/schema#",
|
|
276
|
+
type: "array",
|
|
277
|
+
items: [{ type: "string" }, { type: "number" }],
|
|
278
|
+
additionalItems: true,
|
|
279
|
+
});
|
|
280
|
+
expect(schema.parse(["hello", 42, "extra", true])).toEqual(["hello", 42, "extra", true]);
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
test("tuple with items array respects additionalItems schema (draft-7)", () => {
|
|
284
|
+
const schema = fromJSONSchema({
|
|
285
|
+
$schema: "http://json-schema.org/draft-07/schema#",
|
|
286
|
+
type: "array",
|
|
287
|
+
items: [{ type: "string" }],
|
|
288
|
+
additionalItems: { type: "number" },
|
|
289
|
+
});
|
|
290
|
+
expect(schema.parse(["hello", 1, 2])).toEqual(["hello", 1, 2]);
|
|
291
|
+
expect(() => schema.parse(["hello", 1, "extra"])).toThrow();
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
test("enum schema", () => {
|
|
295
|
+
const schema = fromJSONSchema({
|
|
296
|
+
enum: ["red", "green", "blue"],
|
|
297
|
+
});
|
|
298
|
+
expect(schema.parse("red")).toBe("red");
|
|
299
|
+
expect(() => schema.parse("yellow")).toThrow();
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
test("const schema", () => {
|
|
303
|
+
const schema = fromJSONSchema({
|
|
304
|
+
const: "hello",
|
|
305
|
+
});
|
|
306
|
+
expect(schema.parse("hello")).toBe("hello");
|
|
307
|
+
expect(() => schema.parse("world")).toThrow();
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
test("anyOf schema", () => {
|
|
311
|
+
const schema = fromJSONSchema({
|
|
312
|
+
anyOf: [{ type: "string" }, { type: "number" }],
|
|
313
|
+
});
|
|
314
|
+
expect(schema.parse("hello")).toBe("hello");
|
|
315
|
+
expect(schema.parse(42)).toBe(42);
|
|
316
|
+
expect(() => schema.parse(true)).toThrow();
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
test("allOf schema", () => {
|
|
320
|
+
const schema = fromJSONSchema({
|
|
321
|
+
allOf: [
|
|
322
|
+
{ type: "object", properties: { name: { type: "string" } }, required: ["name"] },
|
|
323
|
+
{ type: "object", properties: { age: { type: "number" } }, required: ["age"] },
|
|
324
|
+
],
|
|
325
|
+
});
|
|
326
|
+
const result = schema.parse({ name: "John", age: 30 }) as { name: string; age: number };
|
|
327
|
+
expect(result.name).toBe("John");
|
|
328
|
+
expect(result.age).toBe(30);
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
test("allOf with empty array", () => {
|
|
332
|
+
// Empty allOf without explicit type returns any
|
|
333
|
+
const schema1 = fromJSONSchema({
|
|
334
|
+
allOf: [],
|
|
335
|
+
});
|
|
336
|
+
expect(schema1.parse("hello")).toBe("hello");
|
|
337
|
+
expect(schema1.parse(123)).toBe(123);
|
|
338
|
+
expect(schema1.parse({})).toEqual({});
|
|
339
|
+
|
|
340
|
+
// Empty allOf with explicit type returns base schema
|
|
341
|
+
const schema2 = fromJSONSchema({
|
|
342
|
+
type: "string",
|
|
343
|
+
allOf: [],
|
|
344
|
+
});
|
|
345
|
+
expect(schema2.parse("hello")).toBe("hello");
|
|
346
|
+
expect(() => schema2.parse(123)).toThrow();
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
test("oneOf schema (exclusive union)", () => {
|
|
350
|
+
const schema = fromJSONSchema({
|
|
351
|
+
oneOf: [{ type: "string" }, { type: "number" }],
|
|
352
|
+
});
|
|
353
|
+
expect(schema.parse("hello")).toBe("hello");
|
|
354
|
+
expect(schema.parse(42)).toBe(42);
|
|
355
|
+
expect(() => schema.parse(true)).toThrow();
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
test("type with anyOf creates intersection", () => {
|
|
359
|
+
// type: string AND (type:string,minLength:5 OR type:string,pattern:^a)
|
|
360
|
+
const schema = fromJSONSchema({
|
|
361
|
+
type: "string",
|
|
362
|
+
anyOf: [
|
|
363
|
+
{ type: "string", minLength: 5 },
|
|
364
|
+
{ type: "string", pattern: "^a" },
|
|
365
|
+
],
|
|
366
|
+
});
|
|
367
|
+
// Should pass: string AND (minLength:5 OR pattern:^a) - matches minLength
|
|
368
|
+
expect(schema.parse("hello")).toBe("hello");
|
|
369
|
+
// Should pass: string AND (minLength:5 OR pattern:^a) - matches pattern
|
|
370
|
+
expect(schema.parse("abc")).toBe("abc");
|
|
371
|
+
// Should fail: string but neither minLength nor pattern match
|
|
372
|
+
expect(() => schema.parse("hi")).toThrow();
|
|
373
|
+
// Should fail: not a string
|
|
374
|
+
expect(() => schema.parse(123)).toThrow();
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
test("type with oneOf creates intersection", () => {
|
|
378
|
+
// type: string AND (exactly one of: type:string,minLength:5 OR type:string,pattern:^a)
|
|
379
|
+
const schema = fromJSONSchema({
|
|
380
|
+
type: "string",
|
|
381
|
+
oneOf: [
|
|
382
|
+
{ type: "string", minLength: 5 },
|
|
383
|
+
{ type: "string", pattern: "^a" },
|
|
384
|
+
],
|
|
385
|
+
});
|
|
386
|
+
// Should pass: string AND minLength:5 (exactly one match - "hello" length 5 >= 5, doesn't start with 'a')
|
|
387
|
+
expect(schema.parse("hello")).toBe("hello");
|
|
388
|
+
// Should pass: string AND pattern:^a (exactly one match - "abc" starts with 'a', length 3 < 5)
|
|
389
|
+
expect(schema.parse("abc")).toBe("abc");
|
|
390
|
+
// Should fail: string but neither match
|
|
391
|
+
expect(() => schema.parse("hi")).toThrow();
|
|
392
|
+
// Should fail: not a string
|
|
393
|
+
expect(() => schema.parse(123)).toThrow();
|
|
394
|
+
// Should fail: matches both (length >= 5 AND starts with 'a') - exclusive union fails
|
|
395
|
+
expect(() => schema.parse("apple")).toThrow();
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
test("unevaluatedItems throws error", () => {
|
|
399
|
+
expect(() => {
|
|
400
|
+
fromJSONSchema({
|
|
401
|
+
type: "array",
|
|
402
|
+
unevaluatedItems: false,
|
|
403
|
+
});
|
|
404
|
+
}).toThrow("unevaluatedItems is not supported");
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
test("unevaluatedProperties throws error", () => {
|
|
408
|
+
expect(() => {
|
|
409
|
+
fromJSONSchema({
|
|
410
|
+
type: "object",
|
|
411
|
+
unevaluatedProperties: false,
|
|
412
|
+
});
|
|
413
|
+
}).toThrow("unevaluatedProperties is not supported");
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
test("if/then/else throws error", () => {
|
|
417
|
+
expect(() => {
|
|
418
|
+
fromJSONSchema({
|
|
419
|
+
if: { type: "string" },
|
|
420
|
+
then: { type: "number" },
|
|
421
|
+
});
|
|
422
|
+
}).toThrow("Conditional schemas");
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
test("external $ref throws error", () => {
|
|
426
|
+
expect(() => {
|
|
427
|
+
fromJSONSchema({
|
|
428
|
+
$ref: "https://example.com/schema#/definitions/User",
|
|
429
|
+
});
|
|
430
|
+
}).toThrow("External $ref is not supported");
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
test("local $ref resolution", () => {
|
|
434
|
+
const schema = fromJSONSchema({
|
|
435
|
+
$defs: {
|
|
436
|
+
User: {
|
|
437
|
+
type: "object",
|
|
438
|
+
properties: {
|
|
439
|
+
name: { type: "string" },
|
|
440
|
+
},
|
|
441
|
+
required: ["name"],
|
|
442
|
+
},
|
|
443
|
+
},
|
|
444
|
+
$ref: "#/$defs/User",
|
|
445
|
+
});
|
|
446
|
+
expect(schema.parse({ name: "John" })).toEqual({ name: "John" });
|
|
447
|
+
expect(() => schema.parse({})).toThrow();
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
test("local $ref resolution unescapes JSON Pointer tokens", () => {
|
|
451
|
+
// `~1` must be decoded before `~0`, so the id "a~1b" survives as itself and not as "a/b".
|
|
452
|
+
for (const id of ["Shared/User", "My~Model", "a~b/c", "a~1b"]) {
|
|
453
|
+
const inner = z.object({ name: z.string() }).meta({ id });
|
|
454
|
+
const schema = fromJSONSchema(z.toJSONSchema(z.object({ inner })));
|
|
455
|
+
expect(schema.parse({ inner: { name: "John" } })).toEqual({ inner: { name: "John" } });
|
|
456
|
+
}
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
test("circular $ref with lazy", () => {
|
|
460
|
+
const schema = fromJSONSchema({
|
|
461
|
+
$defs: {
|
|
462
|
+
Node: {
|
|
463
|
+
type: "object",
|
|
464
|
+
properties: {
|
|
465
|
+
value: { type: "string" },
|
|
466
|
+
children: {
|
|
467
|
+
type: "array",
|
|
468
|
+
items: { $ref: "#/$defs/Node" },
|
|
469
|
+
},
|
|
470
|
+
},
|
|
471
|
+
},
|
|
472
|
+
},
|
|
473
|
+
$ref: "#/$defs/Node",
|
|
474
|
+
});
|
|
475
|
+
type Node = { value: string; children: Node[] };
|
|
476
|
+
const result = schema.parse({
|
|
477
|
+
value: "root",
|
|
478
|
+
children: [{ value: "child", children: [] }],
|
|
479
|
+
}) as Node;
|
|
480
|
+
expect(result.value).toBe("root");
|
|
481
|
+
expect(result.children[0]?.value).toBe("child");
|
|
482
|
+
});
|
|
483
|
+
|
|
484
|
+
test("patternProperties", () => {
|
|
485
|
+
const schema = fromJSONSchema({
|
|
486
|
+
type: "object",
|
|
487
|
+
patternProperties: {
|
|
488
|
+
"^S_": { type: "string" },
|
|
489
|
+
},
|
|
490
|
+
});
|
|
491
|
+
const result = schema.parse({ S_name: "John", S_age: "30" }) as Record<string, string>;
|
|
492
|
+
expect(result.S_name).toBe("John");
|
|
493
|
+
expect(result.S_age).toBe("30");
|
|
494
|
+
});
|
|
495
|
+
|
|
496
|
+
test("propertyNames with enum does not require keys", () => {
|
|
497
|
+
const schema = fromJSONSchema({
|
|
498
|
+
type: "object",
|
|
499
|
+
propertyNames: { enum: ["a", "b"] },
|
|
500
|
+
additionalProperties: { type: "string" },
|
|
501
|
+
});
|
|
502
|
+
expect(schema.parse({ a: "x" })).toEqual({ a: "x" });
|
|
503
|
+
expect(schema.parse({ b: "y" })).toEqual({ b: "y" });
|
|
504
|
+
expect(schema.parse({})).toEqual({});
|
|
505
|
+
expect(() => schema.parse({ c: "z" })).toThrow();
|
|
506
|
+
});
|
|
507
|
+
|
|
508
|
+
test("propertyNames with enum alongside properties", () => {
|
|
509
|
+
const schema = fromJSONSchema({
|
|
510
|
+
type: "object",
|
|
511
|
+
properties: { a: { type: "string" } },
|
|
512
|
+
propertyNames: { enum: ["a", "b"] },
|
|
513
|
+
additionalProperties: { type: "string" },
|
|
514
|
+
});
|
|
515
|
+
expect(schema.parse({ a: "x" })).toEqual({ a: "x" });
|
|
516
|
+
expect(schema.parse({})).toEqual({});
|
|
517
|
+
expect(() => schema.parse({ c: "z" })).toThrow();
|
|
518
|
+
expect(() => schema.parse({ a: "x", c: "z" })).toThrow();
|
|
519
|
+
});
|
|
520
|
+
|
|
521
|
+
test("propertyNames with enum still honors required", () => {
|
|
522
|
+
const schema = fromJSONSchema({
|
|
523
|
+
type: "object",
|
|
524
|
+
properties: { a: { type: "string" }, b: { type: "string" } },
|
|
525
|
+
required: ["a"],
|
|
526
|
+
propertyNames: { enum: ["a", "b"] },
|
|
527
|
+
additionalProperties: { type: "string" },
|
|
528
|
+
});
|
|
529
|
+
expect(schema.parse({ a: "x" })).toEqual({ a: "x" });
|
|
530
|
+
expect(schema.parse({ a: "x", b: "y" })).toEqual({ a: "x", b: "y" });
|
|
531
|
+
expect(() => schema.parse({})).toThrow();
|
|
532
|
+
expect(() => schema.parse({ b: "y" })).toThrow();
|
|
533
|
+
});
|
|
534
|
+
|
|
535
|
+
test("propertyNames does not apply additionalProperties to declared properties", () => {
|
|
536
|
+
const schema = fromJSONSchema({
|
|
537
|
+
type: "object",
|
|
538
|
+
properties: { a: { type: "number" } },
|
|
539
|
+
propertyNames: { enum: ["a", "b"] },
|
|
540
|
+
additionalProperties: { type: "string" },
|
|
541
|
+
});
|
|
542
|
+
expect(schema.parse({ a: 1 })).toEqual({ a: 1 });
|
|
543
|
+
expect(schema.parse({ a: 1, b: "x" })).toEqual({ a: 1, b: "x" });
|
|
544
|
+
expect(() => schema.parse({ b: 1 })).toThrow();
|
|
545
|
+
});
|
|
546
|
+
|
|
547
|
+
test("propertyNames alongside additionalProperties: false", () => {
|
|
548
|
+
const schema = fromJSONSchema({
|
|
549
|
+
type: "object",
|
|
550
|
+
properties: { a: { type: "string" } },
|
|
551
|
+
propertyNames: { enum: ["a", "b"] },
|
|
552
|
+
additionalProperties: false,
|
|
553
|
+
});
|
|
554
|
+
expect(schema.parse({ a: "x" })).toEqual({ a: "x" });
|
|
555
|
+
expect(() => schema.parse({ a: "x", b: "y" })).toThrow();
|
|
556
|
+
expect(() => schema.parse({ c: "z" })).toThrow();
|
|
557
|
+
});
|
|
558
|
+
|
|
559
|
+
test("propertyNames alongside patternProperties", () => {
|
|
560
|
+
const schema = fromJSONSchema({
|
|
561
|
+
type: "object",
|
|
562
|
+
patternProperties: { "^S_": { type: "string" } },
|
|
563
|
+
propertyNames: { type: "string", pattern: "^S_" },
|
|
564
|
+
});
|
|
565
|
+
expect(schema.parse({ S_a: "x" })).toEqual({ S_a: "x" });
|
|
566
|
+
expect(() => schema.parse({ S_a: 1 })).toThrow();
|
|
567
|
+
expect(() => schema.parse({ other: "x" })).toThrow();
|
|
568
|
+
});
|
|
569
|
+
|
|
570
|
+
test("propertyNames rejects keys the object parse would drop or synthesize", () => {
|
|
571
|
+
const schema = fromJSONSchema({ type: "object", propertyNames: { enum: ["a"] } });
|
|
572
|
+
// An object parse strips `__proto__`, so the guard has to see the raw input.
|
|
573
|
+
expect(() => schema.parse(JSON.parse('{"__proto__":1}'))).toThrow();
|
|
574
|
+
|
|
575
|
+
// A property `default` adds a key that was never in the instance.
|
|
576
|
+
const withDefault = fromJSONSchema({
|
|
577
|
+
type: "object",
|
|
578
|
+
properties: { AB: { type: "string", default: "x" } },
|
|
579
|
+
propertyNames: { type: "string", pattern: "^[a-z]+$" },
|
|
580
|
+
});
|
|
581
|
+
expect(withDefault.parse({})).toEqual({ AB: "x" });
|
|
582
|
+
});
|
|
583
|
+
|
|
584
|
+
test("propertyNames subschema without a type still constrains keys", () => {
|
|
585
|
+
const schema = fromJSONSchema({
|
|
586
|
+
type: "object",
|
|
587
|
+
propertyNames: { pattern: "^[A-Za-z_][A-Za-z0-9_]*$" },
|
|
588
|
+
});
|
|
589
|
+
expect(schema.parse({ valid_key: "x" })).toEqual({ valid_key: "x" });
|
|
590
|
+
expect(() => schema.parse({ "001 invalid": "x" })).toThrow();
|
|
591
|
+
});
|
|
592
|
+
|
|
593
|
+
test("boolean propertyNames", () => {
|
|
594
|
+
expect(() => fromJSONSchema({ type: "object", propertyNames: false }).parse({ a: 1 })).toThrow();
|
|
595
|
+
expect(fromJSONSchema({ type: "object", propertyNames: false }).parse({})).toEqual({});
|
|
596
|
+
expect(fromJSONSchema({ type: "object", propertyNames: true }).parse({ anything: 1 })).toEqual({ anything: 1 });
|
|
597
|
+
});
|
|
598
|
+
|
|
599
|
+
test("propertyNames metadata is not attached where it is inert", () => {
|
|
600
|
+
// Not an object: nothing enforces it, so it must not be advertised either.
|
|
601
|
+
expect(z.toJSONSchema(fromJSONSchema({ type: "string", propertyNames: { enum: ["a"] } }))).not.toHaveProperty(
|
|
602
|
+
"propertyNames"
|
|
603
|
+
);
|
|
604
|
+
|
|
605
|
+
// A $ref target is shared by every reference to it; metadata must not leak onto it.
|
|
606
|
+
const shared = z.toJSONSchema(
|
|
607
|
+
fromJSONSchema({
|
|
608
|
+
$defs: { N: { type: "object", properties: { v: { type: "string" } } } },
|
|
609
|
+
type: "object",
|
|
610
|
+
properties: { a: { $ref: "#/$defs/N", propertyNames: { enum: ["v"] } }, b: { $ref: "#/$defs/N" } },
|
|
611
|
+
})
|
|
612
|
+
) as any;
|
|
613
|
+
expect(shared.properties.b).not.toHaveProperty("propertyNames");
|
|
614
|
+
});
|
|
615
|
+
|
|
616
|
+
test("propertyNames survives a toJSONSchema round trip", () => {
|
|
617
|
+
const roundTripped = z.toJSONSchema(
|
|
618
|
+
fromJSONSchema({
|
|
619
|
+
type: "object",
|
|
620
|
+
propertyNames: { enum: ["a", "b"] },
|
|
621
|
+
additionalProperties: { type: "string" },
|
|
622
|
+
})
|
|
623
|
+
) as Record<string, unknown>;
|
|
624
|
+
expect(roundTripped.propertyNames).toEqual({ enum: ["a", "b"] });
|
|
625
|
+
expect(roundTripped.required).toBeUndefined();
|
|
626
|
+
|
|
627
|
+
// The key guard is a pipe; neither side may swallow the object it wraps.
|
|
628
|
+
for (const io of ["input", "output"] as const) {
|
|
629
|
+
const out = z.toJSONSchema(
|
|
630
|
+
fromJSONSchema({
|
|
631
|
+
type: "object",
|
|
632
|
+
properties: { a: { type: "string" } },
|
|
633
|
+
propertyNames: { enum: ["a", "b"] },
|
|
634
|
+
additionalProperties: { type: "string" },
|
|
635
|
+
}),
|
|
636
|
+
{ io }
|
|
637
|
+
) as Record<string, unknown>;
|
|
638
|
+
expect(out.type).toBe("object");
|
|
639
|
+
expect(out.properties).toEqual({ a: { type: "string" } });
|
|
640
|
+
expect(out.propertyNames).toEqual({ enum: ["a", "b"] });
|
|
641
|
+
}
|
|
642
|
+
});
|
|
643
|
+
|
|
644
|
+
test("patternProperties with regular properties", () => {
|
|
645
|
+
// Note: When patternProperties is combined with properties, the intersection validates all keys against the pattern. This test uses a pattern that matches the regular property name as well.
|
|
646
|
+
const schema = fromJSONSchema({
|
|
647
|
+
type: "object",
|
|
648
|
+
properties: {
|
|
649
|
+
S_name: { type: "string" },
|
|
650
|
+
},
|
|
651
|
+
patternProperties: {
|
|
652
|
+
"^S_": { type: "string" },
|
|
653
|
+
},
|
|
654
|
+
required: ["S_name"],
|
|
655
|
+
});
|
|
656
|
+
const result = schema.parse({ S_name: "John", S_extra: "value" }) as Record<string, string>;
|
|
657
|
+
expect(result.S_name).toBe("John");
|
|
658
|
+
expect(result.S_extra).toBe("value");
|
|
659
|
+
});
|
|
660
|
+
|
|
661
|
+
test("multiple patternProperties", () => {
|
|
662
|
+
const schema = fromJSONSchema({
|
|
663
|
+
type: "object",
|
|
664
|
+
patternProperties: {
|
|
665
|
+
"^S_": { type: "string" },
|
|
666
|
+
"^N_": { type: "number" },
|
|
667
|
+
},
|
|
668
|
+
});
|
|
669
|
+
const result = schema.parse({ S_name: "John", N_count: 123 }) as Record<string, string | number>;
|
|
670
|
+
expect(result.S_name).toBe("John");
|
|
671
|
+
expect(result.N_count).toBe(123);
|
|
672
|
+
// Keys not matching any pattern should pass through
|
|
673
|
+
const result2 = schema.parse({ S_name: "John", N_count: 123, other: "value" }) as Record<string, string | number>;
|
|
674
|
+
expect(result2.other).toBe("value");
|
|
675
|
+
});
|
|
676
|
+
|
|
677
|
+
test("multiple overlapping patternProperties", () => {
|
|
678
|
+
// If a key matches multiple patterns, value must satisfy all schemas
|
|
679
|
+
const schema = fromJSONSchema({
|
|
680
|
+
type: "object",
|
|
681
|
+
patternProperties: {
|
|
682
|
+
"^S_": { type: "string" },
|
|
683
|
+
"^S_N": { type: "string", minLength: 3 },
|
|
684
|
+
},
|
|
685
|
+
});
|
|
686
|
+
// S_name matches ^S_ but not ^S_N
|
|
687
|
+
expect(schema.parse({ S_name: "John" })).toEqual({ S_name: "John" });
|
|
688
|
+
// S_N matches both patterns - must satisfy both (string with minLength 3)
|
|
689
|
+
expect(schema.parse({ S_N: "abc" })).toEqual({ S_N: "abc" });
|
|
690
|
+
expect(() => schema.parse({ S_N: "ab" })).toThrow(); // too short for ^S_N pattern
|
|
691
|
+
});
|
|
692
|
+
|
|
693
|
+
test("patternProperties with additionalProperties false", () => {
|
|
694
|
+
const schema = fromJSONSchema({
|
|
695
|
+
type: "object",
|
|
696
|
+
patternProperties: {
|
|
697
|
+
"^S_": { type: "string" },
|
|
698
|
+
},
|
|
699
|
+
additionalProperties: false,
|
|
700
|
+
});
|
|
701
|
+
expect(schema.parse({ S_name: "ok" })).toEqual({ S_name: "ok" });
|
|
702
|
+
expect(() => schema.parse({ X_A: false })).toThrow();
|
|
703
|
+
expect(() => schema.parse({ S_name: 1 })).toThrow();
|
|
704
|
+
});
|
|
705
|
+
|
|
706
|
+
test("patternProperties and properties with additionalProperties false", () => {
|
|
707
|
+
const schema = fromJSONSchema({
|
|
708
|
+
type: "object",
|
|
709
|
+
properties: {
|
|
710
|
+
name: { type: "string" },
|
|
711
|
+
},
|
|
712
|
+
patternProperties: {
|
|
713
|
+
"^S_": { type: "string" },
|
|
714
|
+
},
|
|
715
|
+
additionalProperties: false,
|
|
716
|
+
required: ["name"],
|
|
717
|
+
});
|
|
718
|
+
expect(schema.parse({ name: "ok", S_count: "1" })).toEqual({ name: "ok", S_count: "1" });
|
|
719
|
+
expect(() => schema.parse({ name: "ok", S_count: 1 })).toThrow();
|
|
720
|
+
expect(() => schema.parse({ name: "ok", other: "value" })).toThrow();
|
|
721
|
+
});
|
|
722
|
+
|
|
723
|
+
test("multiple patternProperties with additionalProperties false", () => {
|
|
724
|
+
const schema = fromJSONSchema({
|
|
725
|
+
type: "object",
|
|
726
|
+
patternProperties: {
|
|
727
|
+
"^S_": { type: "string" },
|
|
728
|
+
"^N_": { type: "number" },
|
|
729
|
+
},
|
|
730
|
+
additionalProperties: false,
|
|
731
|
+
});
|
|
732
|
+
expect(schema.parse({ S_a: "a", N_b: 1 })).toEqual({ S_a: "a", N_b: 1 });
|
|
733
|
+
expect(() => schema.parse({ S_a: "a", N_b: 1, other: "value" })).toThrow();
|
|
734
|
+
});
|
|
735
|
+
|
|
736
|
+
test("default value", () => {
|
|
737
|
+
const schema = fromJSONSchema({
|
|
738
|
+
type: "string",
|
|
739
|
+
default: "hello",
|
|
740
|
+
});
|
|
741
|
+
// Default is applied during parsing if value is missing/undefined. This depends on Yatsp's default behavior
|
|
742
|
+
expect(schema.parse("world")).toBe("world");
|
|
743
|
+
});
|
|
744
|
+
|
|
745
|
+
test("description metadata", () => {
|
|
746
|
+
const schema = fromJSONSchema({
|
|
747
|
+
type: "string",
|
|
748
|
+
description: "A string value",
|
|
749
|
+
});
|
|
750
|
+
expect(schema.parse("hello")).toBe("hello");
|
|
751
|
+
});
|
|
752
|
+
|
|
753
|
+
test("version detection - draft-2020-12", () => {
|
|
754
|
+
const schema = fromJSONSchema({
|
|
755
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
756
|
+
type: "array",
|
|
757
|
+
prefixItems: [{ type: "string" }],
|
|
758
|
+
});
|
|
759
|
+
expect(schema.parse(["hello"])).toEqual(["hello"]);
|
|
760
|
+
});
|
|
761
|
+
|
|
762
|
+
test("version detection - draft-7", () => {
|
|
763
|
+
const schema = fromJSONSchema({
|
|
764
|
+
$schema: "http://json-schema.org/draft-07/schema#",
|
|
765
|
+
type: "array",
|
|
766
|
+
items: [{ type: "string" }],
|
|
767
|
+
});
|
|
768
|
+
expect(schema.parse(["hello"])).toEqual(["hello"]);
|
|
769
|
+
});
|
|
770
|
+
|
|
771
|
+
test("version detection - draft-4", () => {
|
|
772
|
+
const schema = fromJSONSchema({
|
|
773
|
+
$schema: "http://json-schema.org/draft-04/schema#",
|
|
774
|
+
type: "array",
|
|
775
|
+
items: [{ type: "string" }],
|
|
776
|
+
});
|
|
777
|
+
expect(schema.parse(["hello"])).toEqual(["hello"]);
|
|
778
|
+
});
|
|
779
|
+
|
|
780
|
+
test("default version (draft-2020-12)", () => {
|
|
781
|
+
const schema = fromJSONSchema({
|
|
782
|
+
type: "array",
|
|
783
|
+
prefixItems: [{ type: "string" }],
|
|
784
|
+
});
|
|
785
|
+
expect(schema.parse(["hello"])).toEqual(["hello"]);
|
|
786
|
+
});
|
|
787
|
+
|
|
788
|
+
test("string format - email", () => {
|
|
789
|
+
const schema = fromJSONSchema({
|
|
790
|
+
type: "string",
|
|
791
|
+
format: "email",
|
|
792
|
+
});
|
|
793
|
+
expect(schema.parse("test@example.com")).toBe("test@example.com");
|
|
794
|
+
});
|
|
795
|
+
|
|
796
|
+
test("string format - uuid", () => {
|
|
797
|
+
const schema = fromJSONSchema({
|
|
798
|
+
type: "string",
|
|
799
|
+
format: "uuid",
|
|
800
|
+
});
|
|
801
|
+
const uuid = "550e8400-e29b-41d4-a716-446655440000";
|
|
802
|
+
expect(schema.parse(uuid)).toBe(uuid);
|
|
803
|
+
});
|
|
804
|
+
|
|
805
|
+
test("string format - date-time", () => {
|
|
806
|
+
const schema = fromJSONSchema({
|
|
807
|
+
type: "string",
|
|
808
|
+
format: "date-time",
|
|
809
|
+
});
|
|
810
|
+
expect(schema.safeParse("2026-07-29T14:30:00Z").success).toBe(true);
|
|
811
|
+
expect(schema.safeParse("2026-07-29T16:30:00+02:00").success).toBe(true);
|
|
812
|
+
expect(schema.safeParse("2026-07-29T14:30:00").success).toBe(false);
|
|
813
|
+
expect(schema.safeParse("2026-07-29T16:30:00+0200").success).toBe(false);
|
|
814
|
+
|
|
815
|
+
const zuluOnly = fromJSONSchema({
|
|
816
|
+
type: "string",
|
|
817
|
+
format: "date-time",
|
|
818
|
+
pattern: "Z$",
|
|
819
|
+
});
|
|
820
|
+
expect(zuluOnly.safeParse("2026-07-29T14:30:00Z").success).toBe(true);
|
|
821
|
+
expect(zuluOnly.safeParse("2026-07-29T16:30:00+02:00").success).toBe(false);
|
|
822
|
+
expect(zuluOnly.safeParse("garbageZ").success).toBe(false);
|
|
823
|
+
|
|
824
|
+
const roundTripped = fromJSONSchema(z.toJSONSchema(z.iso.datetime({ offset: true })));
|
|
825
|
+
expect(roundTripped.safeParse("2026-07-29T16:30:00+02:00").success).toBe(true);
|
|
826
|
+
});
|
|
827
|
+
|
|
828
|
+
test("string format - time", () => {
|
|
829
|
+
const schema = fromJSONSchema({
|
|
830
|
+
type: "string",
|
|
831
|
+
format: "time",
|
|
832
|
+
});
|
|
833
|
+
// full-time, so seconds and an offset are both required
|
|
834
|
+
expect(schema.safeParse("14:30:00Z").success).toBe(true);
|
|
835
|
+
expect(schema.safeParse("16:30:00+02:00").success).toBe(true);
|
|
836
|
+
expect(schema.safeParse("16:30:00.123-05:30").success).toBe(true);
|
|
837
|
+
expect(schema.safeParse("14:30:00").success).toBe(false);
|
|
838
|
+
expect(schema.safeParse("14:30Z").success).toBe(false);
|
|
839
|
+
expect(schema.safeParse("16:30:00+0200").success).toBe(false);
|
|
840
|
+
expect(schema.safeParse("16:30:00+24:00").success).toBe(false);
|
|
841
|
+
});
|
|
842
|
+
|
|
843
|
+
test("string format - hostname", () => {
|
|
844
|
+
const schema = fromJSONSchema({
|
|
845
|
+
type: "string",
|
|
846
|
+
format: "hostname",
|
|
847
|
+
});
|
|
848
|
+
expect(schema.parse("example.com")).toBe("example.com");
|
|
849
|
+
expect(() => schema.parse("not a hostname!")).toThrow();
|
|
850
|
+
});
|
|
851
|
+
|
|
852
|
+
test("exclusiveMinimum and exclusiveMaximum", () => {
|
|
853
|
+
const schema = fromJSONSchema({
|
|
854
|
+
type: "number",
|
|
855
|
+
exclusiveMinimum: 0,
|
|
856
|
+
exclusiveMaximum: 100,
|
|
857
|
+
});
|
|
858
|
+
expect(schema.parse(50)).toBe(50);
|
|
859
|
+
expect(() => schema.parse(0)).toThrow();
|
|
860
|
+
expect(() => schema.parse(100)).toThrow();
|
|
861
|
+
});
|
|
862
|
+
|
|
863
|
+
test("boolean schema (true/false)", () => {
|
|
864
|
+
const trueSchema = fromJSONSchema(true);
|
|
865
|
+
expect(trueSchema.parse("anything")).toBe("anything");
|
|
866
|
+
|
|
867
|
+
const falseSchema = fromJSONSchema(false);
|
|
868
|
+
expect(() => falseSchema.parse("anything")).toThrow();
|
|
869
|
+
});
|
|
870
|
+
|
|
871
|
+
test("empty object schema", () => {
|
|
872
|
+
const schema = fromJSONSchema({
|
|
873
|
+
type: "object",
|
|
874
|
+
});
|
|
875
|
+
expect(schema.parse({})).toEqual({});
|
|
876
|
+
expect(schema.parse({ extra: "field" })).toEqual({ extra: "field" });
|
|
877
|
+
});
|
|
878
|
+
|
|
879
|
+
test("array without items", () => {
|
|
880
|
+
const schema = fromJSONSchema({
|
|
881
|
+
type: "array",
|
|
882
|
+
});
|
|
883
|
+
expect(schema.parse([1, "string", true])).toEqual([1, "string", true]);
|
|
884
|
+
});
|
|
885
|
+
|
|
886
|
+
test("mixed enum types", () => {
|
|
887
|
+
const schema = fromJSONSchema({
|
|
888
|
+
enum: ["string", 42, true, null],
|
|
889
|
+
});
|
|
890
|
+
expect(schema.parse("string")).toBe("string");
|
|
891
|
+
expect(schema.parse(42)).toBe(42);
|
|
892
|
+
expect(schema.parse(true)).toBe(true);
|
|
893
|
+
expect(schema.parse(null)).toBe(null);
|
|
894
|
+
});
|
|
895
|
+
|
|
896
|
+
test("nullable in OpenAPI 3.0", () => {
|
|
897
|
+
// General nullable case (not just enum: [null])
|
|
898
|
+
const stringSchema = fromJSONSchema(
|
|
899
|
+
{
|
|
900
|
+
type: "string",
|
|
901
|
+
nullable: true,
|
|
902
|
+
},
|
|
903
|
+
{ defaultTarget: "openapi-3.0" }
|
|
904
|
+
);
|
|
905
|
+
expect(stringSchema.parse("hello")).toBe("hello");
|
|
906
|
+
expect(stringSchema.parse(null)).toBe(null);
|
|
907
|
+
expect(() => stringSchema.parse(123)).toThrow();
|
|
908
|
+
|
|
909
|
+
const numberSchema = fromJSONSchema(
|
|
910
|
+
{
|
|
911
|
+
type: "number",
|
|
912
|
+
nullable: true,
|
|
913
|
+
},
|
|
914
|
+
{ defaultTarget: "openapi-3.0" }
|
|
915
|
+
);
|
|
916
|
+
expect(numberSchema.parse(42)).toBe(42);
|
|
917
|
+
expect(numberSchema.parse(null)).toBe(null);
|
|
918
|
+
expect(() => numberSchema.parse("string")).toThrow();
|
|
919
|
+
|
|
920
|
+
const objectSchema = fromJSONSchema(
|
|
921
|
+
{
|
|
922
|
+
type: "object",
|
|
923
|
+
properties: { name: { type: "string" } },
|
|
924
|
+
nullable: true,
|
|
925
|
+
},
|
|
926
|
+
{ defaultTarget: "openapi-3.0" }
|
|
927
|
+
);
|
|
928
|
+
expect(objectSchema.parse({ name: "John" })).toEqual({ name: "John" });
|
|
929
|
+
expect(objectSchema.parse(null)).toBe(null);
|
|
930
|
+
});
|
|
931
|
+
|
|
932
|
+
// Metadata extraction tests
|
|
933
|
+
|
|
934
|
+
test("unrecognized keys stored in globalRegistry by default", () => {
|
|
935
|
+
const schema = fromJSONSchema({
|
|
936
|
+
type: "string",
|
|
937
|
+
title: "My String",
|
|
938
|
+
deprecated: true,
|
|
939
|
+
examples: ["hello", "world"],
|
|
940
|
+
"x-custom": "custom value",
|
|
941
|
+
});
|
|
942
|
+
|
|
943
|
+
const meta = z.globalRegistry.get(schema);
|
|
944
|
+
expect(meta).toBeDefined();
|
|
945
|
+
expect(meta?.title).toBe("My String");
|
|
946
|
+
expect(meta?.deprecated).toBe(true);
|
|
947
|
+
expect(meta?.examples).toEqual(["hello", "world"]);
|
|
948
|
+
expect((meta as any)?.["x-custom"]).toBe("custom value");
|
|
949
|
+
|
|
950
|
+
// Clean up
|
|
951
|
+
z.globalRegistry.remove(schema);
|
|
952
|
+
});
|
|
953
|
+
|
|
954
|
+
test("unrecognized keys stored in custom registry", () => {
|
|
955
|
+
const customRegistry = z.registry<{ title?: string; deprecated?: boolean }>();
|
|
956
|
+
|
|
957
|
+
const schema = fromJSONSchema(
|
|
958
|
+
{
|
|
959
|
+
type: "number",
|
|
960
|
+
title: "Age",
|
|
961
|
+
deprecated: true,
|
|
962
|
+
},
|
|
963
|
+
{ registry: customRegistry }
|
|
964
|
+
);
|
|
965
|
+
|
|
966
|
+
// Should be in custom registry
|
|
967
|
+
const meta = customRegistry.get(schema);
|
|
968
|
+
expect(meta).toBeDefined();
|
|
969
|
+
expect(meta?.title).toBe("Age");
|
|
970
|
+
expect(meta?.deprecated).toBe(true);
|
|
971
|
+
|
|
972
|
+
// Should NOT be in globalRegistry
|
|
973
|
+
expect(z.globalRegistry.get(schema)).toBeUndefined();
|
|
974
|
+
});
|
|
975
|
+
|
|
976
|
+
test("$id and id are captured as metadata", () => {
|
|
977
|
+
const customRegistry = z.registry<{ $id?: string; id?: string }>();
|
|
978
|
+
|
|
979
|
+
const schema1 = fromJSONSchema(
|
|
980
|
+
{
|
|
981
|
+
$id: "https://example.com/schemas/user",
|
|
982
|
+
type: "object",
|
|
983
|
+
},
|
|
984
|
+
{ registry: customRegistry }
|
|
985
|
+
);
|
|
986
|
+
expect(customRegistry.get(schema1)?.$id).toBe("https://example.com/schemas/user");
|
|
987
|
+
|
|
988
|
+
const schema2 = fromJSONSchema(
|
|
989
|
+
{
|
|
990
|
+
id: "legacy-id",
|
|
991
|
+
type: "string",
|
|
992
|
+
},
|
|
993
|
+
{ registry: customRegistry }
|
|
994
|
+
);
|
|
995
|
+
expect(customRegistry.get(schema2)?.id).toBe("legacy-id");
|
|
996
|
+
});
|
|
997
|
+
|
|
998
|
+
test("x-* extension keys are captured as metadata", () => {
|
|
999
|
+
const customRegistry = z.registry<Record<string, unknown>>();
|
|
1000
|
+
|
|
1001
|
+
const schema = fromJSONSchema(
|
|
1002
|
+
{
|
|
1003
|
+
type: "string",
|
|
1004
|
+
"x-openapi-example": "example value",
|
|
1005
|
+
"x-internal": true,
|
|
1006
|
+
"x-tags": ["api", "public"],
|
|
1007
|
+
},
|
|
1008
|
+
{ registry: customRegistry }
|
|
1009
|
+
);
|
|
1010
|
+
|
|
1011
|
+
const meta = customRegistry.get(schema);
|
|
1012
|
+
expect(meta?.["x-openapi-example"]).toBe("example value");
|
|
1013
|
+
expect(meta?.["x-internal"]).toBe(true);
|
|
1014
|
+
expect(meta?.["x-tags"]).toEqual(["api", "public"]);
|
|
1015
|
+
});
|
|
1016
|
+
|
|
1017
|
+
test("metadata on nested schemas", () => {
|
|
1018
|
+
const customRegistry = z.registry<Record<string, unknown>>();
|
|
1019
|
+
|
|
1020
|
+
const parentSchema = fromJSONSchema(
|
|
1021
|
+
{
|
|
1022
|
+
type: "object",
|
|
1023
|
+
title: "User",
|
|
1024
|
+
properties: {
|
|
1025
|
+
name: {
|
|
1026
|
+
type: "string",
|
|
1027
|
+
title: "Name",
|
|
1028
|
+
"x-field-order": 1,
|
|
1029
|
+
},
|
|
1030
|
+
age: {
|
|
1031
|
+
type: "number",
|
|
1032
|
+
title: "Age",
|
|
1033
|
+
deprecated: true,
|
|
1034
|
+
},
|
|
1035
|
+
},
|
|
1036
|
+
},
|
|
1037
|
+
{ registry: customRegistry }
|
|
1038
|
+
);
|
|
1039
|
+
|
|
1040
|
+
// Verify parent schema has its metadata
|
|
1041
|
+
expect(customRegistry.get(parentSchema)?.title).toBe("User");
|
|
1042
|
+
|
|
1043
|
+
// We can't easily access nested schemas directly, but we can verify the registry is being used correctly by checking a separate schema
|
|
1044
|
+
const simpleSchema = fromJSONSchema(
|
|
1045
|
+
{
|
|
1046
|
+
type: "string",
|
|
1047
|
+
title: "Simple",
|
|
1048
|
+
},
|
|
1049
|
+
{ registry: customRegistry }
|
|
1050
|
+
);
|
|
1051
|
+
expect(customRegistry.get(simpleSchema)?.title).toBe("Simple");
|
|
1052
|
+
});
|
|
1053
|
+
|
|
1054
|
+
test("no metadata added when no unrecognized keys", () => {
|
|
1055
|
+
const customRegistry = z.registry<Record<string, unknown>>();
|
|
1056
|
+
|
|
1057
|
+
const schema = fromJSONSchema(
|
|
1058
|
+
{
|
|
1059
|
+
type: "string",
|
|
1060
|
+
minLength: 1,
|
|
1061
|
+
maxLength: 100,
|
|
1062
|
+
description: "A regular string",
|
|
1063
|
+
},
|
|
1064
|
+
{ registry: customRegistry }
|
|
1065
|
+
);
|
|
1066
|
+
|
|
1067
|
+
// description is handled via .describe(), so it shouldn't be in metadata. All other keys are recognized, so no metadata should be added
|
|
1068
|
+
expect(customRegistry.get(schema)).toBeUndefined();
|
|
1069
|
+
});
|
|
1070
|
+
|
|
1071
|
+
test("writeOnly and examples are captured as metadata", () => {
|
|
1072
|
+
const customRegistry = z.registry<{ writeOnly?: boolean; examples?: unknown[] }>();
|
|
1073
|
+
|
|
1074
|
+
const schema = fromJSONSchema(
|
|
1075
|
+
{
|
|
1076
|
+
type: "string",
|
|
1077
|
+
writeOnly: true,
|
|
1078
|
+
examples: ["password123", "secret"],
|
|
1079
|
+
},
|
|
1080
|
+
{ registry: customRegistry }
|
|
1081
|
+
);
|
|
1082
|
+
|
|
1083
|
+
const meta = customRegistry.get(schema);
|
|
1084
|
+
expect(meta?.writeOnly).toBe(true);
|
|
1085
|
+
expect(meta?.examples).toEqual(["password123", "secret"]);
|
|
1086
|
+
});
|
|
1087
|
+
|
|
1088
|
+
test("$comment and $anchor are captured as metadata", () => {
|
|
1089
|
+
const customRegistry = z.registry<{ $comment?: string; $anchor?: string }>();
|
|
1090
|
+
|
|
1091
|
+
const schema = fromJSONSchema(
|
|
1092
|
+
{
|
|
1093
|
+
type: "string",
|
|
1094
|
+
$comment: "This is a developer note",
|
|
1095
|
+
$anchor: "my-anchor",
|
|
1096
|
+
},
|
|
1097
|
+
{ registry: customRegistry }
|
|
1098
|
+
);
|
|
1099
|
+
|
|
1100
|
+
const meta = customRegistry.get(schema);
|
|
1101
|
+
expect(meta?.$comment).toBe("This is a developer note");
|
|
1102
|
+
expect(meta?.$anchor).toBe("my-anchor");
|
|
1103
|
+
});
|
|
1104
|
+
|
|
1105
|
+
test("contentEncoding and contentMediaType are stored as metadata", () => {
|
|
1106
|
+
const customRegistry = z.registry<{ contentEncoding?: string; contentMediaType?: string }>();
|
|
1107
|
+
|
|
1108
|
+
const schema = fromJSONSchema(
|
|
1109
|
+
{
|
|
1110
|
+
type: "string",
|
|
1111
|
+
contentEncoding: "base64",
|
|
1112
|
+
contentMediaType: "image/png",
|
|
1113
|
+
},
|
|
1114
|
+
{ registry: customRegistry }
|
|
1115
|
+
);
|
|
1116
|
+
|
|
1117
|
+
// Should just be a string schema
|
|
1118
|
+
expect(schema.parse("aGVsbG8gd29ybGQ=")).toBe("aGVsbG8gd29ybGQ=");
|
|
1119
|
+
|
|
1120
|
+
// Content keywords should be in metadata
|
|
1121
|
+
const meta = customRegistry.get(schema);
|
|
1122
|
+
expect(meta?.contentEncoding).toBe("base64");
|
|
1123
|
+
expect(meta?.contentMediaType).toBe("image/png");
|
|
1124
|
+
});
|
|
1125
|
+
|
|
1126
|
+
test("description on enum schema is applied", () => {
|
|
1127
|
+
const schema = fromJSONSchema({
|
|
1128
|
+
enum: ["red", "green", "blue"],
|
|
1129
|
+
description: "A color value",
|
|
1130
|
+
});
|
|
1131
|
+
expect(schema.description).toBe("A color value");
|
|
1132
|
+
expect(schema.parse("red")).toBe("red");
|
|
1133
|
+
});
|
|
1134
|
+
|
|
1135
|
+
test("description on const schema is applied", () => {
|
|
1136
|
+
const schema = fromJSONSchema({
|
|
1137
|
+
const: "hello",
|
|
1138
|
+
description: "A greeting",
|
|
1139
|
+
});
|
|
1140
|
+
expect(schema.description).toBe("A greeting");
|
|
1141
|
+
expect(schema.parse("hello")).toBe("hello");
|
|
1142
|
+
});
|
|
1143
|
+
|
|
1144
|
+
test("description on not: {} (never) schema is applied", () => {
|
|
1145
|
+
const schema = fromJSONSchema({
|
|
1146
|
+
not: {},
|
|
1147
|
+
description: "A never schema",
|
|
1148
|
+
});
|
|
1149
|
+
expect(schema.description).toBe("A never schema");
|
|
1150
|
+
expect(() => schema.parse("anything")).toThrow();
|
|
1151
|
+
});
|
|
1152
|
+
|
|
1153
|
+
test("default on enum schema is applied", () => {
|
|
1154
|
+
const schema = fromJSONSchema({
|
|
1155
|
+
enum: ["red", "green", "blue"],
|
|
1156
|
+
default: "red",
|
|
1157
|
+
});
|
|
1158
|
+
expect(schema.parse(undefined)).toBe("red");
|
|
1159
|
+
});
|
|
1160
|
+
|
|
1161
|
+
test("default on const schema is applied", () => {
|
|
1162
|
+
const schema = fromJSONSchema({
|
|
1163
|
+
const: "hello",
|
|
1164
|
+
default: "hello",
|
|
1165
|
+
});
|
|
1166
|
+
expect(schema.parse(undefined)).toBe("hello");
|
|
1167
|
+
});
|
|
1168
|
+
|
|
1169
|
+
test("description and default on enum schema are both applied", () => {
|
|
1170
|
+
const schema = fromJSONSchema({
|
|
1171
|
+
enum: ["red", "green", "blue"],
|
|
1172
|
+
description: "A color value",
|
|
1173
|
+
default: "red",
|
|
1174
|
+
});
|
|
1175
|
+
expect(schema.description).toBe("A color value");
|
|
1176
|
+
expect(schema.parse(undefined)).toBe("red");
|
|
1177
|
+
expect(schema.parse("green")).toBe("green");
|
|
1178
|
+
});
|
|
1179
|
+
|
|
1180
|
+
test("description on type-array schema is applied", () => {
|
|
1181
|
+
const schema = fromJSONSchema({
|
|
1182
|
+
type: ["string", "number"],
|
|
1183
|
+
description: "A string or number",
|
|
1184
|
+
} as any);
|
|
1185
|
+
expect(schema.description).toBe("A string or number");
|
|
1186
|
+
expect(schema.parse("hello")).toBe("hello");
|
|
1187
|
+
expect(schema.parse(42)).toBe(42);
|
|
1188
|
+
});
|
|
1189
|
+
|
|
1190
|
+
test("default on type-array schema is applied", () => {
|
|
1191
|
+
const schema = fromJSONSchema({
|
|
1192
|
+
type: ["string", "number"],
|
|
1193
|
+
default: "fallback",
|
|
1194
|
+
} as any);
|
|
1195
|
+
expect(schema.parse(undefined)).toBe("fallback");
|
|
1196
|
+
expect(schema.parse("hello")).toBe("hello");
|
|
1197
|
+
expect(schema.parse(42)).toBe(42);
|
|
1198
|
+
});
|
|
1199
|
+
|
|
1200
|
+
test("description on schema with anyOf is applied to the outer schema", () => {
|
|
1201
|
+
const schema = fromJSONSchema({
|
|
1202
|
+
description: "Either a string or a number",
|
|
1203
|
+
anyOf: [{ type: "string" }, { type: "number" }],
|
|
1204
|
+
});
|
|
1205
|
+
expect(schema.description).toBe("Either a string or a number");
|
|
1206
|
+
expect(schema.parse("hello")).toBe("hello");
|
|
1207
|
+
expect(schema.parse(42)).toBe(42);
|
|
1208
|
+
});
|
|
1209
|
+
|
|
1210
|
+
test("default on schema with anyOf is applied to the outer schema", () => {
|
|
1211
|
+
const schema = fromJSONSchema({
|
|
1212
|
+
default: "fallback",
|
|
1213
|
+
anyOf: [{ type: "string" }, { type: "number" }],
|
|
1214
|
+
});
|
|
1215
|
+
expect(schema.parse(undefined)).toBe("fallback");
|
|
1216
|
+
expect(schema.parse(42)).toBe(42);
|
|
1217
|
+
});
|
|
1218
|
+
|
|
1219
|
+
test("description and unrecognized metadata coexist on the same schema", () => {
|
|
1220
|
+
const customRegistry = z.registry<{ "x-custom"?: string; description?: string }>();
|
|
1221
|
+
const schema = fromJSONSchema(
|
|
1222
|
+
{
|
|
1223
|
+
type: "string",
|
|
1224
|
+
description: "A custom string",
|
|
1225
|
+
"x-custom": "value",
|
|
1226
|
+
},
|
|
1227
|
+
{ registry: customRegistry }
|
|
1228
|
+
);
|
|
1229
|
+
expect(schema.description).toBe("A custom string");
|
|
1230
|
+
expect(customRegistry.get(schema)?.["x-custom"]).toBe("value");
|
|
1231
|
+
});
|
|
1232
|
+
|
|
1233
|
+
test("circular input throws a clear error", () => {
|
|
1234
|
+
const person: any = {
|
|
1235
|
+
type: "object",
|
|
1236
|
+
properties: { name: { type: "string" } },
|
|
1237
|
+
required: ["name"],
|
|
1238
|
+
};
|
|
1239
|
+
person.properties.bestFriend = person;
|
|
1240
|
+
expect(() => fromJSONSchema(person)).toThrow(/not valid JSON/);
|
|
1241
|
+
});
|
|
1242
|
+
|
|
1243
|
+
test("getter-based input that synthesizes a cycle throws", () => {
|
|
1244
|
+
const root: any = { type: "object", properties: { name: { type: "string" } } };
|
|
1245
|
+
Object.defineProperty(root.properties, "self", {
|
|
1246
|
+
enumerable: true,
|
|
1247
|
+
get() {
|
|
1248
|
+
return root;
|
|
1249
|
+
},
|
|
1250
|
+
});
|
|
1251
|
+
expect(() => fromJSONSchema(root)).toThrow(/not valid JSON/);
|
|
1252
|
+
});
|
|
1253
|
+
|
|
1254
|
+
test("BigInt in input throws", () => {
|
|
1255
|
+
const input: any = { type: "integer", minimum: 1n };
|
|
1256
|
+
expect(() => fromJSONSchema(input)).toThrow(/not valid JSON/);
|
|
1257
|
+
});
|
|
1258
|
+
|
|
1259
|
+
test("class-instance input is normalized to a plain object", () => {
|
|
1260
|
+
class StringSchema {
|
|
1261
|
+
type = "string" as const;
|
|
1262
|
+
minLength = 2;
|
|
1263
|
+
}
|
|
1264
|
+
const schema = fromJSONSchema(new StringSchema() as any);
|
|
1265
|
+
expect(schema.parse("hi")).toBe("hi");
|
|
1266
|
+
expect(() => schema.parse("h")).toThrow();
|
|
1267
|
+
});
|
|
1268
|
+
|
|
1269
|
+
test("getter-based properties are materialized", () => {
|
|
1270
|
+
const input: any = { type: "object", properties: {}, required: [] };
|
|
1271
|
+
Object.defineProperty(input.properties, "name", {
|
|
1272
|
+
enumerable: true,
|
|
1273
|
+
get() {
|
|
1274
|
+
return { type: "string" };
|
|
1275
|
+
},
|
|
1276
|
+
});
|
|
1277
|
+
const schema = fromJSONSchema(input);
|
|
1278
|
+
expect(schema.parse({ name: "Alice" })).toEqual({ name: "Alice" });
|
|
1279
|
+
});
|
|
1280
|
+
|
|
1281
|
+
test("Date default is coerced to its JSON string form", () => {
|
|
1282
|
+
const date = new Date("2026-01-02T03:04:05.000Z");
|
|
1283
|
+
const schema = fromJSONSchema({ type: "string", default: date as any });
|
|
1284
|
+
expect(schema.parse(undefined)).toBe(date.toISOString());
|
|
1285
|
+
});
|
|
1286
|
+
|
|
1287
|
+
// An object literal can't express an own "__proto__" key — `{ __proto__: x }` sets the literal's prototype instead. JSON.parse is both the realistic source of a JSON Schema and the only way to write these cases.
|
|
1288
|
+
test("required __proto__ property is represented in the shape but stripped during parsing", () => {
|
|
1289
|
+
const schema = fromJSONSchema(
|
|
1290
|
+
JSON.parse(`{
|
|
1291
|
+
"type": "object",
|
|
1292
|
+
"properties": { "__proto__": { "type": "string", "const": "admin" }, "role": { "type": "string" } },
|
|
1293
|
+
"required": ["__proto__", "role"]
|
|
1294
|
+
}`)
|
|
1295
|
+
);
|
|
1296
|
+
|
|
1297
|
+
expect(Object.prototype.hasOwnProperty.call((schema as z.YatspObject).shape, "__proto__")).toBe(true);
|
|
1298
|
+
expect(schema.parse({ role: "x" })).toEqual({ role: "x" });
|
|
1299
|
+
expect(schema.parse(JSON.parse(`{ "role": "x", "__proto__": "wrong" }`))).toEqual({ role: "x" });
|
|
1300
|
+
expect(schema.parse(JSON.parse(`{ "role": "x", "__proto__": "admin" }`))).toEqual({ role: "x" });
|
|
1301
|
+
});
|
|
1302
|
+
|
|
1303
|
+
test("__proto__ annotation key reaches the registry", () => {
|
|
1304
|
+
const registry = z.registry<Record<string, unknown>>();
|
|
1305
|
+
const schema = fromJSONSchema(JSON.parse(`{ "type": "string", "__proto__": { "custom": 1 } }`), { registry });
|
|
1306
|
+
|
|
1307
|
+
const meta = registry.get(schema)!;
|
|
1308
|
+
expect(Object.prototype.hasOwnProperty.call(meta, "__proto__")).toBe(true);
|
|
1309
|
+
expect(meta.__proto__).toEqual({ custom: 1 });
|
|
1310
|
+
});
|
|
1311
|
+
|
|
1312
|
+
test("minProperties and maxProperties count the raw input", () => {
|
|
1313
|
+
const schema = fromJSONSchema({
|
|
1314
|
+
type: "object",
|
|
1315
|
+
properties: { a: { type: "string" } },
|
|
1316
|
+
minProperties: 1,
|
|
1317
|
+
maxProperties: 2,
|
|
1318
|
+
});
|
|
1319
|
+
expect(schema.safeParse({ a: "x" }).success).toBe(true);
|
|
1320
|
+
expect(schema.safeParse({}).error!.issues[0]).toMatchObject({ code: "too_small", origin: "object", minimum: 1 });
|
|
1321
|
+
expect(schema.safeParse({ a: "x", b: 1, c: 2 }).error!.issues[0]).toMatchObject({
|
|
1322
|
+
code: "too_big",
|
|
1323
|
+
origin: "object",
|
|
1324
|
+
maximum: 2,
|
|
1325
|
+
});
|
|
1326
|
+
// raw input: a defaulted property does not satisfy the minimum, and a dropped __proto__ still counts
|
|
1327
|
+
const defaulted = fromJSONSchema({
|
|
1328
|
+
type: "object",
|
|
1329
|
+
properties: { a: { type: "string", default: "x" } },
|
|
1330
|
+
minProperties: 1,
|
|
1331
|
+
});
|
|
1332
|
+
expect(defaulted.safeParse({}).success).toBe(false);
|
|
1333
|
+
const capped = fromJSONSchema({ type: "object", maxProperties: 1 });
|
|
1334
|
+
expect(capped.safeParse(JSON.parse('{"__proto__":1,"a":1}')).success).toBe(false);
|
|
1335
|
+
expect(z.toJSONSchema(schema)).toMatchObject({ minProperties: 1, maxProperties: 2 });
|
|
1336
|
+
});
|
|
1337
|
+
|
|
1338
|
+
test("minProperties composes with propertyNames in one guard", () => {
|
|
1339
|
+
const schema = fromJSONSchema({ type: "object", propertyNames: { pattern: "^a" }, minProperties: 1 });
|
|
1340
|
+
expect(schema.safeParse({}).error!.issues[0]!.code).toBe("too_small");
|
|
1341
|
+
expect(schema.safeParse({ b: 1 }).error!.issues[0]!.code).toBe("invalid_key");
|
|
1342
|
+
expect(schema.safeParse({ a: 1 }).success).toBe(true);
|
|
1343
|
+
});
|
|
1344
|
+
|
|
1345
|
+
test("uniqueItems rejects structural duplicates", () => {
|
|
1346
|
+
const schema = fromJSONSchema({ type: "array", uniqueItems: true });
|
|
1347
|
+
expect(
|
|
1348
|
+
schema.safeParse([
|
|
1349
|
+
{ a: 1, b: 2 },
|
|
1350
|
+
{ b: 2, a: 1 },
|
|
1351
|
+
]).error!.issues[0]
|
|
1352
|
+
).toMatchObject({
|
|
1353
|
+
code: "custom",
|
|
1354
|
+
path: [1],
|
|
1355
|
+
});
|
|
1356
|
+
expect(schema.safeParse([1, "1", true, 1]).success).toBe(false);
|
|
1357
|
+
expect(schema.safeParse([{ a: 1 }, { a: 2 }, [1], [2]]).success).toBe(true);
|
|
1358
|
+
expect(z.toJSONSchema(schema)).toMatchObject({ uniqueItems: true });
|
|
1359
|
+
});
|
|
1360
|
+
|
|
1361
|
+
test("contains with minContains and maxContains", () => {
|
|
1362
|
+
const schema = fromJSONSchema({ type: "array", contains: { type: "integer" } });
|
|
1363
|
+
expect(schema.safeParse(["a"]).success).toBe(false);
|
|
1364
|
+
expect(schema.safeParse(["a", 1]).success).toBe(true);
|
|
1365
|
+
const bounded = fromJSONSchema({ type: "array", contains: { type: "integer" }, minContains: 2, maxContains: 3 });
|
|
1366
|
+
expect(bounded.safeParse([1, "a"]).success).toBe(false);
|
|
1367
|
+
expect(bounded.safeParse([1, 2, "a"]).success).toBe(true);
|
|
1368
|
+
expect(bounded.safeParse([1, 2, 3, 4]).success).toBe(false);
|
|
1369
|
+
// minContains: 0 accepts an array with no matches at all
|
|
1370
|
+
expect(fromJSONSchema({ type: "array", contains: { type: "integer" }, minContains: 0 }).safeParse([]).success).toBe(
|
|
1371
|
+
true
|
|
1372
|
+
);
|
|
1373
|
+
expect(z.toJSONSchema(bounded)).toMatchObject({ contains: { type: "integer" }, minContains: 2, maxContains: 3 });
|
|
1374
|
+
});
|
|
1375
|
+
|
|
1376
|
+
test("uniqueItems and contains see the raw instance, not the parsed output", () => {
|
|
1377
|
+
const items = { type: "object", properties: { a: { type: "string", default: "x" } } } as const;
|
|
1378
|
+
// no instance item carries `a`; the default must not synthesize the match
|
|
1379
|
+
const contains = fromJSONSchema({
|
|
1380
|
+
type: "array",
|
|
1381
|
+
items,
|
|
1382
|
+
contains: { type: "object", properties: { a: { const: "x" } }, required: ["a"] },
|
|
1383
|
+
});
|
|
1384
|
+
expect(contains.safeParse([{}]).success).toBe(false);
|
|
1385
|
+
expect(contains.safeParse([{ a: "x" }]).success).toBe(true);
|
|
1386
|
+
// the instance items differ; the default must not collapse them into duplicates
|
|
1387
|
+
const unique = fromJSONSchema({ type: "array", items, uniqueItems: true });
|
|
1388
|
+
expect(unique.safeParse([{ a: "x" }, {}]).success).toBe(true);
|
|
1389
|
+
expect(unique.safeParse([{}, {}]).error!.issues[0]).toMatchObject({ code: "custom", path: [1] });
|
|
1390
|
+
});
|
|
1391
|
+
|
|
1392
|
+
test("a guard keyword takes precedence over the length keywords", () => {
|
|
1393
|
+
const schema = fromJSONSchema({ type: "array", items: { type: "integer" }, minItems: 3, uniqueItems: true });
|
|
1394
|
+
// the guard runs on the input side of the pipe, so its issue aborts before minItems is reached
|
|
1395
|
+
expect(schema.safeParse([1, 1]).error!.issues.map((i) => i.code)).toEqual(["custom"]);
|
|
1396
|
+
expect(schema.safeParse([1, 2]).error!.issues.map((i) => i.code)).toEqual(["too_small"]);
|
|
1397
|
+
});
|
|
1398
|
+
|
|
1399
|
+
test("a guard keyword drops default and examples from the input-mode document", () => {
|
|
1400
|
+
// the guard is a transform on the pipe's input side, and toJSONSchema strips annotations from a transforming schema in input mode
|
|
1401
|
+
const schema = fromJSONSchema({ type: "array", uniqueItems: true, default: [], examples: [[1]] });
|
|
1402
|
+
expect(z.toJSONSchema(schema, { io: "input" })).not.toHaveProperty("default");
|
|
1403
|
+
expect(z.toJSONSchema(schema, { io: "output" })).toMatchObject({ default: [], examples: [[1]] });
|
|
1404
|
+
// without a guard keyword both modes carry them
|
|
1405
|
+
const plain = fromJSONSchema({ type: "array", default: [], examples: [[1]] });
|
|
1406
|
+
expect(z.toJSONSchema(plain, { io: "input" })).toMatchObject({ default: [], examples: [[1]] });
|
|
1407
|
+
});
|
|
1408
|
+
|
|
1409
|
+
test("uniqueItems equality follows JSON semantics", () => {
|
|
1410
|
+
const schema = fromJSONSchema({ type: "array", uniqueItems: true });
|
|
1411
|
+
expect(schema.safeParse([1, "1", true, null]).success).toBe(true);
|
|
1412
|
+
expect(schema.safeParse([{ a: undefined }, {}]).success).toBe(true);
|
|
1413
|
+
expect(
|
|
1414
|
+
schema.safeParse([
|
|
1415
|
+
[1, [2]],
|
|
1416
|
+
[1, [2]],
|
|
1417
|
+
]).success
|
|
1418
|
+
).toBe(false);
|
|
1419
|
+
expect(schema.safeParse([{ a: "b=c" }, { "a=b": "c" }]).success).toBe(true);
|
|
1420
|
+
// every duplicate is reported against the first element it matches
|
|
1421
|
+
expect(schema.safeParse([1, 1, 1]).error!.issues.map((i) => i.path[0])).toEqual([1, 2]);
|
|
1422
|
+
// a cycle cannot be compared, so it is never called a duplicate rather than hanging
|
|
1423
|
+
const cyclic: any = {};
|
|
1424
|
+
cyclic.self = cyclic;
|
|
1425
|
+
expect(schema.safeParse([cyclic, cyclic]).success).toBe(true);
|
|
1426
|
+
});
|
|
1427
|
+
|
|
1428
|
+
test("a carried subschema that references $defs is dropped rather than emitted dangling", () => {
|
|
1429
|
+
const schema = fromJSONSchema({
|
|
1430
|
+
type: "array",
|
|
1431
|
+
contains: { $ref: "#/$defs/Hit" },
|
|
1432
|
+
$defs: { Hit: { type: "object", properties: { id: { type: "string" } }, required: ["id"] } },
|
|
1433
|
+
});
|
|
1434
|
+
// enforcement is unaffected; only the round trip gives the keyword up
|
|
1435
|
+
expect(schema.safeParse([{ id: "a" }]).success).toBe(true);
|
|
1436
|
+
expect(schema.safeParse([{}]).success).toBe(false);
|
|
1437
|
+
expect(JSON.stringify(z.toJSONSchema(schema))).not.toContain("$ref");
|
|
1438
|
+
expect(z.toJSONSchema(schema)).not.toHaveProperty("contains");
|
|
1439
|
+
// an inline subschema is self-contained, so it still round-trips
|
|
1440
|
+
const inline = fromJSONSchema({ type: "array", contains: { type: "integer" } });
|
|
1441
|
+
expect(z.toJSONSchema(inline)).toMatchObject({ contains: { type: "integer" } });
|
|
1442
|
+
// only schema positions are walked, so a `$ref` key in instance data or an unknown annotation still travels
|
|
1443
|
+
for (const annotation of [{ default: { $ref: "literal" } }, { "x-note": { $ref: "literal" } }]) {
|
|
1444
|
+
const annotated = fromJSONSchema({ type: "array", contains: { type: "integer", ...annotation } });
|
|
1445
|
+
expect(annotated.safeParse(["x"]).success).toBe(false);
|
|
1446
|
+
expect(z.toJSONSchema(annotated)).toMatchObject({ contains: { type: "integer" } });
|
|
1447
|
+
}
|
|
1448
|
+
// draft-7 `dependencies` is a schema map; its array form names properties and holds no reference
|
|
1449
|
+
const dependent = fromJSONSchema({
|
|
1450
|
+
type: "array",
|
|
1451
|
+
contains: { type: "object", dependencies: { a: { $ref: "#/definitions/Hit" } } },
|
|
1452
|
+
definitions: { Hit: { type: "object" } },
|
|
1453
|
+
});
|
|
1454
|
+
expect(z.toJSONSchema(dependent)).not.toHaveProperty("contains");
|
|
1455
|
+
const named = fromJSONSchema({
|
|
1456
|
+
type: "array",
|
|
1457
|
+
contains: { type: "object", dependencies: { a: ["b"] } },
|
|
1458
|
+
});
|
|
1459
|
+
expect(z.toJSONSchema(named)).toMatchObject({ contains: { dependencies: { a: ["b"] } } });
|
|
1460
|
+
// a reference nested in a schema position is still caught
|
|
1461
|
+
const nested = fromJSONSchema({
|
|
1462
|
+
type: "array",
|
|
1463
|
+
contains: { type: "array", items: { $ref: "#/$defs/Hit" } },
|
|
1464
|
+
$defs: { Hit: { type: "string" } },
|
|
1465
|
+
});
|
|
1466
|
+
expect(z.toJSONSchema(nested)).not.toHaveProperty("contains");
|
|
1467
|
+
});
|