ata-validator 1.34.0 → 1.36.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -15
- package/build.d.ts +23 -0
- package/build.mjs +4 -0
- package/compiled.d.ts +17 -0
- package/compiled.js +7 -0
- package/compiled.mjs +3 -0
- package/index.browser.mjs +3 -2
- package/index.d.ts +6 -0
- package/index.js +741 -3035
- package/index.mjs +1 -1
- package/index.node.mjs +10 -0
- package/lib/aot-build.js +46 -0
- package/lib/buffer-gate.js +0 -1
- package/lib/compiled.js +96 -0
- package/lib/defaults.js +88 -0
- package/lib/enrich-error.js +7 -0
- package/lib/formats-source.js +323 -0
- package/lib/formats.js +1 -317
- package/lib/interpreter.js +4 -4
- package/lib/js-compiler.js +216 -128
- package/lib/rejections.js +329 -0
- package/lib/schema-order.js +53 -3
- package/lib/validator-core.js +2031 -0
- package/lib/version.js +1 -1
- package/lib/vocabularies.js +1 -2
- package/package.json +24 -11
package/lib/js-compiler.js
CHANGED
|
@@ -72,6 +72,8 @@ function apMembershipCheck (ctx, names, v) {
|
|
|
72
72
|
return `var _apk${i}=Object.keys(${v});for(var _api${i}=0;_api${i}<_apk${i}.length;_api${i}++)if(${id}[_apk${i}[_api${i}]]===undefined)return false`
|
|
73
73
|
}
|
|
74
74
|
}
|
|
75
|
+
// With nothing declared every key is additional; `if()` would not parse.
|
|
76
|
+
if (names.length === 0) return `for(var _k in ${v})return false`
|
|
75
77
|
return `for(var _k in ${v})if(${names.map((k) => `_k!==${JSON.stringify(k)}`).join('&&')})return false`
|
|
76
78
|
}
|
|
77
79
|
|
|
@@ -304,17 +306,13 @@ function compileToJS(schema, defs, schemaMap) {
|
|
|
304
306
|
if (schema.$dynamicRef) {
|
|
305
307
|
const ref = schema.$dynamicRef
|
|
306
308
|
const anchorName = ref.startsWith('#') ? ref : '#' + ref
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
const entry = rootDefs[m[1]]
|
|
315
|
-
checks.push((d) => { const fn = entry.fn; return fn ? fn(d) : true })
|
|
316
|
-
}
|
|
317
|
-
}
|
|
309
|
+
// Resolved at compile time; one that resolves to nothing, or to a
|
|
310
|
+
// definition that cannot compile, declines instead of checking nothing.
|
|
311
|
+
const m = ref.match(/^#\/(?:\$defs|definitions)\/(.+)$/)
|
|
312
|
+
const entry = rootDefs && (rootDefs[anchorName] || (m && rootDefs[m[1]]))
|
|
313
|
+
const fn = entry ? entry.fn : null
|
|
314
|
+
if (!fn) return null
|
|
315
|
+
checks.push(fn)
|
|
318
316
|
}
|
|
319
317
|
|
|
320
318
|
// type
|
|
@@ -634,6 +632,8 @@ function compileToJS(schema, defs, schemaMap) {
|
|
|
634
632
|
})
|
|
635
633
|
}
|
|
636
634
|
|
|
635
|
+
// A definition reached through a cycle and found uncompilable only later.
|
|
636
|
+
if (!defs && rootDefs && rootDefs[DEF_FAILED]) return null
|
|
637
637
|
if (checks.length === 0) return () => true
|
|
638
638
|
if (checks.length === 1) return checks[0]
|
|
639
639
|
|
|
@@ -646,86 +646,48 @@ function compileToJS(schema, defs, schemaMap) {
|
|
|
646
646
|
}
|
|
647
647
|
}
|
|
648
648
|
|
|
649
|
+
// A definition compiled on first use. A reference into it made while it is
|
|
650
|
+
// still compiling (a cycle) gets a function that calls the finished one when
|
|
651
|
+
// it runs. One that fails to compile yields null, which the reference turns
|
|
652
|
+
// into a decline, and marks the set so the root declines too: a cycle may have
|
|
653
|
+
// reached it before the failure was known. It used to return an always-true
|
|
654
|
+
// function for both, so a definition this path could not compile, such as one
|
|
655
|
+
// containing `$ref: '#'`, accepted every value that reached it.
|
|
656
|
+
const DEF_FAILED = Symbol('ata.defFailed')
|
|
657
|
+
function lazyDefEntry (def, defs) {
|
|
658
|
+
let state = 0
|
|
659
|
+
let compiled = null
|
|
660
|
+
return {
|
|
661
|
+
get fn () {
|
|
662
|
+
if (state === 0) {
|
|
663
|
+
state = 1
|
|
664
|
+
compiled = compileToJS(def, defs)
|
|
665
|
+
state = 2
|
|
666
|
+
if (!compiled) defs[DEF_FAILED] = true
|
|
667
|
+
}
|
|
668
|
+
if (state === 1) return (d) => (compiled ? compiled(d) : false)
|
|
669
|
+
return compiled
|
|
670
|
+
},
|
|
671
|
+
raw: def,
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
|
|
649
675
|
function collectDefs(schema) {
|
|
650
676
|
const defs = {}
|
|
651
677
|
const raw = schema.$defs || schema.definitions
|
|
652
678
|
if (raw && typeof raw === 'object') {
|
|
653
679
|
for (const [name, def] of Object.entries(raw)) {
|
|
654
|
-
|
|
655
|
-
let cached = undefined
|
|
656
|
-
defs[name] = {
|
|
657
|
-
get fn() {
|
|
658
|
-
if (cached === undefined) {
|
|
659
|
-
cached = null // sentinel: compilation in progress
|
|
660
|
-
cached = compileToJS(def, defs)
|
|
661
|
-
}
|
|
662
|
-
// cached===null means circular ref or compile failure — be permissive
|
|
663
|
-
return cached || (() => true)
|
|
664
|
-
},
|
|
665
|
-
raw: def
|
|
666
|
-
}
|
|
680
|
+
defs[name] = lazyDefEntry(def, defs)
|
|
667
681
|
// Register anchors
|
|
668
682
|
if (def && typeof def === 'object') {
|
|
669
|
-
if (def.$anchor)
|
|
670
|
-
|
|
671
|
-
let anchorCached = undefined
|
|
672
|
-
defs['#' + def.$anchor] = {
|
|
673
|
-
get fn() {
|
|
674
|
-
if (anchorCached === undefined) {
|
|
675
|
-
anchorCached = null
|
|
676
|
-
anchorCached = compileToJS(anchorDef, defs)
|
|
677
|
-
}
|
|
678
|
-
return anchorCached || (() => true)
|
|
679
|
-
},
|
|
680
|
-
raw: anchorDef
|
|
681
|
-
}
|
|
682
|
-
}
|
|
683
|
-
if (def.$dynamicAnchor) {
|
|
684
|
-
const daDef = def
|
|
685
|
-
let daCached = undefined
|
|
686
|
-
defs['#' + def.$dynamicAnchor] = {
|
|
687
|
-
get fn() {
|
|
688
|
-
if (daCached === undefined) {
|
|
689
|
-
daCached = null
|
|
690
|
-
daCached = compileToJS(daDef, defs)
|
|
691
|
-
}
|
|
692
|
-
return daCached || (() => true)
|
|
693
|
-
},
|
|
694
|
-
raw: daDef
|
|
695
|
-
}
|
|
696
|
-
}
|
|
683
|
+
if (def.$anchor) defs['#' + def.$anchor] = lazyDefEntry(def, defs)
|
|
684
|
+
if (def.$dynamicAnchor) defs['#' + def.$dynamicAnchor] = lazyDefEntry(def, defs)
|
|
697
685
|
}
|
|
698
686
|
}
|
|
699
687
|
}
|
|
700
688
|
// Register root-level $anchor/$dynamicAnchor (self-referencing schemas)
|
|
701
|
-
if (schema.$anchor && !defs['#' + schema.$anchor])
|
|
702
|
-
|
|
703
|
-
let rootACached = undefined
|
|
704
|
-
defs['#' + schema.$anchor] = {
|
|
705
|
-
get fn() {
|
|
706
|
-
if (rootACached === undefined) {
|
|
707
|
-
rootACached = null
|
|
708
|
-
rootACached = compileToJS(rootAnchorSchema, defs)
|
|
709
|
-
}
|
|
710
|
-
return rootACached || (() => true)
|
|
711
|
-
},
|
|
712
|
-
raw: rootAnchorSchema
|
|
713
|
-
}
|
|
714
|
-
}
|
|
715
|
-
if (schema.$dynamicAnchor && !defs['#' + schema.$dynamicAnchor]) {
|
|
716
|
-
const rootDASchema = schema
|
|
717
|
-
let rootDACached = undefined
|
|
718
|
-
defs['#' + schema.$dynamicAnchor] = {
|
|
719
|
-
get fn() {
|
|
720
|
-
if (rootDACached === undefined) {
|
|
721
|
-
rootDACached = null
|
|
722
|
-
rootDACached = compileToJS(rootDASchema, defs)
|
|
723
|
-
}
|
|
724
|
-
return rootDACached || (() => true)
|
|
725
|
-
},
|
|
726
|
-
raw: rootDASchema
|
|
727
|
-
}
|
|
728
|
-
}
|
|
689
|
+
if (schema.$anchor && !defs['#' + schema.$anchor]) defs['#' + schema.$anchor] = lazyDefEntry(schema, defs)
|
|
690
|
+
if (schema.$dynamicAnchor && !defs['#' + schema.$dynamicAnchor]) defs['#' + schema.$dynamicAnchor] = lazyDefEntry(schema, defs)
|
|
729
691
|
return defs
|
|
730
692
|
}
|
|
731
693
|
|
|
@@ -789,12 +751,12 @@ function resolveRef(ref, defs, schemaMap) {
|
|
|
789
751
|
if (m) {
|
|
790
752
|
const name = m[1]
|
|
791
753
|
const entry = defs[name]
|
|
792
|
-
if (entry) return
|
|
754
|
+
if (entry) return entry.fn || null
|
|
793
755
|
}
|
|
794
756
|
// Anchor ref: "#foo"
|
|
795
757
|
if (ref.startsWith('#') && !ref.startsWith('#/')) {
|
|
796
758
|
const entry = defs[ref]
|
|
797
|
-
if (entry) return
|
|
759
|
+
if (entry) return entry.fn || null
|
|
798
760
|
}
|
|
799
761
|
}
|
|
800
762
|
// A referenced schema that cannot itself be compiled must propagate the bail.
|
|
@@ -865,7 +827,7 @@ function _canonical(x) {
|
|
|
865
827
|
return '{' + Object.keys(x).sort().map((k) => JSON.stringify(k) + ':' + _canonical(x[k])).join(',') + '}'
|
|
866
828
|
}
|
|
867
829
|
|
|
868
|
-
const _formats = require('./formats')
|
|
830
|
+
const _formats = { ...require('./formats'), ...require('./formats-source') }
|
|
869
831
|
// The closure path's formats. It carried its own idea of an email and knew
|
|
870
832
|
// five formats in all, so a schema that reached it was told "valid" for
|
|
871
833
|
// anything the other engines turned away; the first call on a fresh validator
|
|
@@ -953,9 +915,16 @@ function protoIs (ctx, v) {
|
|
|
953
915
|
useOwnHelper(ctx, '_PN')
|
|
954
916
|
return `((_PN?${v}.__proto__:_gp(${v}))===_OP)`
|
|
955
917
|
}
|
|
918
|
+
// The flag withPlain declared for `v`, if any, recorded as used when read so
|
|
919
|
+
// withPlain can drop an unused declaration without searching the code for it.
|
|
920
|
+
function plainFlag (ctx, v) {
|
|
921
|
+
const pk = ctx.plainOf ? ctx.plainOf.get(v) : undefined
|
|
922
|
+
if (pk !== undefined) (ctx.plainUsed || (ctx.plainUsed = new Set())).add(pk)
|
|
923
|
+
return pk
|
|
924
|
+
}
|
|
956
925
|
function ownKeyExpr (ctx, v, key) {
|
|
957
926
|
const k = JSON.stringify(key)
|
|
958
|
-
const pk = ctx
|
|
927
|
+
const pk = plainFlag(ctx, v)
|
|
959
928
|
useOwnHelper(ctx, PROTO_NAMES.has(key) || pk !== undefined ? '_h' : '_ok')
|
|
960
929
|
if (PROTO_NAMES.has(key)) return `_h(${v},${k})`
|
|
961
930
|
return pk !== undefined ? `(${pk}?${k} in ${v}:_h(${v},${k}))` : `(${k} in ${v}&&_ok(${v},${k}))`
|
|
@@ -964,7 +933,7 @@ function ownKeyExpr (ctx, v, key) {
|
|
|
964
933
|
// it came from `v` itself. `in` is known to hold, so only the second half runs.
|
|
965
934
|
function ownGuard (ctx, v, key) {
|
|
966
935
|
const k = JSON.stringify(key)
|
|
967
|
-
const pk = ctx
|
|
936
|
+
const pk = plainFlag(ctx, v)
|
|
968
937
|
useOwnHelper(ctx, PROTO_NAMES.has(key) || pk !== undefined ? '_h' : '_ok')
|
|
969
938
|
if (PROTO_NAMES.has(key)) return `_h(${v},${k})`
|
|
970
939
|
return pk !== undefined ? `(${pk}||_h(${v},${k}))` : `_ok(${v},${k})`
|
|
@@ -993,9 +962,10 @@ function withPlain (schema, v, lines, ctx, run) {
|
|
|
993
962
|
else ctx.plainOf.set(v, prev)
|
|
994
963
|
// A node whose keys are all typed never reads the flag; declaring it
|
|
995
964
|
// anyway cost a strict-object check about five percent. Unused, it goes.
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
965
|
+
// Readers record the flag through plainFlag(); searching the lines this
|
|
966
|
+
// node emitted for its name instead made every ancestor scan its whole
|
|
967
|
+
// subtree again, 2.2 of the 5 seconds a 7.6 MB schema took to compile.
|
|
968
|
+
const used = ctx.plainUsed !== undefined && ctx.plainUsed.has(name)
|
|
999
969
|
if (!used && lines[at] === decl) lines.splice(at, 1)
|
|
1000
970
|
}
|
|
1001
971
|
}
|
|
@@ -1157,6 +1127,21 @@ function hasNestedIdScope(node, isRoot = true) {
|
|
|
1157
1127
|
// only safe way out.
|
|
1158
1128
|
const DECLINE = Symbol('ata.decline')
|
|
1159
1129
|
|
|
1130
|
+
// How much source the verdict generator may hoist before it hands the schema to
|
|
1131
|
+
// the interpreted engine. Across the 977 SchemaStore schemas the median emits
|
|
1132
|
+
// 11 KB and the largest ordinary one, SARIF, 3.4 MB; Kestra's emitted 75 MB and
|
|
1133
|
+
// took 7.3 seconds and 1.6 GB before its first answer, which the interpreted
|
|
1134
|
+
// engine gives in about 0.4 seconds.
|
|
1135
|
+
const CODE_BUDGET = 16 * 1024 * 1024
|
|
1136
|
+
let codeBudget = CODE_BUDGET
|
|
1137
|
+
function hoist (ctx, code) {
|
|
1138
|
+
ctx.preamble.push(code)
|
|
1139
|
+
ctx.preambleChars = (ctx.preambleChars || 0) + code.length
|
|
1140
|
+
if (ctx.preambleChars > codeBudget) throw DECLINE
|
|
1141
|
+
}
|
|
1142
|
+
// For tests: a budget small enough to reach with a schema that fits in a test.
|
|
1143
|
+
function _setCodeBudget (n) { codeBudget = n === undefined ? CODE_BUDGET : n }
|
|
1144
|
+
|
|
1160
1145
|
// The error and combined generators compile the subschemas of not, if, contains
|
|
1161
1146
|
// and their anyOf/oneOf closures with the boolean generator. That generator
|
|
1162
1147
|
// follows a reference back to the root with `_validate` and a reference cycle
|
|
@@ -1243,6 +1228,15 @@ function cyclicDefNames (defs) {
|
|
|
1243
1228
|
return out
|
|
1244
1229
|
}
|
|
1245
1230
|
|
|
1231
|
+
// Keys beside a $ref that change nothing about validation: schema organization,
|
|
1232
|
+
// the annotation vocabulary, and the annotation keys editors read.
|
|
1233
|
+
const REF_NEUTRAL_SIBLINGS = new Set([
|
|
1234
|
+
'$ref', '$defs', 'definitions', '$schema', '$id', '$dynamicAnchor', '$anchor', '$comment',
|
|
1235
|
+
'title', 'description', 'default', 'examples', 'deprecated', 'readOnly', 'writeOnly',
|
|
1236
|
+
'markdownDescription', 'deprecationMessage', 'markdownDeprecationMessage', 'enumDescriptions',
|
|
1237
|
+
'markdownEnumDescriptions', 'defaultSnippets', 'doNotSuggest', 'suggestSortText',
|
|
1238
|
+
])
|
|
1239
|
+
|
|
1246
1240
|
function codegenSafe(schema, schemaMap) {
|
|
1247
1241
|
if (typeof schema === 'boolean') return true
|
|
1248
1242
|
if (typeof schema !== 'object' || schema === null) return true
|
|
@@ -1258,7 +1252,10 @@ function codegenSafe(schema, schemaMap) {
|
|
|
1258
1252
|
// Boolean sub-schemas anywhere cause bail — codegen doesn't handle schema=false correctly
|
|
1259
1253
|
// items: false and items: true are constants; every generator handles a
|
|
1260
1254
|
// boolean child at its head, so neither falls through as "nothing".
|
|
1261
|
-
|
|
1255
|
+
// `additionalProperties: true` needs no check of its own, and it says nothing
|
|
1256
|
+
// about the rest of the node: returning early here waved the whole subtree
|
|
1257
|
+
// through, so a nested construct the generator cannot express was dropped
|
|
1258
|
+
// instead of declined, from 1.0.0 to 1.35.0.
|
|
1262
1259
|
if (schema.properties) {
|
|
1263
1260
|
for (const v of Object.values(schema.properties)) {
|
|
1264
1261
|
if (typeof v === 'boolean') continue // constants; every generator handles them at its head
|
|
@@ -1316,10 +1313,15 @@ function codegenSafe(schema, schemaMap) {
|
|
|
1316
1313
|
if (!targetSimple && schema.unevaluatedProperties === undefined && schema.unevaluatedItems === undefined) return false
|
|
1317
1314
|
}
|
|
1318
1315
|
// In Draft 2020-12, $ref with siblings is allowed. Only bail if no unevaluated* keyword
|
|
1319
|
-
// (unevaluated schemas need $ref + siblings to work properly)
|
|
1320
|
-
//
|
|
1321
|
-
|
|
1322
|
-
|
|
1316
|
+
// (unevaluated schemas need $ref + siblings to work properly): without one, the
|
|
1317
|
+
// generators emit the $ref and skip the rest of the node, so a sibling that
|
|
1318
|
+
// validates would be dropped. Schema-organization keywords and annotations
|
|
1319
|
+
// validate nothing and do not count. Annotations are listed rather than
|
|
1320
|
+
// inferred from what is not a validation keyword, so a keyword this list
|
|
1321
|
+
// does not know still declines instead of being skipped. Measured on the
|
|
1322
|
+
// 977 SchemaStore schemas: a $ref with a description or title beside it sent
|
|
1323
|
+
// 223 of them to the interpreted engine.
|
|
1324
|
+
const siblings = Object.keys(schema).filter(k => !REF_NEUTRAL_SIBLINGS.has(k) && !k.startsWith('x-'))
|
|
1323
1325
|
if (siblings.length > 0 && schema.unevaluatedProperties === undefined && schema.unevaluatedItems === undefined) return false
|
|
1324
1326
|
}
|
|
1325
1327
|
|
|
@@ -1333,7 +1335,6 @@ function codegenSafe(schema, schemaMap) {
|
|
|
1333
1335
|
if (typeof schema.additionalProperties === 'object' && schema.additionalProperties !== null) {
|
|
1334
1336
|
if (!codegenSafe(schema.additionalProperties, schemaMap)) return false
|
|
1335
1337
|
}
|
|
1336
|
-
if (schema.additionalProperties === false && !schema.properties) return false
|
|
1337
1338
|
|
|
1338
1339
|
// propertyNames: false — codegen doesn't handle this
|
|
1339
1340
|
// propertyNames: false is emitted as a per-key rejection by every generator.
|
|
@@ -1386,6 +1387,26 @@ function codegenSafe(schema, schemaMap) {
|
|
|
1386
1387
|
}
|
|
1387
1388
|
}
|
|
1388
1389
|
|
|
1390
|
+
// The same checks compileToJSCodegen makes on the root, at every depth. They
|
|
1391
|
+
// were made on the root only, so a nested patternProperties with a Unicode
|
|
1392
|
+
// property escape, or a nested propertyNames the generators cannot express,
|
|
1393
|
+
// compiled to code that ignored it.
|
|
1394
|
+
if (schema.patternProperties) {
|
|
1395
|
+
for (const [pat, sub] of Object.entries(schema.patternProperties)) {
|
|
1396
|
+
if (/\\[pP]\{/.test(pat)) return false
|
|
1397
|
+
if (typeof sub === 'object' && sub !== null && !codegenSafe(sub, schemaMap)) return false
|
|
1398
|
+
}
|
|
1399
|
+
}
|
|
1400
|
+
if (schema.dependentSchemas) {
|
|
1401
|
+
for (const sub of Object.values(schema.dependentSchemas)) {
|
|
1402
|
+
if (typeof sub === 'object' && sub !== null && !codegenSafe(sub, schemaMap)) return false
|
|
1403
|
+
}
|
|
1404
|
+
}
|
|
1405
|
+
if (typeof schema.propertyNames === 'object' && schema.propertyNames !== null) {
|
|
1406
|
+
const supported = ['maxLength', 'minLength', 'pattern', 'const', 'enum']
|
|
1407
|
+
if (Object.keys(schema.propertyNames).some(k => k !== '$schema' && !supported.includes(k))) return false
|
|
1408
|
+
}
|
|
1409
|
+
|
|
1389
1410
|
// Recurse into sub-schemas — bail on boolean schemas in any position
|
|
1390
1411
|
// A boolean subschema is a constant that every generator emits at its
|
|
1391
1412
|
// head with the child's own path. The emitters below test for presence,
|
|
@@ -1749,7 +1770,12 @@ function compileToJSCodegen(schema, schemaMap, userFormats, opts) {
|
|
|
1749
1770
|
|
|
1750
1771
|
const ctx = { varCounter: 0, helpers: [], helperCode: [], preamble: [], shared: [], closureVars: ['_cpLen'], closureVals: [_cpLen], rootDefs, refStack: new Set(), schemaMap: schemaMap || null, anchors, rootSchema: schema, userFormats: userFormats || null, removeNodes }
|
|
1751
1772
|
const lines = []
|
|
1752
|
-
|
|
1773
|
+
try {
|
|
1774
|
+
genCode(schema, 'd', lines, ctx)
|
|
1775
|
+
} catch (e) {
|
|
1776
|
+
if (e === DECLINE) return null
|
|
1777
|
+
throw e
|
|
1778
|
+
}
|
|
1753
1779
|
|
|
1754
1780
|
// Append deferred checks (additionalProperties, unevaluatedProperties) at the end
|
|
1755
1781
|
if (ctx.deferredChecks) {
|
|
@@ -2224,7 +2250,7 @@ function genCodeNode(schema, v, lines, ctx, knownType) {
|
|
|
2224
2250
|
// When withPlain already holds the answer for this object in a flag,
|
|
2225
2251
|
// read the flag: the prototype test is an accessor call, and making it
|
|
2226
2252
|
// twice per object was a tenth of a plain order's verdict.
|
|
2227
|
-
const pk = ctx
|
|
2253
|
+
const pk = plainFlag(ctx, v)
|
|
2228
2254
|
lines.push(`if(!${pk || protoIs(ctx, v)}&&!_hall(${v},${JSON.stringify(typed)}))return false`)
|
|
2229
2255
|
}
|
|
2230
2256
|
} else if (schema.required && schema.required.length > 0) {
|
|
@@ -2375,14 +2401,17 @@ function genCodeNode(schema, v, lines, ctx, knownType) {
|
|
|
2375
2401
|
loop = `{var ${n}=0;for(var ${kv} in ${v})${n}++;if(${n}!==${declared.length}){${loop}}}`
|
|
2376
2402
|
}
|
|
2377
2403
|
_deferOrInline(ctx, lines, v, isObj ? loop : `if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){${loop}}`)
|
|
2378
|
-
} else if (schema.additionalProperties === false &&
|
|
2379
|
-
|
|
2404
|
+
} else if (schema.additionalProperties === false && !schema.patternProperties) {
|
|
2405
|
+
// No `properties` means nothing is declared, so every key is additional.
|
|
2406
|
+
// The generators used to emit nothing for that shape, and the safety gate
|
|
2407
|
+
// declined it everywhere; at any depth it now compiles.
|
|
2408
|
+
const propCount = Object.keys(schema.properties || {}).length
|
|
2380
2409
|
const allRequired = schema.required && schema.required.length === propCount
|
|
2381
2410
|
const inner = allRequired
|
|
2382
2411
|
? (propCount <= 15
|
|
2383
2412
|
? `var _n=0;for(var _k in ${v})_n++;if(_n!==${propCount})return false`
|
|
2384
2413
|
: `if(Object.keys(${v}).length!==${propCount})return false`)
|
|
2385
|
-
: apMembershipCheck(ctx, Object.keys(schema.properties), v)
|
|
2414
|
+
: apMembershipCheck(ctx, Object.keys(schema.properties || {}), v)
|
|
2386
2415
|
_deferOrInline(ctx, lines, v, isObj ? inner : `if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){${inner}}`)
|
|
2387
2416
|
}
|
|
2388
2417
|
|
|
@@ -2650,18 +2679,33 @@ function genContainsNode(schema, v, lines, ctx, knownType, isArr) {
|
|
|
2650
2679
|
function genAnyOfNode(schema, v, lines, ctx, knownType) {
|
|
2651
2680
|
const fi = ctx.varCounter++
|
|
2652
2681
|
const branchBodies = []
|
|
2682
|
+
const reused = []
|
|
2653
2683
|
let canHoist = !!ctx.preamble
|
|
2684
|
+
// Branch reuse as in genOneOfNode; anyOf branches take `_av`, so they keep
|
|
2685
|
+
// their own map.
|
|
2686
|
+
if (canHoist && !ctx.hoistedAny) ctx.hoistedAny = new Map()
|
|
2654
2687
|
for (let i = 0; i < schema.anyOf.length; i++) {
|
|
2688
|
+
const sub = schema.anyOf[i]
|
|
2689
|
+
const key = canHoist ? branchKey(sub, ctx) : undefined
|
|
2690
|
+
const known = key !== undefined ? ctx.hoistedAny.get(key) : undefined
|
|
2691
|
+
if (known !== undefined) {
|
|
2692
|
+
reused[i] = known.name
|
|
2693
|
+
branchBodies.push(known.body)
|
|
2694
|
+
continue
|
|
2695
|
+
}
|
|
2655
2696
|
const subLines = []
|
|
2656
|
-
genCode(
|
|
2697
|
+
genCode(sub, '_av', subLines, ctx)
|
|
2657
2698
|
const body = subLines.length === 0 ? 'return true' : `${subLines.join(';')};return true`
|
|
2658
2699
|
if (/\b_validate\b/.test(body)) canHoist = false
|
|
2659
2700
|
branchBodies.push(body)
|
|
2660
2701
|
}
|
|
2661
2702
|
if (canHoist) {
|
|
2662
2703
|
const names = branchBodies.map((body, i) => {
|
|
2704
|
+
if (reused[i] !== undefined) return reused[i]
|
|
2663
2705
|
const name = `_af${fi}_b${i}`
|
|
2664
|
-
ctx
|
|
2706
|
+
hoist(ctx, `function ${name}(_av){${body}}`)
|
|
2707
|
+
const key = branchKey(schema.anyOf[i], ctx)
|
|
2708
|
+
if (key !== undefined) ctx.hoistedAny.set(key, { name, body })
|
|
2665
2709
|
return name
|
|
2666
2710
|
})
|
|
2667
2711
|
const checks = names.map(n => `${n}(${v})`).join('||')
|
|
@@ -2672,13 +2716,46 @@ function genAnyOfNode(schema, v, lines, ctx, knownType) {
|
|
|
2672
2716
|
}
|
|
2673
2717
|
}
|
|
2674
2718
|
|
|
2719
|
+
// What identifies a oneOf branch for reuse: the schema object, or for a branch
|
|
2720
|
+
// that is only a local $ref beside annotations, the reference itself, since
|
|
2721
|
+
// `{ $ref: '#/definitions/X' }` is a new object at every place it is written.
|
|
2722
|
+
// A reference means the same thing everywhere only when no nested $id opens
|
|
2723
|
+
// another base URI, so that is checked once per compilation.
|
|
2724
|
+
const SCOPE_KEYS = new Set(['$id', '$defs', 'definitions', '$schema', '$anchor', '$dynamicAnchor'])
|
|
2725
|
+
function branchKey (sub, ctx) {
|
|
2726
|
+
if (typeof sub !== 'object' || sub === null) return undefined
|
|
2727
|
+
if (typeof sub.$ref === 'string' && sub.$ref.startsWith('#') &&
|
|
2728
|
+
Object.keys(sub).every(k => (REF_NEUTRAL_SIBLINGS.has(k) && !SCOPE_KEYS.has(k)) || k.startsWith('x-'))) {
|
|
2729
|
+
if (ctx.refKeysSafe === undefined) ctx.refKeysSafe = !hasNestedIdScope(ctx.rootSchema)
|
|
2730
|
+
if (ctx.refKeysSafe) return 'ref:' + sub.$ref
|
|
2731
|
+
}
|
|
2732
|
+
return sub
|
|
2733
|
+
}
|
|
2734
|
+
|
|
2675
2735
|
function genOneOfNode(schema, v, lines, ctx, knownType) {
|
|
2676
2736
|
const fi = ctx.varCounter++
|
|
2677
2737
|
const branchBodies = []
|
|
2738
|
+
const reused = []
|
|
2678
2739
|
let canHoist = !!ctx.preamble
|
|
2740
|
+
// A branch schema object already emitted as a hoisted function is called
|
|
2741
|
+
// again rather than generated again. The same oneOf reached through many
|
|
2742
|
+
// $refs was expanded at every one: SchemaStore's Kestra schema, a oneOf of
|
|
2743
|
+
// several hundred task types referenced from each task list, produced
|
|
2744
|
+
// 86919 branch functions and 197 MB of source, and took seconds to build.
|
|
2745
|
+
// A hoisted branch takes its input as a parameter and reads only
|
|
2746
|
+
// module-level names, so the same schema object gives the same function.
|
|
2747
|
+
if (canHoist && !ctx.hoistedBranch) ctx.hoistedBranch = new Map()
|
|
2679
2748
|
for (let i = 0; i < schema.oneOf.length; i++) {
|
|
2749
|
+
const sub = schema.oneOf[i]
|
|
2750
|
+
const key = canHoist ? branchKey(sub, ctx) : undefined
|
|
2751
|
+
const known = key !== undefined ? ctx.hoistedBranch.get(key) : undefined
|
|
2752
|
+
if (known !== undefined) {
|
|
2753
|
+
reused[i] = known.name
|
|
2754
|
+
branchBodies.push(known.body)
|
|
2755
|
+
continue
|
|
2756
|
+
}
|
|
2680
2757
|
const subLines = []
|
|
2681
|
-
genCode(
|
|
2758
|
+
genCode(sub, '_ov', subLines, ctx)
|
|
2682
2759
|
const body = subLines.length === 0 ? 'return true' : `${subLines.join(';')};return true`
|
|
2683
2760
|
// _validate is the recursive entry — hoisting branches above it breaks scope.
|
|
2684
2761
|
if (/\b_validate\b/.test(body)) canHoist = false
|
|
@@ -2686,8 +2763,11 @@ function genOneOfNode(schema, v, lines, ctx, knownType) {
|
|
|
2686
2763
|
}
|
|
2687
2764
|
if (canHoist) {
|
|
2688
2765
|
const names = branchBodies.map((body, i) => {
|
|
2766
|
+
if (reused[i] !== undefined) return reused[i]
|
|
2689
2767
|
const name = `_of${fi}_b${i}`
|
|
2690
|
-
ctx
|
|
2768
|
+
hoist(ctx, `function ${name}(_ov){${body}}`)
|
|
2769
|
+
const key = branchKey(schema.oneOf[i], ctx)
|
|
2770
|
+
if (key !== undefined) ctx.hoistedBranch.set(key, { name, body })
|
|
2691
2771
|
return name
|
|
2692
2772
|
})
|
|
2693
2773
|
const calls = names.map(n => `if(${n}(${v})){_oc${fi}++;if(_oc${fi}>1)return false}`).join(';')
|
|
@@ -2769,11 +2849,17 @@ function genRefNode(schema, v, lines, ctx, knownType, hasSiblings) {
|
|
|
2769
2849
|
genCode(ctx.rootDefs[defName], 'd', bodyLines, ctx)
|
|
2770
2850
|
if (ctx.deferredChecks) for (const dc of ctx.deferredChecks) bodyLines.push(dc)
|
|
2771
2851
|
ctx.deferredChecks = outerDeferred
|
|
2852
|
+
// The def is hoisted above the entry function, where the recursive
|
|
2853
|
+
// entry `_validate` is not in scope; a body that calls it (a `$ref: '#'`
|
|
2854
|
+
// inside the def) threw a ReferenceError at the first document that
|
|
2855
|
+
// reached it. SchemaStore's jsone schema is such a case. Decline, as
|
|
2856
|
+
// oneOf and anyOf do not hoist such a branch.
|
|
2857
|
+
if (bodyLines.some((l) => /\b_validate\b/.test(l))) throw DECLINE
|
|
2772
2858
|
// Entered through the cycle guard: see emitGuardState. In the fast
|
|
2773
2859
|
// pass the wrapper only counts depth; the set is used by the second,
|
|
2774
2860
|
// guarded pass that runs when data turns out to point back at
|
|
2775
2861
|
// itself.
|
|
2776
|
-
ctx
|
|
2862
|
+
hoist(ctx,
|
|
2777
2863
|
`const ${fnName}_s=new Set()\n function ${fnName}(d){\n ` +
|
|
2778
2864
|
`if(_sg){if(typeof d!=='object'||d===null)return ${fnName}_b(d);if(${fnName}_s.has(d))return true;${fnName}_s.add(d);try{return ${fnName}_b(d)}finally{${fnName}_s.delete(d)}}\n ` +
|
|
2779
2865
|
`if(++_sd>${CYCLE_DEPTH})throw _CYC\n const _r=${fnName}_b(d)\n _sd--\n return _r\n }\n ` +
|
|
@@ -2864,7 +2950,7 @@ function genPatternPropertiesNode(schema, v, lines, ctx, knownType, isObj) {
|
|
|
2864
2950
|
const guard = isObj ? '' : `if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v}))`
|
|
2865
2951
|
|
|
2866
2952
|
const apSchema = typeof schema.additionalProperties === 'object' && schema.additionalProperties !== null ? schema.additionalProperties : null
|
|
2867
|
-
if (
|
|
2953
|
+
if (schema.additionalProperties === false || apSchema) {
|
|
2868
2954
|
// Unified loop: properties + patterns + propertyNames + additionalProperties.
|
|
2869
2955
|
// A key that is neither declared nor matched by any pattern is
|
|
2870
2956
|
// additional: rejected outright, or run through the additionalProperties
|
|
@@ -4231,7 +4317,7 @@ function genCodeENode(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
|
4231
4317
|
const inlineCheck = compilePatternInline(schema.pattern, v)
|
|
4232
4318
|
if (inlineCheck) {
|
|
4233
4319
|
const c = isStr ? `!(${inlineCheck})` : `typeof ${v}==='string'&&!(${inlineCheck})`
|
|
4234
|
-
lines.push(`if(${c}){${fail('pattern', 'pattern', `{pattern:${JSON.stringify(schema.pattern)}}`, `
|
|
4320
|
+
lines.push(`if(${c}){${fail('pattern', 'pattern', `{pattern:${JSON.stringify(schema.pattern)}}`, JSON.stringify(`must match pattern "${schema.pattern}"`))}}`)
|
|
4235
4321
|
} else {
|
|
4236
4322
|
const pattern = JSON.stringify(schema.pattern);
|
|
4237
4323
|
if (!ctx.regExpMap.has(pattern)) {
|
|
@@ -4246,7 +4332,7 @@ function genCodeENode(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
|
4246
4332
|
}
|
|
4247
4333
|
const ri = ctx.regExpMap.get(pattern);
|
|
4248
4334
|
const c = isStr ? `!_re${ri}.test(${v})` : `typeof ${v}==='string'&&!_re${ri}.test(${v})`
|
|
4249
|
-
lines.push(`if(${c}){${fail('pattern', 'pattern', `{pattern:${JSON.stringify(schema.pattern)}}`, `
|
|
4335
|
+
lines.push(`if(${c}){${fail('pattern', 'pattern', `{pattern:${JSON.stringify(schema.pattern)}}`, JSON.stringify(`must match pattern "${schema.pattern}"`))}}`)
|
|
4250
4336
|
}
|
|
4251
4337
|
}
|
|
4252
4338
|
if (schema.format) {
|
|
@@ -4301,7 +4387,7 @@ function genCodeENode(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
|
4301
4387
|
|
|
4302
4388
|
// additionalProperties: false. A key matched by any patternProperties
|
|
4303
4389
|
// entry is not additional, so those patterns are consulted here as well.
|
|
4304
|
-
if (schema.additionalProperties === false
|
|
4390
|
+
if (schema.additionalProperties === false) {
|
|
4305
4391
|
const allowed = Object.keys(schema.properties || {}).map(k => `${JSON.stringify(k)}`).join(',')
|
|
4306
4392
|
const ci = ctx.varCounter++
|
|
4307
4393
|
const apSp = `${schemaPrefix}/additionalProperties`
|
|
@@ -4519,7 +4605,7 @@ function genPatternPropertiesE(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
|
4519
4605
|
const _peFn = emitPtrEsc(ctx)
|
|
4520
4606
|
const p = pathExpr ? `${pathExpr}+'/'+${_peFn}(_k${ki})` : `'/'+${_peFn}(_k${ki})`
|
|
4521
4607
|
// The real pointer, so source maps can locate the failing keyword.
|
|
4522
|
-
genCodeE(sub, `${v}[_k${ki}]`, p, lines, ctx, schemaPrefix + '/patternProperties/' + pat
|
|
4608
|
+
genCodeE(sub, `${v}[_k${ki}]`, p, lines, ctx, schemaPrefix + '/patternProperties/' + ptrSeg(pat))
|
|
4523
4609
|
lines.push(`}}}`)
|
|
4524
4610
|
}
|
|
4525
4611
|
}
|
|
@@ -4595,7 +4681,7 @@ function genPropertyNamesE(schema, v, pathExpr, lines, ctx, schemaPrefix, fail)
|
|
|
4595
4681
|
}
|
|
4596
4682
|
}
|
|
4597
4683
|
const ri = ctx.regExpMap.get(pattern);
|
|
4598
|
-
lines.push(`if(!_re${ri}.test(_k${ki})){${fail('pattern', 'propertyNames/pattern', `{pattern:${JSON.stringify(pn.pattern)}}`, `
|
|
4684
|
+
lines.push(`if(!_re${ri}.test(_k${ki})){${fail('pattern', 'propertyNames/pattern', `{pattern:${JSON.stringify(pn.pattern)}}`, JSON.stringify(`must match pattern "${pn.pattern}"`))}}`)
|
|
4599
4685
|
}
|
|
4600
4686
|
if (pn.const !== undefined) {
|
|
4601
4687
|
lines.push(`if(_k${ki}!==${JSON.stringify(pn.const)}){${fail('const', 'propertyNames/const', `{allowedValue:${JSON.stringify(pn.const)}}`, "'must be equal to constant'")}}`)
|
|
@@ -4625,9 +4711,9 @@ function genContainsE(schema, v, pathExpr, lines, ctx, schemaPrefix, fail) {
|
|
|
4625
4711
|
const minC = schema.minContains !== undefined ? schema.minContains : 1
|
|
4626
4712
|
const maxC = schema.maxContains
|
|
4627
4713
|
lines.push(`if(Array.isArray(${v})){const _cf${ci}=function(_cv){${fnBody}};let _cc${ci}=0;for(let _ci${ci}=0;_ci${ci}<${v}.length;_ci${ci}++){if(_cf${ci}(${v}[_ci${ci}]))_cc${ci}++}`)
|
|
4628
|
-
lines.push(`if(_cc${ci}<${minC}){${fail('contains', 'contains', `{minContains:${minC}}`, `'
|
|
4714
|
+
lines.push(`if(_cc${ci}<${minC}){${fail('contains', 'contains', `{minContains:${minC}}`, `'must contain at least ${minC} valid item(s)'`)}}`)
|
|
4629
4715
|
if (maxC !== undefined) {
|
|
4630
|
-
lines.push(`if(_cc${ci}>${maxC}){${fail('contains', 'contains', `{minContains:${minC},maxContains:${maxC}}`, `'
|
|
4716
|
+
lines.push(`if(_cc${ci}>${maxC}){${fail('contains', 'contains', `{minContains:${minC},maxContains:${maxC}}`, `'must NOT contain more than ${maxC} valid item(s)'`)}}`)
|
|
4631
4717
|
}
|
|
4632
4718
|
lines.push(`}`)
|
|
4633
4719
|
}
|
|
@@ -5019,14 +5105,14 @@ function genCodeCNode(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
|
5019
5105
|
const inlineCheck = compilePatternInline(schema.pattern, v)
|
|
5020
5106
|
if (inlineCheck) {
|
|
5021
5107
|
const c = isStr ? `!(${inlineCheck})` : `typeof ${v}==='string'&&!(${inlineCheck})`
|
|
5022
|
-
lines.push(`if(${c}){${fail('pattern', 'pattern', `{pattern:${JSON.stringify(schema.pattern)}}`, `
|
|
5108
|
+
lines.push(`if(${c}){${fail('pattern', 'pattern', `{pattern:${JSON.stringify(schema.pattern)}}`, JSON.stringify(`must match pattern "${schema.pattern}"`))}}`)
|
|
5023
5109
|
} else {
|
|
5024
5110
|
const ri = ctx.varCounter++
|
|
5025
5111
|
const reVar = `_re${ri}`
|
|
5026
5112
|
ctx.closureVars.push(reVar)
|
|
5027
5113
|
ctx.closureVals.push(patternIsSafe(schema.pattern) ? compileSafe(schema.pattern) : new RegExp(schema.pattern))
|
|
5028
5114
|
const c = isStr ? `!${reVar}.test(${v})` : `typeof ${v}==='string'&&!${reVar}.test(${v})`
|
|
5029
|
-
lines.push(`if(${c}){${fail('pattern', 'pattern', `{pattern:${JSON.stringify(schema.pattern)}}`, `
|
|
5115
|
+
lines.push(`if(${c}){${fail('pattern', 'pattern', `{pattern:${JSON.stringify(schema.pattern)}}`, JSON.stringify(`must match pattern "${schema.pattern}"`))}}`)
|
|
5030
5116
|
}
|
|
5031
5117
|
}
|
|
5032
5118
|
if (schema.format) {
|
|
@@ -5062,8 +5148,8 @@ function genCodeCNode(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
|
5062
5148
|
|
|
5063
5149
|
// additionalProperties — skip if patternProperties present (handled in unified loop below)
|
|
5064
5150
|
// Small property sets: direct === chain (no Set allocation)
|
|
5065
|
-
if (schema.additionalProperties === false &&
|
|
5066
|
-
const propKeys = Object.keys(schema.properties)
|
|
5151
|
+
if (schema.additionalProperties === false && !schema.patternProperties) {
|
|
5152
|
+
const propKeys = Object.keys(schema.properties || {})
|
|
5067
5153
|
const ci = ctx.varCounter++
|
|
5068
5154
|
// The error has to name the offending property, so the keys have to be
|
|
5069
5155
|
// materialised to report one. They do not have to be materialised to find
|
|
@@ -5077,8 +5163,10 @@ function genCodeCNode(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
|
5077
5163
|
let body
|
|
5078
5164
|
if (propKeys.length <= 8) {
|
|
5079
5165
|
// Direct chain: no Set allocation for small schemas
|
|
5080
|
-
|
|
5081
|
-
|
|
5166
|
+
// With nothing declared every key is additional: an empty chain would
|
|
5167
|
+
// leave `if()`, so the test is `true`.
|
|
5168
|
+
const gate = propKeys.map(k => `_f${ci}!==${JSON.stringify(k)}`).join('&&') || 'true'
|
|
5169
|
+
const checks = propKeys.map(k => `_k${ci}[_i]!==${JSON.stringify(k)}`).join('&&') || 'true'
|
|
5082
5170
|
body = `let _x${ci}=0;for(const _f${ci} in ${v}){if(${gate}){_x${ci}=1;break}}if(_x${ci}){${detail(checks)}}`
|
|
5083
5171
|
} else {
|
|
5084
5172
|
const allowed = propKeys.map(k => JSON.stringify(k)).join(',')
|
|
@@ -5297,9 +5385,9 @@ function genPatternPropertiesC(schema, v, pathExpr, lines, ctx, schemaPrefix, fa
|
|
|
5297
5385
|
const guard = isObj ? '' : `if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v}))`
|
|
5298
5386
|
const kVar = `_k${pi}`
|
|
5299
5387
|
|
|
5300
|
-
if (schema.additionalProperties === false
|
|
5388
|
+
if (schema.additionalProperties === false) {
|
|
5301
5389
|
handledPropertyNames = !!pn
|
|
5302
|
-
const propKeys = Object.keys(schema.properties)
|
|
5390
|
+
const propKeys = Object.keys(schema.properties || {})
|
|
5303
5391
|
// Inline key comparison for small property sets
|
|
5304
5392
|
const keyCheck = propKeys.length <= 8
|
|
5305
5393
|
? propKeys.map(k => `${kVar}===${JSON.stringify(k)}`).join('||')
|
|
@@ -5318,12 +5406,12 @@ function genPatternPropertiesC(schema, v, pathExpr, lines, ctx, schemaPrefix, fa
|
|
|
5318
5406
|
if (pn.pattern) {
|
|
5319
5407
|
const fast = fastPrefixCheck(pn.pattern, kVar)
|
|
5320
5408
|
if (fast) {
|
|
5321
|
-
lines.push(`if(!(${fast})){${fail('pattern', 'propertyNames/pattern', `{pattern:${JSON.stringify(pn.pattern)}}`, `
|
|
5409
|
+
lines.push(`if(!(${fast})){${fail('pattern', 'propertyNames/pattern', `{pattern:${JSON.stringify(pn.pattern)}}`, JSON.stringify(`must match pattern "${pn.pattern}"`))}}`)
|
|
5322
5410
|
} else {
|
|
5323
5411
|
const ri = ctx.varCounter++
|
|
5324
5412
|
ctx.closureVars.push(`_re${ri}`)
|
|
5325
5413
|
ctx.closureVals.push(safeReClosure(ctx, pn.pattern))
|
|
5326
|
-
lines.push(`if(!_re${ri}.test(${kVar})){${fail('pattern', 'propertyNames/pattern', `{pattern:${JSON.stringify(pn.pattern)}}`, `
|
|
5414
|
+
lines.push(`if(!_re${ri}.test(${kVar})){${fail('pattern', 'propertyNames/pattern', `{pattern:${JSON.stringify(pn.pattern)}}`, JSON.stringify(`must match pattern "${pn.pattern}"`))}}`)
|
|
5327
5415
|
}
|
|
5328
5416
|
}
|
|
5329
5417
|
if (pn.const !== undefined) lines.push(`if(${kVar}!==${JSON.stringify(pn.const)}){${fail('const', 'propertyNames/const', `{allowedValue:${JSON.stringify(pn.const)}}`, "'must be equal to constant'")}}`)
|
|
@@ -5354,12 +5442,12 @@ function genPatternPropertiesC(schema, v, pathExpr, lines, ctx, schemaPrefix, fa
|
|
|
5354
5442
|
if (pn.pattern) {
|
|
5355
5443
|
const fast = fastPrefixCheck(pn.pattern, kVar)
|
|
5356
5444
|
if (fast) {
|
|
5357
|
-
lines.push(`if(!(${fast})){${fail('pattern', 'propertyNames/pattern', `{pattern:${JSON.stringify(pn.pattern)}}`, `
|
|
5445
|
+
lines.push(`if(!(${fast})){${fail('pattern', 'propertyNames/pattern', `{pattern:${JSON.stringify(pn.pattern)}}`, JSON.stringify(`must match pattern "${pn.pattern}"`))}}`)
|
|
5358
5446
|
} else {
|
|
5359
5447
|
const ri = ctx.varCounter++
|
|
5360
5448
|
ctx.closureVars.push(`_re${ri}`)
|
|
5361
5449
|
ctx.closureVals.push(safeReClosure(ctx, pn.pattern))
|
|
5362
|
-
lines.push(`if(!_re${ri}.test(${kVar})){${fail('pattern', 'propertyNames/pattern', `{pattern:${JSON.stringify(pn.pattern)}}`, `
|
|
5450
|
+
lines.push(`if(!_re${ri}.test(${kVar})){${fail('pattern', 'propertyNames/pattern', `{pattern:${JSON.stringify(pn.pattern)}}`, JSON.stringify(`must match pattern "${pn.pattern}"`))}}`)
|
|
5363
5451
|
}
|
|
5364
5452
|
}
|
|
5365
5453
|
if (pn.const !== undefined) lines.push(`if(${kVar}!==${JSON.stringify(pn.const)}){${fail('const', 'propertyNames/const', `{allowedValue:${JSON.stringify(pn.const)}}`, "'must be equal to constant'")}}`)
|
|
@@ -5408,7 +5496,7 @@ function genPropertyNamesC(schema, v, pathExpr, lines, ctx, schemaPrefix, fail)
|
|
|
5408
5496
|
const ri = ctx.varCounter++
|
|
5409
5497
|
ctx.closureVars.push(`_re${ri}`)
|
|
5410
5498
|
ctx.closureVals.push(safeReClosure(ctx, pn.pattern))
|
|
5411
|
-
lines.push(`if(!_re${ri}.test(_k${ki})){${fail('pattern', 'propertyNames/pattern', `{pattern:${JSON.stringify(pn.pattern)}}`, `
|
|
5499
|
+
lines.push(`if(!_re${ri}.test(_k${ki})){${fail('pattern', 'propertyNames/pattern', `{pattern:${JSON.stringify(pn.pattern)}}`, JSON.stringify(`must match pattern "${pn.pattern}"`))}}`)
|
|
5412
5500
|
}
|
|
5413
5501
|
if (pn.const !== undefined) {
|
|
5414
5502
|
lines.push(`if(_k${ki}!==${JSON.stringify(pn.const)}){${fail('const', 'propertyNames/const', `{allowedValue:${JSON.stringify(pn.const)}}`, "'must be equal to constant'")}}`)
|
|
@@ -5439,8 +5527,8 @@ function genContainsC(schema, v, pathExpr, lines, ctx, schemaPrefix, fail) {
|
|
|
5439
5527
|
const minC = schema.minContains !== undefined ? schema.minContains : 1
|
|
5440
5528
|
const maxC = schema.maxContains
|
|
5441
5529
|
lines.push(`if(Array.isArray(${v})){const _cf${ci}=function(_cv){${fnBody}};let _cc${ci}=0;for(let _ci${ci}=0;_ci${ci}<${v}.length;_ci${ci}++){if(_cf${ci}(${v}[_ci${ci}]))_cc${ci}++}`)
|
|
5442
|
-
lines.push(`if(_cc${ci}<${minC}){${fail('contains', 'contains', `{minContains:${minC}}`, `'
|
|
5443
|
-
if (maxC !== undefined) lines.push(`if(_cc${ci}>${maxC}){${fail('contains', 'contains', `{minContains:${minC},maxContains:${maxC}}`, `'
|
|
5530
|
+
lines.push(`if(_cc${ci}<${minC}){${fail('contains', 'contains', `{minContains:${minC}}`, `'must contain at least ${minC} valid item(s)'`)}}`)
|
|
5531
|
+
if (maxC !== undefined) lines.push(`if(_cc${ci}>${maxC}){${fail('contains', 'contains', `{minContains:${minC},maxContains:${maxC}}`, `'must NOT contain more than ${maxC} valid item(s)'`)}}`)
|
|
5444
5532
|
lines.push(`}`)
|
|
5445
5533
|
}
|
|
5446
5534
|
|
|
@@ -5609,4 +5697,4 @@ function _collectEval(schema, result, defs, schemaMap, refStack, isRoot) {
|
|
|
5609
5697
|
// not → contributes nothing (spec: annotations from not are discarded)
|
|
5610
5698
|
}
|
|
5611
5699
|
|
|
5612
|
-
module.exports = { compileToJS, compileToJSCodegen, compileToJSCodegenWithErrors, compileToJSCombined, collectEvaluated, unevalContributions, AJV_MESSAGES }
|
|
5700
|
+
module.exports = { compileToJS, compileToJSCodegen, compileToJSCodegenWithErrors, compileToJSCombined, collectEvaluated, unevalContributions, AJV_MESSAGES, _setCodeBudget }
|