ata-validator 1.37.1 → 1.39.1
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 +2 -2
- package/index.d.ts +2 -1
- package/lib/aot-impl.js +19 -13
- package/lib/buffer-gate.browser.js +6 -1
- package/lib/buffer-gate.js +6 -7
- package/lib/enrich-error.js +4 -2
- package/lib/js-compiler.js +352 -61
- package/lib/regex-linear.js +65 -0
- package/lib/rejections.js +44 -6
- package/lib/safe-regex.js +424 -413
- package/lib/validator-core.js +7 -1
- package/lib/version.js +1 -1
- package/package.json +12 -12
- package/lib/safe-regex-source.js +0 -8
package/lib/js-compiler.js
CHANGED
|
@@ -135,12 +135,26 @@ function enumCondition(ctx, vals, v) {
|
|
|
135
135
|
const { codeFor } = require('./error-codes')
|
|
136
136
|
const { ordinalFor } = require('./schema-order')
|
|
137
137
|
const { compileSafe, patternIsSafe } = require('./safe-regex')
|
|
138
|
+
// Whether a pattern goes to the linear-time engine. A pattern the platform
|
|
139
|
+
// RegExp also runs in linear time (nativeIsLinear) takes the RegExp: it answers
|
|
140
|
+
// the same, runs faster, and a compiled module that needs no other pattern
|
|
141
|
+
// does not carry the engine. Patterns the engine cannot represent keep the
|
|
142
|
+
// RegExp, as before.
|
|
143
|
+
// Loaded on the first pattern, so a schema without one does not pay for it.
|
|
144
|
+
let _nativeIsLinear = null
|
|
145
|
+
// The linearity check only parses; it runs first so a pattern bound for the
|
|
146
|
+
// RegExp is never compiled for the engine just to be told it could have been.
|
|
147
|
+
const useSafeEngine = (src) => {
|
|
148
|
+
if (_nativeIsLinear === null) _nativeIsLinear = require('./regex-linear').nativeIsLinear
|
|
149
|
+
if (_nativeIsLinear(src)) return false
|
|
150
|
+
return patternIsSafe(src)
|
|
151
|
+
}
|
|
138
152
|
|
|
139
153
|
// Closure value for a patternProperties/propertyNames regex: the linear-time
|
|
140
154
|
// safe matcher when the pattern is in the supported subset (and flag the ctx so
|
|
141
155
|
// standalone embeds the engine), else a plain RegExp. Both expose `.test`.
|
|
142
156
|
function safeReClosure (ctx, src) {
|
|
143
|
-
if (
|
|
157
|
+
if (useSafeEngine(src)) { ctx.usesSafeRe = true; return compileSafe(src) }
|
|
144
158
|
return new RegExp(src)
|
|
145
159
|
}
|
|
146
160
|
|
|
@@ -272,6 +286,9 @@ function compileToJS(schema, defs, schemaMap) {
|
|
|
272
286
|
|
|
273
287
|
if (!defs && needsBaseTracking(schema, schemaMap, new Set())) return null
|
|
274
288
|
if (!defs && externalDocsNeedInterpreter(schema, schemaMap)) return null
|
|
289
|
+
// A $ref cycle on one value recurses here as it does in generated code; see
|
|
290
|
+
// hasSameInstanceRefCycle.
|
|
291
|
+
if (!defs && hasSameInstanceRefCycle(schema)) return null
|
|
275
292
|
|
|
276
293
|
// unevaluatedProperties / unevaluatedItems need annotation tracking this
|
|
277
294
|
// path does not do. Decline so the schema reaches an engine that does.
|
|
@@ -523,7 +540,7 @@ function compileToJS(schema, defs, schemaMap) {
|
|
|
523
540
|
}
|
|
524
541
|
if (schema.pattern) {
|
|
525
542
|
try {
|
|
526
|
-
const re =
|
|
543
|
+
const re = useSafeEngine(schema.pattern) ? compileSafe(schema.pattern) : new RegExp(schema.pattern)
|
|
527
544
|
checks.push((d) => typeof d !== 'string' || re.test(d))
|
|
528
545
|
} catch {
|
|
529
546
|
return null
|
|
@@ -1188,6 +1205,31 @@ function emitGuardedRun (attempt, reset, defSets) {
|
|
|
1188
1205
|
)
|
|
1189
1206
|
}
|
|
1190
1207
|
|
|
1208
|
+
// Definitions referenced from more than one place whose body is large enough
|
|
1209
|
+
// that repeating its error code at every reference costs more than a call.
|
|
1210
|
+
function sharedDefNames (root, defs) {
|
|
1211
|
+
const out = new Set()
|
|
1212
|
+
if (!defs || typeof defs !== 'object') return out
|
|
1213
|
+
const counts = new Map()
|
|
1214
|
+
const seen = new WeakSet()
|
|
1215
|
+
const walk = (node) => {
|
|
1216
|
+
if (node === null || typeof node !== 'object' || seen.has(node)) return
|
|
1217
|
+
seen.add(node)
|
|
1218
|
+
if (typeof node.$ref === 'string') {
|
|
1219
|
+
const m = /^#\/(?:\$defs|definitions)\/([^/]+)$/.exec(node.$ref)
|
|
1220
|
+
if (m) counts.set(m[1], (counts.get(m[1]) || 0) + 1)
|
|
1221
|
+
}
|
|
1222
|
+
for (const v of Object.values(node)) walk(v)
|
|
1223
|
+
}
|
|
1224
|
+
walk(root)
|
|
1225
|
+
for (const [name, n] of counts) {
|
|
1226
|
+
const def = defs[name]
|
|
1227
|
+
if (n >= 2 && def && typeof def === 'object' && JSON.stringify(def).length >= SHARED_DEF_MIN_CHARS) out.add(name)
|
|
1228
|
+
}
|
|
1229
|
+
return out
|
|
1230
|
+
}
|
|
1231
|
+
const SHARED_DEF_MIN_CHARS = 120
|
|
1232
|
+
|
|
1191
1233
|
function cyclicDefNames (defs) {
|
|
1192
1234
|
const out = new Set()
|
|
1193
1235
|
if (!defs || typeof defs !== 'object') return out
|
|
@@ -1261,6 +1303,76 @@ function aliasChainEnds(def, defs) {
|
|
|
1261
1303
|
return true
|
|
1262
1304
|
}
|
|
1263
1305
|
|
|
1306
|
+
// When the properties a schema evaluates depend on which subschemas pass,
|
|
1307
|
+
// genUnevaluatedPropertiesNode picks one of a few runtime models: if/then/else,
|
|
1308
|
+
// one anyOf or oneOf, dependentSchemas, or patterns (root, allOf and a lone
|
|
1309
|
+
// if). Each model reads only the keywords it was written for. A schema whose
|
|
1310
|
+
// evaluated set comes from anything else was compiled with that part ignored,
|
|
1311
|
+
// and a lone `if` with properties emitted no check at all, which accepted every
|
|
1312
|
+
// object. This says whether the model the emitter will pick covers every
|
|
1313
|
+
// source of evaluated properties the schema has; when it does not, the
|
|
1314
|
+
// schema is not generated and the interpreter answers.
|
|
1315
|
+
function unevalPropsModeled(schema, schemaMap) {
|
|
1316
|
+
const own = Object.assign({}, schema)
|
|
1317
|
+
delete own.$defs
|
|
1318
|
+
delete own.definitions
|
|
1319
|
+
if (/"\$(?:ref|dynamicRef|recursiveRef)"/.test(JSON.stringify(own))) return false
|
|
1320
|
+
// A subschema whose evaluated names are a fixed list, which is all the
|
|
1321
|
+
// branch, dependentSchemas and if/then/else models can represent.
|
|
1322
|
+
const fixed = (s) => {
|
|
1323
|
+
if (typeof s === 'boolean') return true
|
|
1324
|
+
if (typeof s !== 'object' || s === null) return false
|
|
1325
|
+
const r = collectEvaluated(s, schemaMap)
|
|
1326
|
+
const j = JSON.stringify(s)
|
|
1327
|
+
return !r.dynamic && !r.allProps && !j.includes('"patternProperties"') && !j.includes('"unevaluatedProperties"')
|
|
1328
|
+
}
|
|
1329
|
+
const allOf = Array.isArray(schema.allOf) ? schema.allOf : []
|
|
1330
|
+
for (const sub of allOf) {
|
|
1331
|
+
if (typeof sub === 'boolean') continue
|
|
1332
|
+
if (typeof sub !== 'object' || sub === null) return false
|
|
1333
|
+
const r = collectEvaluated(sub, schemaMap)
|
|
1334
|
+
if (r.dynamic || r.allProps) return false
|
|
1335
|
+
const rest = Object.assign({}, sub)
|
|
1336
|
+
delete rest.patternProperties
|
|
1337
|
+
const j = JSON.stringify(rest)
|
|
1338
|
+
if (j.includes('"patternProperties"') || j.includes('"unevaluatedProperties"')) return false
|
|
1339
|
+
}
|
|
1340
|
+
const patterns = !!schema.patternProperties || allOf.some((s) => s && typeof s === 'object' && s.patternProperties)
|
|
1341
|
+
const branch = schema.anyOf || schema.oneOf
|
|
1342
|
+
if (schema.anyOf && schema.oneOf) return false
|
|
1343
|
+
const hasIf = schema.if !== undefined
|
|
1344
|
+
const hasTE = schema.then !== undefined || schema.else !== undefined
|
|
1345
|
+
const dep = schema.dependentSchemas
|
|
1346
|
+
if (schema.unevaluatedProperties !== false) {
|
|
1347
|
+
// The schema-valued model reads the fixed names and one branch keyword.
|
|
1348
|
+
if (hasIf || hasTE || dep || patterns) return false
|
|
1349
|
+
return !branch || branch.every(fixed)
|
|
1350
|
+
}
|
|
1351
|
+
if (hasIf && hasTE && !branch && !schema.patternProperties && !dep) {
|
|
1352
|
+
if (patterns) return false
|
|
1353
|
+
if (!fixed(schema.if) || (schema.then !== undefined && !fixed(schema.then)) || (schema.else !== undefined && !fixed(schema.else))) return false
|
|
1354
|
+
// The model credits the if's own `properties` names, and only those.
|
|
1355
|
+
const ifNames = typeof schema.if === 'object' ? collectEvaluated(schema.if, schemaMap).props : []
|
|
1356
|
+
const declared = typeof schema.if === 'object' && schema.if.properties ? Object.keys(schema.if.properties) : []
|
|
1357
|
+
return ifNames.length === declared.length && ifNames.every((k) => declared.includes(k))
|
|
1358
|
+
}
|
|
1359
|
+
if (branch) return !hasIf && !hasTE && !dep && !patterns && branch.every(fixed)
|
|
1360
|
+
if (dep) return !hasIf && !hasTE && !patterns && Object.values(dep).every(fixed)
|
|
1361
|
+
if (hasTE) return false
|
|
1362
|
+
if (hasIf) {
|
|
1363
|
+
// A lone if contributes through its top-level patternProperties only.
|
|
1364
|
+
if (typeof schema.if === 'boolean') return true
|
|
1365
|
+
if (typeof schema.if !== 'object' || schema.if === null) return false
|
|
1366
|
+
const r = collectEvaluated(schema.if, schemaMap)
|
|
1367
|
+
if (r.dynamic || r.allProps || r.props.length) return false
|
|
1368
|
+
const rest = Object.assign({}, schema.if)
|
|
1369
|
+
delete rest.patternProperties
|
|
1370
|
+
const j = JSON.stringify(rest)
|
|
1371
|
+
if (j.includes('"patternProperties"') || j.includes('"unevaluatedProperties"')) return false
|
|
1372
|
+
}
|
|
1373
|
+
return true
|
|
1374
|
+
}
|
|
1375
|
+
|
|
1264
1376
|
function codegenSafe(schema, schemaMap) {
|
|
1265
1377
|
if (typeof schema === 'boolean') return true
|
|
1266
1378
|
if (typeof schema !== 'object' || schema === null) return true
|
|
@@ -1377,6 +1489,7 @@ function codegenSafe(schema, schemaMap) {
|
|
|
1377
1489
|
if (schema.unevaluatedProperties === false || (typeof schema.unevaluatedProperties === 'object' && schema.unevaluatedProperties !== null)) {
|
|
1378
1490
|
const evalResult = collectEvaluated(schema, schemaMap)
|
|
1379
1491
|
if (evalResult.dynamic && evalResult.allProps) return false
|
|
1492
|
+
if (evalResult.dynamic && !unevalPropsModeled(schema, schemaMap)) return false
|
|
1380
1493
|
}
|
|
1381
1494
|
}
|
|
1382
1495
|
// unevaluatedItems: allow boolean and schema values
|
|
@@ -1715,8 +1828,85 @@ function externalDocsNeedInterpreter(schema, schemaMap) {
|
|
|
1715
1828
|
return false
|
|
1716
1829
|
}
|
|
1717
1830
|
|
|
1831
|
+
// A local $ref cycle that returns to a schema without moving to another value,
|
|
1832
|
+
// such as `A: { oneOf: [{ $ref: '#/$defs/A' }, ...] }`, never terminates on its
|
|
1833
|
+
// own. The interpreter stops it and answers; generated code recursed until the
|
|
1834
|
+
// stack ran out and threw a RangeError into the caller, on primitives, where
|
|
1835
|
+
// the cycle guard has no object to recognise. Cycles that pass through
|
|
1836
|
+
// properties, items or any other keyword that moves to a child value are
|
|
1837
|
+
// ordinary recursion and are not affected.
|
|
1838
|
+
const SAME_INSTANCE_LISTS = ['allOf', 'anyOf', 'oneOf']
|
|
1839
|
+
const SAME_INSTANCE_ONE = ['not', 'if', 'then', 'else']
|
|
1840
|
+
function localPointerTarget(root, ref) {
|
|
1841
|
+
if (typeof ref !== 'string' || ref.charCodeAt(0) !== 35) return null
|
|
1842
|
+
if (ref === '#') return root
|
|
1843
|
+
if (ref.charCodeAt(1) !== 47) return null
|
|
1844
|
+
let t = root
|
|
1845
|
+
for (let seg of ref.slice(2).split('/')) {
|
|
1846
|
+
if (seg.indexOf('%') >= 0) { try { seg = decodeURIComponent(seg) } catch { return null } }
|
|
1847
|
+
seg = seg.replace(/~1/g, '/').replace(/~0/g, '~')
|
|
1848
|
+
if (t === null || typeof t !== 'object') return null
|
|
1849
|
+
if (!Object.prototype.hasOwnProperty.call(t, seg)) {
|
|
1850
|
+
const alias = seg === 'definitions' ? '$defs' : seg === '$defs' ? 'definitions' : null
|
|
1851
|
+
if (alias === null || !Object.prototype.hasOwnProperty.call(t, alias)) return null
|
|
1852
|
+
seg = alias
|
|
1853
|
+
}
|
|
1854
|
+
t = t[seg]
|
|
1855
|
+
}
|
|
1856
|
+
return t !== null && typeof t === 'object' ? t : null
|
|
1857
|
+
}
|
|
1858
|
+
function sameInstanceEdges(root, node) {
|
|
1859
|
+
const out = []
|
|
1860
|
+
for (const k of SAME_INSTANCE_LISTS) if (Array.isArray(node[k])) for (const x of node[k]) if (x && typeof x === 'object') out.push(x)
|
|
1861
|
+
for (const k of SAME_INSTANCE_ONE) if (node[k] && typeof node[k] === 'object') out.push(node[k])
|
|
1862
|
+
if (node.dependentSchemas && typeof node.dependentSchemas === 'object') for (const x of Object.values(node.dependentSchemas)) if (x && typeof x === 'object') out.push(x)
|
|
1863
|
+
if (typeof node.$ref === 'string') { const t = localPointerTarget(root, node.$ref); if (t) out.push(t) }
|
|
1864
|
+
return out
|
|
1865
|
+
}
|
|
1866
|
+
const _sameInstanceCycleCache = new WeakMap()
|
|
1867
|
+
function hasSameInstanceRefCycle(root) {
|
|
1868
|
+
if (root === null || typeof root !== 'object') return false
|
|
1869
|
+
const hit = _sameInstanceCycleCache.get(root)
|
|
1870
|
+
if (hit !== undefined) return hit
|
|
1871
|
+
let found = false
|
|
1872
|
+
if (JSON.stringify(root).includes('"$ref"')) {
|
|
1873
|
+
// Only what validation can reach: definitions are entered through the
|
|
1874
|
+
// references that name them, so a broken definition nothing uses (a real
|
|
1875
|
+
// SchemaStore schema has one that includes itself) does not cost the
|
|
1876
|
+
// whole schema its generated code.
|
|
1877
|
+
const nodes = []
|
|
1878
|
+
const seen = new Set()
|
|
1879
|
+
const collect = (n) => {
|
|
1880
|
+
if (n === null || typeof n !== 'object' || seen.has(n)) return
|
|
1881
|
+
seen.add(n)
|
|
1882
|
+
if (Array.isArray(n)) { for (const v of n) collect(v); return }
|
|
1883
|
+
nodes.push(n)
|
|
1884
|
+
for (const [k, v] of Object.entries(n)) {
|
|
1885
|
+
if (k === '$defs' || k === 'definitions' || k === 'enum' || k === 'const' || k === 'default' || k === 'examples') continue
|
|
1886
|
+
collect(v)
|
|
1887
|
+
}
|
|
1888
|
+
if (typeof n.$ref === 'string') collect(localPointerTarget(root, n.$ref))
|
|
1889
|
+
}
|
|
1890
|
+
collect(root)
|
|
1891
|
+
const state = new Map() // 1 on the stack, 2 done
|
|
1892
|
+
const visit = (n) => {
|
|
1893
|
+
const st = state.get(n)
|
|
1894
|
+
if (st === 1) return true
|
|
1895
|
+
if (st === 2) return false
|
|
1896
|
+
state.set(n, 1)
|
|
1897
|
+
for (const m of sameInstanceEdges(root, n)) if (visit(m)) return true
|
|
1898
|
+
state.set(n, 2)
|
|
1899
|
+
return false
|
|
1900
|
+
}
|
|
1901
|
+
for (const n of nodes) if (visit(n)) { found = true; break }
|
|
1902
|
+
}
|
|
1903
|
+
_sameInstanceCycleCache.set(root, found)
|
|
1904
|
+
return found
|
|
1905
|
+
}
|
|
1906
|
+
|
|
1718
1907
|
function sharedCodegenGate(schema, schemaMap) {
|
|
1719
1908
|
if (typeof schema !== 'object' || schema === null) return true
|
|
1909
|
+
if (hasSameInstanceRefCycle(schema)) return false
|
|
1720
1910
|
if (!codegenSafe(schema, schemaMap)) return false
|
|
1721
1911
|
if (needsBaseTracking(schema, schemaMap, new Set())) return false
|
|
1722
1912
|
if (externalDocsNeedInterpreter(schema, schemaMap)) return false
|
|
@@ -1725,16 +1915,10 @@ function sharedCodegenGate(schema, schemaMap) {
|
|
|
1725
1915
|
|
|
1726
1916
|
// --- Codegen mode: generates a single Function (NOT CSP-safe) ---
|
|
1727
1917
|
// This matches ajv's approach: one monolithic function, V8 JIT fully inlines it
|
|
1728
|
-
// `propertyNames` as the generators read it
|
|
1729
|
-
//
|
|
1730
|
-
//
|
|
1731
|
-
//
|
|
1732
|
-
// a copy of the nodes that change: a local reference, through aliases, is
|
|
1733
|
-
// replaced by what it names, `type: 'string'` is dropped since every property
|
|
1734
|
-
// name is a string, and annotations go. Where what is left is still not all
|
|
1735
|
-
// supported keys, the node is left as written and the gate declines as before.
|
|
1736
|
-
// Errors keep the path the interpreted engine reports, which runs through the
|
|
1737
|
-
// reference without naming it.
|
|
1918
|
+
// `propertyNames` as the generators read it (maxLength, minLength, pattern,
|
|
1919
|
+
// const, enum): a local $ref, through aliases, becomes what it names, and
|
|
1920
|
+
// `type: 'string'` and annotations go, on a copy. Anything else is left as
|
|
1921
|
+
// written and the gate declines. Errors keep the interpreted engine's path.
|
|
1738
1922
|
const PN_SUPPORTED = new Set(['maxLength', 'minLength', 'pattern', 'const', 'enum'])
|
|
1739
1923
|
const PN_ANNOTATIONS = new Set(['$schema', '$comment', 'title', 'description', 'examples', 'default', 'deprecated', 'readOnly', 'writeOnly'])
|
|
1740
1924
|
function simplePropertyNames(pn, defs) {
|
|
@@ -2476,7 +2660,7 @@ function genCodeNode(schema, v, lines, ctx, knownType) {
|
|
|
2476
2660
|
if (!ctx.regExpMap.has(pattern)) {
|
|
2477
2661
|
const ri = ctx.varCounter++
|
|
2478
2662
|
ctx.regExpMap.set(pattern, ri)
|
|
2479
|
-
if (
|
|
2663
|
+
if (useSafeEngine(schema.pattern)) {
|
|
2480
2664
|
ctx.helperCode.push(`const _re${ri}=__ataSafeRe(${pattern})`);
|
|
2481
2665
|
ctx.usesSafeRe = true
|
|
2482
2666
|
} else {
|
|
@@ -2529,8 +2713,14 @@ function genCodeNode(schema, v, lines, ctx, knownType) {
|
|
|
2529
2713
|
// No `properties` means nothing is declared, so every key is additional.
|
|
2530
2714
|
// The generators used to emit nothing for that shape, and the safety gate
|
|
2531
2715
|
// declined it everywhere; at any depth it now compiles.
|
|
2532
|
-
const
|
|
2533
|
-
const
|
|
2716
|
+
const declaredKeys = Object.keys(schema.properties || {})
|
|
2717
|
+
const propCount = declaredKeys.length
|
|
2718
|
+
// Counting keys proves there is no extra key only when every declared
|
|
2719
|
+
// name is required. Comparing the list lengths was not that: with
|
|
2720
|
+
// `properties: {c}` and `required: ['b']` the counts matched, and `{b: 1}`
|
|
2721
|
+
// was accepted although `b` is an additional property.
|
|
2722
|
+
const req = new Set(schema.required || [])
|
|
2723
|
+
const allRequired = !!schema.required && declaredKeys.every((k) => req.has(k))
|
|
2534
2724
|
const inner = allRequired
|
|
2535
2725
|
? (propCount <= 15
|
|
2536
2726
|
? `var _n=0;for(var _k in ${v})_n++;if(_n!==${propCount})return false`
|
|
@@ -2637,8 +2827,12 @@ function genCodeNode(schema, v, lines, ctx, knownType) {
|
|
|
2637
2827
|
}
|
|
2638
2828
|
|
|
2639
2829
|
// anyOf — branch fns hoisted when safe, inline fallback when recursive.
|
|
2640
|
-
//
|
|
2641
|
-
|
|
2830
|
+
// The anyOf model in genUnevaluatedPropertiesNode checks that a branch
|
|
2831
|
+
// passes while it records what the branches evaluate, so anyOf is emitted
|
|
2832
|
+
// once, there. Only that model does: with unevaluatedProperties true or a
|
|
2833
|
+
// schema, skipping it here left anyOf unchecked and accepted a document no
|
|
2834
|
+
// branch matched.
|
|
2835
|
+
if (schema.anyOf && !unevalChecksAnyOf(schema, ctx)) genAnyOfNode(schema, v, lines, ctx, knownType)
|
|
2642
2836
|
|
|
2643
2837
|
// oneOf — branch fns hoisted to factory scope when safe (no recursion/ref).
|
|
2644
2838
|
// Falls back to inline closures if any branch touches recursive validation.
|
|
@@ -3170,6 +3364,12 @@ function genPatternPropertiesNode(schema, v, lines, ctx, knownType, isObj) {
|
|
|
3170
3364
|
return handledPropertyNames
|
|
3171
3365
|
}
|
|
3172
3366
|
|
|
3367
|
+
function unevalChecksAnyOf(schema, ctx) {
|
|
3368
|
+
if (schema.unevaluatedProperties !== false) return false
|
|
3369
|
+
const r = collectEvaluated(schema, ctx.schemaMap, ctx.rootDefs)
|
|
3370
|
+
return r.dynamic && !r.allProps
|
|
3371
|
+
}
|
|
3372
|
+
|
|
3173
3373
|
function genUnevaluatedPropertiesNode(schema, v, lines, ctx, knownType, isObj, hoisted, earlyKeyCount) {
|
|
3174
3374
|
const evalResult = collectEvaluated(schema, ctx.schemaMap, ctx.rootDefs)
|
|
3175
3375
|
|
|
@@ -3448,6 +3648,16 @@ function genUnevaluatedPropertiesNode(schema, v, lines, ctx, knownType, isObj, h
|
|
|
3448
3648
|
const inner = `for(var _k in ${v}){if(${evVar}[_k])continue;${reVars.map(rv => `if(${rv}.test(_k)){${evVar}[_k]=1;continue}`).join('')}return false}`
|
|
3449
3649
|
_deferOrInline(ctx, lines, v, isObj ? inner + '}' : `if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){${inner}}}`)
|
|
3450
3650
|
}
|
|
3651
|
+
} else {
|
|
3652
|
+
// Nothing dynamic contributes a name (a lone if with no properties
|
|
3653
|
+
// or patterns, say): only the fixed names are evaluated. This branch
|
|
3654
|
+
// used to emit nothing, and nothing read as valid.
|
|
3655
|
+
const ei = ctx.varCounter++
|
|
3656
|
+
const evVar = `_ev${ei}`
|
|
3657
|
+
lines.push(`{const ${evVar}={}`)
|
|
3658
|
+
for (const k of baseProps) lines.push(`${evVar}[${JSON.stringify(k)}]=1`)
|
|
3659
|
+
const inner = `for(var _k in ${v}){if(!${evVar}[_k])return false}`
|
|
3660
|
+
_deferOrInline(ctx, lines, v, isObj ? inner + '}' : `if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){${inner}}}`)
|
|
3451
3661
|
}
|
|
3452
3662
|
}
|
|
3453
3663
|
} else if (typeof schema.unevaluatedProperties === 'object') {
|
|
@@ -4161,17 +4371,44 @@ function compileToJSCodegenWithErrors(schema, schemaMap, userFormats, sourceOpts
|
|
|
4161
4371
|
// function and were rebuilt on every call, which on a product schema with
|
|
4162
4372
|
// six URLs and one anyOf cost about 450 ns before a single check ran, more
|
|
4163
4373
|
// than every check and every error together. Here they run once, in a
|
|
4164
|
-
// factory, as the verdict function's preamble does
|
|
4165
|
-
//
|
|
4166
|
-
//
|
|
4167
|
-
//
|
|
4168
|
-
|
|
4169
|
-
|
|
4170
|
-
|
|
4374
|
+
// factory, as the verdict function's preamble does; with $defs or
|
|
4375
|
+
// recursion too, below. `_errSource` stays `body` either way, for the
|
|
4376
|
+
// emitters.
|
|
4377
|
+
// The factory builds the helpers once and returns the error function.
|
|
4378
|
+
// With $defs or recursion the helpers are the definitions' functions and
|
|
4379
|
+
// the cycle guard's state lives beside them; the guarded run resets that
|
|
4380
|
+
// state at its start, and a call made while one is running (a custom
|
|
4381
|
+
// keyword validating with the same validator) gets a function from a fresh
|
|
4382
|
+
// factory call instead of sharing it. `_errFactory` carries the same text
|
|
4383
|
+
// to the standalone emitter.
|
|
4384
|
+
let factoryBody
|
|
4385
|
+
if (needsGuardE) {
|
|
4386
|
+
const attempt = ctx.usesRecursion ? '_validateE(d,_all,_e)' : '_bodyE(d,_all,_e)'
|
|
4387
|
+
const runner = ctx.usesRecursion
|
|
4388
|
+
? emitRootGuard('_validateE', 'd,_all,_e', '') + `function _validateE_b(d,_all,_e){\n ${checkStr}\n }\n `
|
|
4389
|
+
: `function _bodyE(d,_all,_e){\n ${checkStr}\n }\n `
|
|
4390
|
+
factoryBody = guardStrE + helpersE + runner + `return function(d,_all){const _e=[];\n ` +
|
|
4391
|
+
emitGuardedRun(attempt, '_e.length=0;', defSetsE) + `return{valid:_e.length===0,errors:_e}}`
|
|
4392
|
+
} else {
|
|
4393
|
+
factoryBody = helpersE + 'return function(d,_all){const _e=[];\n ' + checkStr +
|
|
4171
4394
|
'\n return{valid:_e.length===0,errors:_e}}'
|
|
4172
|
-
|
|
4173
|
-
|
|
4174
|
-
|
|
4395
|
+
}
|
|
4396
|
+
const hoistable = !ctx.usesRecursion && defSetsE.length === 0 && helpersE !== ''
|
|
4397
|
+
if (hoistable || needsGuardE) {
|
|
4398
|
+
const params = [...cvars, '__ataSafeRe']
|
|
4399
|
+
const args = [...cvals, compileSafe]
|
|
4400
|
+
const make = new Function(...params, factoryBody)
|
|
4401
|
+
const main = make(...args)
|
|
4402
|
+
if (needsGuardE) {
|
|
4403
|
+
let busy = false
|
|
4404
|
+
fn = (d, _all) => {
|
|
4405
|
+
if (busy) return make(...args)(d, _all)
|
|
4406
|
+
busy = true
|
|
4407
|
+
try { return main(d, _all) } finally { busy = false }
|
|
4408
|
+
}
|
|
4409
|
+
} else {
|
|
4410
|
+
fn = main
|
|
4411
|
+
}
|
|
4175
4412
|
} else if (ctx.usesSafeRe) {
|
|
4176
4413
|
// Inlined helperCode references __ataSafeRe; bind the safe-regex factory.
|
|
4177
4414
|
// Standalone keeps the inline _errSource — the embed defines __ataSafeRe.
|
|
@@ -4186,6 +4423,8 @@ function compileToJSCodegenWithErrors(schema, schemaMap, userFormats, sourceOpts
|
|
|
4186
4423
|
fn = new Function('d', '_all', body)
|
|
4187
4424
|
}
|
|
4188
4425
|
fn._errSource = body
|
|
4426
|
+
fn._errFactory = factoryBody
|
|
4427
|
+
fn._errGuarded = needsGuardE
|
|
4189
4428
|
fn._sharedHelpers = ctx.shared ? ctx.shared.slice() : []
|
|
4190
4429
|
fn._usesSafeRe = !!ctx.usesSafeRe
|
|
4191
4430
|
if (cvars.length) {
|
|
@@ -4216,32 +4455,65 @@ function emitBranchCollapse (branches, keyword, v, pathExpr, lines, ctx, schemaP
|
|
|
4216
4455
|
const branchKw = keyword
|
|
4217
4456
|
const branchSp = schemaPrefix + '/' + branchKw
|
|
4218
4457
|
// Per-branch error-collecting functions, each returning {valid,errors}.
|
|
4458
|
+
// They take the value, its instance path, `_all` and, inside a definition
|
|
4459
|
+
// written once for all its references, that reference's schema path, so
|
|
4460
|
+
// they can be built once per call of the error function rather than each
|
|
4461
|
+
// time the keyword is reached: on a list of 1723 tokens that is thousands
|
|
4462
|
+
// of closure arrays per validation.
|
|
4219
4463
|
const fns = []
|
|
4220
4464
|
const titles = []
|
|
4465
|
+
let usesSp = false
|
|
4221
4466
|
for (let i = 0; i < branches.length; i++) {
|
|
4222
4467
|
const sub = branches[i]
|
|
4223
4468
|
const subLines = []
|
|
4224
4469
|
const subSp = branchSp + '/' + i
|
|
4225
4470
|
if (typeof sub === 'object' && sub !== null) {
|
|
4226
|
-
genCodeE(sub, '_bv',
|
|
4471
|
+
genCodeE(sub, '_bv', '_bpth', subLines, ctx, subSp)
|
|
4227
4472
|
} else if (sub === false) {
|
|
4228
4473
|
// A false branch never matches; it reports the same error the
|
|
4229
4474
|
// interpreter attaches to a false schema.
|
|
4230
|
-
subLines.push(`_e.push({keyword:'not',instancePath
|
|
4475
|
+
subLines.push(`_e.push({keyword:'not',instancePath:_bpth,schemaPath:'${subSp}'${ordinalField(ctx, `${subSp}`)},params:{},message:'boolean schema is false'})`)
|
|
4231
4476
|
}
|
|
4232
4477
|
const title = (sub && typeof sub === 'object' && typeof sub.title === 'string') ? sub.title : ''
|
|
4233
4478
|
titles.push(title)
|
|
4234
|
-
|
|
4235
|
-
|
|
4236
|
-
|
|
4479
|
+
let body = subLines.length === 0 ? '' : `const _e=[];${subLines.join(';')};return{valid:_e.length===0,errors:_e}`
|
|
4480
|
+
if (body.includes(SP_PLACEHOLDER)) {
|
|
4481
|
+
body = body.split(`'${SP_PLACEHOLDER}`).join(`_sp+'`).split(`"${SP_PLACEHOLDER}`).join(`_sp+"`)
|
|
4482
|
+
if (body.includes(SP_PLACEHOLDER)) throw DECLINE
|
|
4483
|
+
usesSp = true
|
|
4484
|
+
}
|
|
4237
4485
|
fns.push(body)
|
|
4238
4486
|
}
|
|
4487
|
+
const params = usesSp ? '_bv,_bpth,_all,_sp' : '_bv,_bpth,_all'
|
|
4239
4488
|
const fnArr = `_brf${fi}`
|
|
4240
4489
|
const resArr = `_brr${fi}`
|
|
4241
4490
|
const titleArr = JSON.stringify(titles)
|
|
4242
4491
|
const collapsed = `_brc${fi}`
|
|
4243
4492
|
const pp = pathExpr || '""'
|
|
4244
|
-
|
|
4493
|
+
ctx.helperCode.push(`const ${fnArr}=[${fns.map((b) => b === '' ? `function(){return{valid:true,errors:[]}}` : `function(${params}){${b}}`).join(',')}];const _bt${fi}=${titleArr}`)
|
|
4494
|
+
const call = `${fnArr}[_bi](${v},_pp${fi},_all${usesSp ? ',_sp' : ''})`
|
|
4495
|
+
const ord = (() => { const o = ordinalFor(ctx.rootSchema, branchSp); return o === null ? 'null' : o })()
|
|
4496
|
+
const report = `const ${collapsed}=__ataCollapse('${branchKw}',${resArr},_pp${fi},'${branchSp}',${ord});if(${collapsed}){_e.push(${collapsed});if(!_all)return{valid:false,errors:_e}}`
|
|
4497
|
+
if (keyword === 'anyOf') {
|
|
4498
|
+
// anyOf reports nothing once a branch passes, so the branches are first
|
|
4499
|
+
// only asked whether they pass. They are called with `_all` set to 0, a
|
|
4500
|
+
// third value next to true (collect everything) and false (stop at the
|
|
4501
|
+
// first error, for abortEarly): every check reads it as `!_all` and stops
|
|
4502
|
+
// at its first error, and an anyOf reached that way that fails returns
|
|
4503
|
+
// at once instead of collecting its branches and collapsing them, since
|
|
4504
|
+
// the caller only wants to know. An object tried against a branch that is
|
|
4505
|
+
// an anyOf of primitive types used to collapse that anyOf in full and then
|
|
4506
|
+
// pass the next branch, which on a list of 1723 tokens was 2353 collapses
|
|
4507
|
+
// no one read. Only when no branch passes do they run again with the
|
|
4508
|
+
// caller's `_all`, and the collapse sees them all, as before.
|
|
4509
|
+
const probe = `${fnArr}[_bi](${v},_pp${fi},0${usesSp ? ',_sp' : ''})`
|
|
4510
|
+
lines.push(`{const _pp${fi}=${pp};let _bp${fi}=false;for(let _bi=0;_bi<${fnArr}.length;_bi++){if(${probe}.valid){_bp${fi}=true;break}}if(!_bp${fi}){if(_all===0){_e.push(null);return{valid:false,errors:_e}}const ${resArr}=[];for(let _bi=0;_bi<${fnArr}.length;_bi++){const _br=${call};${resArr}.push({valid:false,errors:_br.errors,title:_bt${fi}[_bi]})}${report}}}`)
|
|
4511
|
+
} else {
|
|
4512
|
+
// Asked only whether it passes (`_all` 0, see anyOf above), oneOf counts
|
|
4513
|
+
// the passing branches and answers; the branch errors of a probe are not
|
|
4514
|
+
// errors anyone reads and are not collapsed.
|
|
4515
|
+
lines.push(`{const _pp${fi}=${pp};const ${resArr}=[];for(let _bi=0;_bi<${fnArr}.length;_bi++){const _br=${call};${resArr}.push({valid:_br.valid,errors:_br.errors,title:_bt${fi}[_bi]})}if(_all===0){let _n=0;for(const _r of ${resArr})if(_r.valid)_n++;if(_n!==1){_e.push(null);return{valid:false,errors:_e}}}else{${report}}}`)
|
|
4516
|
+
}
|
|
4245
4517
|
}
|
|
4246
4518
|
|
|
4247
4519
|
// Error-collecting code generator.
|
|
@@ -4277,9 +4549,14 @@ function genCodeENode(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
|
4277
4549
|
if (m && ctx.rootDefs && ctx.rootDefs[m[1]]) {
|
|
4278
4550
|
const defName = m[1]
|
|
4279
4551
|
if (!ctx.cyclicDefs) ctx.cyclicDefs = cyclicDefNames(ctx.rootDefs)
|
|
4280
|
-
if (ctx.
|
|
4281
|
-
|
|
4282
|
-
//
|
|
4552
|
+
if (!ctx.sharedDefs) ctx.sharedDefs = sharedDefNames(ctx.rootSchema, ctx.rootDefs)
|
|
4553
|
+
if (ctx.cyclicDefs.has(defName) || ctx.sharedDefs.has(defName)) {
|
|
4554
|
+
// Named helper for a def on a cycle, and for one referenced from
|
|
4555
|
+
// several places, which inlined would repeat its whole error code at
|
|
4556
|
+
// each: Uniswap's token-list schema has 16 references to 10
|
|
4557
|
+
// definitions. The path comes in as an argument because every call
|
|
4558
|
+
// site has its own. Only the error path runs these; the verdict
|
|
4559
|
+
// function, which answers valid documents, is unchanged.
|
|
4283
4560
|
if (!ctx.defFns) ctx.defFns = new Map()
|
|
4284
4561
|
let fnName = ctx.defFns.get(defName)
|
|
4285
4562
|
if (!fnName) {
|
|
@@ -4459,7 +4736,7 @@ function genCodeENode(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
|
4459
4736
|
if (!ctx.regExpMap.has(pattern)) {
|
|
4460
4737
|
const ri = ctx.varCounter++
|
|
4461
4738
|
ctx.regExpMap.set(pattern, ri)
|
|
4462
|
-
if (
|
|
4739
|
+
if (useSafeEngine(schema.pattern)) {
|
|
4463
4740
|
ctx.helperCode.push(`const _re${ri}=__ataSafeRe(${pattern})`)
|
|
4464
4741
|
ctx.usesSafeRe = true
|
|
4465
4742
|
} else {
|
|
@@ -4534,7 +4811,7 @@ function genCodeENode(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
|
4534
4811
|
if (!ctx.regExpMap.has(pattern)) {
|
|
4535
4812
|
const ri = ctx.varCounter++
|
|
4536
4813
|
ctx.regExpMap.set(pattern, ri)
|
|
4537
|
-
if (
|
|
4814
|
+
if (useSafeEngine(pat)) {
|
|
4538
4815
|
ctx.helperCode.push(`const _re${ri}=__ataSafeRe(${pattern})`)
|
|
4539
4816
|
ctx.usesSafeRe = true
|
|
4540
4817
|
} else {
|
|
@@ -4551,7 +4828,10 @@ function genCodeENode(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
|
4551
4828
|
const gateChecks = patChecks.map((c) => c.split(`_k${ci}[_i]`).join(`_f${ci}`))
|
|
4552
4829
|
const gateTest = gateChecks.length ? `!_a${ci}.has(_f${ci})&&!(${gateChecks.join('||')})` : `!_a${ci}.has(_f${ci})`
|
|
4553
4830
|
const detail = `const _k${ci}=Object.keys(${v});for(let _i=0;_i<_k${ci}.length;_i++){if(${isAdditional}){_e.push({code:'${apLit.codeStr}',keyword:'additionalProperties',instancePath:${pathExpr||'""'},schemaPath:'${apSp}'${ordinalField(ctx, `${apSp}`)},params:{additionalProperty:_k${ci}[_i]},message:'must NOT have additional properties',docUrl:'${apLit.docUrl}'${apLit.frame}});if(!_all)return{valid:false,errors:_e}}}`
|
|
4554
|
-
|
|
4831
|
+
// The name set is built once per call of the error function, not at every
|
|
4832
|
+
// object it checks: on a list of 1723 tokens it was 1723 Sets.
|
|
4833
|
+
ctx.helperCode.push(`const _a${ci}=new Set([${allowed}])`)
|
|
4834
|
+
const inner = `let _x${ci}=0;for(const _f${ci} in ${v}){if(${gateTest}){_x${ci}=1;break}}if(_x${ci}){${detail}}`
|
|
4555
4835
|
lines.push(isObj ? `{${inner}}` : `if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){${inner}}`)
|
|
4556
4836
|
}
|
|
4557
4837
|
|
|
@@ -4697,7 +4977,7 @@ function genUnevaluatedFalseE(schema, v, pathExpr, lines, ctx, schemaPrefix, isO
|
|
|
4697
4977
|
if (!ctx.regExpMap.has(pattern)) {
|
|
4698
4978
|
const ri = ctx.varCounter++
|
|
4699
4979
|
ctx.regExpMap.set(pattern, ri)
|
|
4700
|
-
if (
|
|
4980
|
+
if (useSafeEngine(pat)) {
|
|
4701
4981
|
ctx.helperCode.push(`const _re${ri}=__ataSafeRe(${pattern})`)
|
|
4702
4982
|
ctx.usesSafeRe = true
|
|
4703
4983
|
} else {
|
|
@@ -4707,7 +4987,8 @@ function genUnevaluatedFalseE(schema, v, pathExpr, lines, ctx, schemaPrefix, isO
|
|
|
4707
4987
|
patChecksU.push(`_re${ctx.regExpMap.get(pattern)}.test(_k${ui}[_i])`)
|
|
4708
4988
|
}
|
|
4709
4989
|
const isUneval = patChecksU.length ? `!_a${ui}.has(_k${ui}[_i])&&!(${patChecksU.join('||')})` : `!_a${ui}.has(_k${ui}[_i])`
|
|
4710
|
-
|
|
4990
|
+
ctx.helperCode.push(`const _a${ui}=new Set([${allowedU}])`)
|
|
4991
|
+
const innerU = `const _k${ui}=Object.keys(${v});for(let _i=0;_i<_k${ui}.length;_i++){if(${isUneval}){_e.push({code:'${upLit.codeStr}',keyword:'unevaluatedProperties',instancePath:${pathExpr||'""'},schemaPath:'${upSp}'${ordinalField(ctx, `${upSp}`)},params:{unevaluatedProperty:_k${ui}[_i]},message:'must NOT have unevaluated properties',docUrl:'${upLit.docUrl}'${upLit.frame}});if(!_all)return{valid:false,errors:_e}}}`
|
|
4711
4992
|
lines.push(isObj ? `{${innerU}}` : `if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){${innerU}}`)
|
|
4712
4993
|
}
|
|
4713
4994
|
|
|
@@ -4728,7 +5009,7 @@ function genPatternPropertiesE(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
|
4728
5009
|
if (!ctx.regExpMap.has(pattern)) {
|
|
4729
5010
|
const ri = ctx.varCounter++
|
|
4730
5011
|
ctx.regExpMap.set(pattern, ri)
|
|
4731
|
-
if (
|
|
5012
|
+
if (useSafeEngine(pat)) {
|
|
4732
5013
|
ctx.helperCode.push(`const _re${ri}=__ataSafeRe(${pattern})`);
|
|
4733
5014
|
ctx.usesSafeRe = true
|
|
4734
5015
|
} else {
|
|
@@ -4749,7 +5030,8 @@ function genPatternPropertiesE(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
|
4749
5030
|
function genAdditionalSchemaE(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
4750
5031
|
const ki = ctx.varCounter++
|
|
4751
5032
|
const known = Object.keys(schema.properties || {})
|
|
4752
|
-
|
|
5033
|
+
if (known.length) ctx.helperCode.push(`const _ak${ki}=new Set([${known.map((k) => JSON.stringify(k)).join(',')}])`)
|
|
5034
|
+
const guard = ''
|
|
4753
5035
|
// A key matched by any patternProperties entry is not additional.
|
|
4754
5036
|
const patTests = []
|
|
4755
5037
|
for (const pat of Object.keys(schema.patternProperties || {})) {
|
|
@@ -4757,7 +5039,7 @@ function genAdditionalSchemaE(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
|
4757
5039
|
if (!ctx.regExpMap.has(pattern)) {
|
|
4758
5040
|
const ri = ctx.varCounter++
|
|
4759
5041
|
ctx.regExpMap.set(pattern, ri)
|
|
4760
|
-
if (
|
|
5042
|
+
if (useSafeEngine(pat)) {
|
|
4761
5043
|
ctx.helperCode.push(`const _re${ri}=__ataSafeRe(${pattern})`)
|
|
4762
5044
|
ctx.usesSafeRe = true
|
|
4763
5045
|
} else {
|
|
@@ -4809,7 +5091,7 @@ function genPropertyNamesE(schema, v, pathExpr, lines, ctx, schemaPrefix, fail)
|
|
|
4809
5091
|
if (!ctx.regExpMap.has(pattern)) {
|
|
4810
5092
|
const ri = ctx.varCounter++
|
|
4811
5093
|
ctx.regExpMap.set(pattern, ri)
|
|
4812
|
-
if (
|
|
5094
|
+
if (useSafeEngine(pn.pattern)) {
|
|
4813
5095
|
ctx.helperCode.push(`const _re${ri}=__ataSafeRe(${pattern})`);
|
|
4814
5096
|
ctx.usesSafeRe = true
|
|
4815
5097
|
} else {
|
|
@@ -5122,6 +5404,27 @@ function genCodeCNode(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
|
5122
5404
|
return `(_e||(_e=[])).push({keyword:'${keyword}',instancePath:${pathExpr||'""'},schemaPath:'${sp}',params:${paramsCode},message:${msgCode}${ordinalField(ctx, sp)}})`
|
|
5123
5405
|
}
|
|
5124
5406
|
|
|
5407
|
+
// enum and const apply to a value of any type, so a value that fails
|
|
5408
|
+
// `type` is still checked against them, as the other engines do. They sat
|
|
5409
|
+
// inside the type-success block, and `{type: 'number', enum: [1]}` reported
|
|
5410
|
+
// only `type` for 'x' where the interpreter reports both.
|
|
5411
|
+
const emitEnumConst = () => {
|
|
5412
|
+
// enum
|
|
5413
|
+
if (schema.enum) {
|
|
5414
|
+
lines.push(`if(!(${enumCondition(ctx, schema.enum, v)})){${fail('enum', 'enum', `{allowedValues:${JSON.stringify(schema.enum)}}`, "'must be equal to one of the allowed values'")}}`)
|
|
5415
|
+
}
|
|
5416
|
+
|
|
5417
|
+
// const
|
|
5418
|
+
if (schema.const !== undefined) {
|
|
5419
|
+
const cv = schema.const
|
|
5420
|
+
if (cv === null || typeof cv !== 'object') {
|
|
5421
|
+
lines.push(`if(${v}!==${JSON.stringify(cv)}){${fail('const', 'const', `{allowedValue:${JSON.stringify(schema.const)}}`, "'must be equal to constant'")}}`)
|
|
5422
|
+
} else {
|
|
5423
|
+
lines.push(`if(!${emitDeq(ctx)}(${v},${emitConstant(ctx, cv)})){${fail('const', 'const', `{allowedValue:JSON.parse(${JSON.stringify(JSON.stringify(schema.const))})}`, "'must be equal to constant'")}}`)
|
|
5424
|
+
}
|
|
5425
|
+
}
|
|
5426
|
+
}
|
|
5427
|
+
|
|
5125
5428
|
if (types) {
|
|
5126
5429
|
const conds = types.map(t => {
|
|
5127
5430
|
switch (t) {
|
|
@@ -5148,22 +5451,10 @@ function genCodeCNode(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
|
5148
5451
|
isStr = types[0] === 'string'
|
|
5149
5452
|
isNum = types[0] === 'number' || types[0] === 'integer'
|
|
5150
5453
|
}
|
|
5454
|
+
emitEnumConst()
|
|
5151
5455
|
lines.push(`if(${typeOk}){`)
|
|
5152
|
-
}
|
|
5153
|
-
|
|
5154
|
-
// enum
|
|
5155
|
-
if (schema.enum) {
|
|
5156
|
-
lines.push(`if(!(${enumCondition(ctx, schema.enum, v)})){${fail('enum', 'enum', `{allowedValues:${JSON.stringify(schema.enum)}}`, "'must be equal to one of the allowed values'")}}`)
|
|
5157
|
-
}
|
|
5158
|
-
|
|
5159
|
-
// const
|
|
5160
|
-
if (schema.const !== undefined) {
|
|
5161
|
-
const cv = schema.const
|
|
5162
|
-
if (cv === null || typeof cv !== 'object') {
|
|
5163
|
-
lines.push(`if(${v}!==${JSON.stringify(cv)}){${fail('const', 'const', `{allowedValue:${JSON.stringify(schema.const)}}`, "'must be equal to constant'")}}`)
|
|
5164
|
-
} else {
|
|
5165
|
-
lines.push(`if(!${emitDeq(ctx)}(${v},${emitConstant(ctx, cv)})){${fail('const', 'const', `{allowedValue:JSON.parse(${JSON.stringify(JSON.stringify(schema.const))})}`, "'must be equal to constant'")}}`)
|
|
5166
|
-
}
|
|
5456
|
+
} else {
|
|
5457
|
+
emitEnumConst()
|
|
5167
5458
|
}
|
|
5168
5459
|
|
|
5169
5460
|
// required — use destructuring when type is object (SAFE because type check already passed)
|
|
@@ -5248,7 +5539,7 @@ function genCodeCNode(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
|
5248
5539
|
const ri = ctx.varCounter++
|
|
5249
5540
|
const reVar = `_re${ri}`
|
|
5250
5541
|
ctx.closureVars.push(reVar)
|
|
5251
|
-
ctx.closureVals.push(
|
|
5542
|
+
ctx.closureVals.push(useSafeEngine(schema.pattern) ? compileSafe(schema.pattern) : new RegExp(schema.pattern))
|
|
5252
5543
|
const c = isStr ? `!${reVar}.test(${v})` : `typeof ${v}==='string'&&!${reVar}.test(${v})`
|
|
5253
5544
|
lines.push(`if(${c}){${fail('pattern', 'pattern', `{pattern:${JSON.stringify(schema.pattern)}}`, JSON.stringify(`must match pattern "${schema.pattern}"`))}}`)
|
|
5254
5545
|
}
|