ata-validator 1.31.0 → 1.31.2
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/compat.js +2 -2
- package/index.js +16 -3
- package/lib/compat-errors.js +12 -2
- package/lib/js-compiler.js +193 -70
- package/lib/shape-classifier.js +9 -0
- package/lib/tier0.js +11 -1
- package/lib/version.js +1 -1
- package/package.json +9 -9
package/compat.js
CHANGED
|
@@ -22,7 +22,7 @@ const { resolvePointer } = require('./lib/pointer.js');
|
|
|
22
22
|
|
|
23
23
|
const { Validator } = require('./index');
|
|
24
24
|
const { METASCHEMAS } = require('./lib/metaschemas');
|
|
25
|
-
const { createShaper } = require('./lib/compat-errors');
|
|
25
|
+
const { createShaper, asReferenceKeyword } = require('./lib/compat-errors');
|
|
26
26
|
|
|
27
27
|
const DRAFT7 = 'http://json-schema.org/draft-07/schema#';
|
|
28
28
|
const DRAFT2020 = 'https://json-schema.org/draft/2020-12/schema';
|
|
@@ -185,7 +185,7 @@ class Ata {
|
|
|
185
185
|
validate.errors = null;
|
|
186
186
|
return true;
|
|
187
187
|
}
|
|
188
|
-
let errors = shaper === null ? result.errors : shaper(result.errors, data);
|
|
188
|
+
let errors = shaper === null ? result.errors.map(asReferenceKeyword) : shaper(result.errors, data);
|
|
189
189
|
// Errors the shaper synthesises, the `if` wrapper among them, are built
|
|
190
190
|
// from the group rather than carried up from the validator, so verbose
|
|
191
191
|
// mode has to fill in the value they point at here or they would be the
|
package/index.js
CHANGED
|
@@ -1406,7 +1406,7 @@ class Validator {
|
|
|
1406
1406
|
return this._compiled.validate(x);
|
|
1407
1407
|
});
|
|
1408
1408
|
}
|
|
1409
|
-
return _errOnlyImpl(d);
|
|
1409
|
+
return _mustReject(_errOnlyImpl(d));
|
|
1410
1410
|
};
|
|
1411
1411
|
|
|
1412
1412
|
// Best path: combined validator (single pass, validates + collects errors)
|
|
@@ -1450,7 +1450,7 @@ class Validator {
|
|
|
1450
1450
|
let _errPreferredImpl = null;
|
|
1451
1451
|
const errPreferCombined = (d) => {
|
|
1452
1452
|
if (_errPreferredImpl === null) _errPreferredImpl = combinedIfSafe() || errOnly;
|
|
1453
|
-
return _errPreferredImpl(d);
|
|
1453
|
+
return _mustReject(_errPreferredImpl(d));
|
|
1454
1454
|
};
|
|
1455
1455
|
|
|
1456
1456
|
// The boolean engine is the verdict authority for these paths; the
|
|
@@ -1513,7 +1513,7 @@ class Validator {
|
|
|
1513
1513
|
let impl = null;
|
|
1514
1514
|
const onReject = (data) => {
|
|
1515
1515
|
const combined = combinedIfSafe();
|
|
1516
|
-
if (combined) { impl = combined; return combined(data); }
|
|
1516
|
+
if (combined) { impl = combined; return _mustReject(combined(data)); }
|
|
1517
1517
|
return errOnly(data);
|
|
1518
1518
|
};
|
|
1519
1519
|
const hybridFn = jsFn._hybridFactory(VALID_RESULT, onReject);
|
|
@@ -2666,6 +2666,19 @@ Object.defineProperty(Validator.prototype, "~standard", {
|
|
|
2666
2666
|
},
|
|
2667
2667
|
});
|
|
2668
2668
|
|
|
2669
|
+
// The error resolvers run only after a verdict function has said no. If one
|
|
2670
|
+
// answers valid anyway, two generators disagree, and the verdict is the one
|
|
2671
|
+
// to keep: returning the resolver's answer is how a vacuous combined function
|
|
2672
|
+
// turned a rejection into an acceptance in validateJSON. The disagreement is
|
|
2673
|
+
// reported as a generic failure rather than hidden.
|
|
2674
|
+
const _VERDICT_DISAGREES = Object.freeze({
|
|
2675
|
+
valid: false,
|
|
2676
|
+
errors: Object.freeze([Object.freeze({ keyword: 'validation', instancePath: '', schemaPath: '#', params: Object.freeze({}), message: 'schema validation failed' })]),
|
|
2677
|
+
});
|
|
2678
|
+
function _mustReject(r) {
|
|
2679
|
+
return r && r.valid === false ? r : _VERDICT_DISAGREES;
|
|
2680
|
+
}
|
|
2681
|
+
|
|
2669
2682
|
// Install the verdict method. Every place that binds isValidObject comes
|
|
2670
2683
|
// through here, so a check registered with _extendVerdict survives the method
|
|
2671
2684
|
// being replaced as the validator compiles further, which it does more than
|
package/lib/compat-errors.js
CHANGED
|
@@ -133,6 +133,16 @@ function escapePointer(s) {
|
|
|
133
133
|
// validate() already reports them so.
|
|
134
134
|
const WRAPPERS = new Set(['anyOf', 'oneOf', 'propertyNames', 'contains', 'not', 'if']);
|
|
135
135
|
|
|
136
|
+
// ata names the keyword of a false schema `not`, at the root and nested alike,
|
|
137
|
+
// which is what its own error codes and every consumer switch on. The
|
|
138
|
+
// reference names it `false schema`. The root case used to come out of the
|
|
139
|
+
// code generator as `false schema` by an inconsistency that has since been
|
|
140
|
+
// fixed, so this shim matched the reference there and not for a nested false
|
|
141
|
+
// schema; the rename here makes both match.
|
|
142
|
+
function asReferenceKeyword(e) {
|
|
143
|
+
return e.keyword === 'not' && e.message === 'boolean schema is false' ? { ...e, keyword: 'false schema' } : e;
|
|
144
|
+
}
|
|
145
|
+
|
|
136
146
|
function createShaper({ Validator, rootId, rootDoc, schemas, options, allErrors, macroKeywords }) {
|
|
137
147
|
const branchCache = new Map();
|
|
138
148
|
const branchValidator = (pointer) => {
|
|
@@ -360,11 +370,11 @@ function createShaper({ Validator, rootId, rootDoc, schemas, options, allErrors,
|
|
|
360
370
|
}
|
|
361
371
|
|
|
362
372
|
return function shapeErrors(errors, data) {
|
|
363
|
-
const shaped = shape(errors, data, '');
|
|
373
|
+
const shaped = shape(errors, data, '').map(asReferenceKeyword);
|
|
364
374
|
if (allErrors) return shaped;
|
|
365
375
|
const n = groupEnd(shaped);
|
|
366
376
|
return shaped.slice(0, n);
|
|
367
377
|
};
|
|
368
378
|
}
|
|
369
379
|
|
|
370
|
-
module.exports = { createShaper, sortLikeReference, REFERENCE_ORDER };
|
|
380
|
+
module.exports = { createShaper, sortLikeReference, REFERENCE_ORDER, asReferenceKeyword };
|
package/lib/js-compiler.js
CHANGED
|
@@ -157,10 +157,31 @@ const DOC_BASE = 'https://ata-validator.com/e/'
|
|
|
157
157
|
// reader sorts by without deriving it from the path. Empty when the path is
|
|
158
158
|
// not into the root document.
|
|
159
159
|
function ordinalField (ctx, schemaPath) {
|
|
160
|
-
const o = ctx && ctx.rootSchema ? ordinalFor(ctx.rootSchema, schemaPath) : null
|
|
160
|
+
const o = ctx && ctx.rootSchema ? ordinalFor(ctx.rootSchema, unescapeSp(schemaPath)) : null
|
|
161
161
|
return o === null ? '' : `,_o:${o}`
|
|
162
162
|
}
|
|
163
163
|
|
|
164
|
+
// A schema path as the generators hold it is text for a single-quoted source
|
|
165
|
+
// literal: ptrSeg put a backslash before every backslash and quote in a key.
|
|
166
|
+
// Anything that uses the path as a value, the ordinal lookup and the frozen
|
|
167
|
+
// error objects below, has to take that backslash off again, or a key such as
|
|
168
|
+
// `it's` reads as `it\'s` and the error names a path that does not exist.
|
|
169
|
+
// The value of a string literal the generators built for source. One that does
|
|
170
|
+
// not parse on its own (a key with a raw line break) would not parse inside the
|
|
171
|
+
// generated function either, so the generator declines, as it would have when
|
|
172
|
+
// that function failed to build.
|
|
173
|
+
function literalValue (lit) {
|
|
174
|
+
try {
|
|
175
|
+
return Function('return ' + lit)()
|
|
176
|
+
} catch {
|
|
177
|
+
throw DECLINE
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function unescapeSp (sp) {
|
|
182
|
+
return sp.indexOf('\\') === -1 ? sp : sp.replace(/\\(.)/g, '$1')
|
|
183
|
+
}
|
|
184
|
+
|
|
164
185
|
function buildErrorLiteral (opts) {
|
|
165
186
|
const { keyword, format, schemaPath, sourceMap } = opts
|
|
166
187
|
let code = (keyword === 'format' && format) ? codeFor('format', format) : codeFor(keyword)
|
|
@@ -340,7 +361,7 @@ function compileToJS(schema, defs, schemaMap) {
|
|
|
340
361
|
// required applies to objects only; other types are ignored.
|
|
341
362
|
if (schema.required && Array.isArray(schema.required)) {
|
|
342
363
|
for (const key of schema.required) {
|
|
343
|
-
checks.push((d) => typeof d !== 'object' || d === null || Array.isArray(d) || d
|
|
364
|
+
checks.push((d) => typeof d !== 'object' || d === null || Array.isArray(d) || hasOwnKey(d, key))
|
|
344
365
|
}
|
|
345
366
|
}
|
|
346
367
|
|
|
@@ -350,7 +371,7 @@ function compileToJS(schema, defs, schemaMap) {
|
|
|
350
371
|
const propCheck = compileToJS(prop, rootDefs)
|
|
351
372
|
if (!propCheck) return null // bail if sub-schema too complex
|
|
352
373
|
checks.push((d) => {
|
|
353
|
-
if (typeof d !== 'object' || d === null || !(key
|
|
374
|
+
if (typeof d !== 'object' || d === null || !hasOwnKey(d, key)) return true
|
|
354
375
|
return propCheck(d[key])
|
|
355
376
|
})
|
|
356
377
|
}
|
|
@@ -387,9 +408,9 @@ function compileToJS(schema, defs, schemaMap) {
|
|
|
387
408
|
if (schema.dependentRequired) {
|
|
388
409
|
for (const [key, deps] of Object.entries(schema.dependentRequired)) {
|
|
389
410
|
checks.push((d) => {
|
|
390
|
-
if (typeof d !== 'object' || d === null || !(key
|
|
411
|
+
if (typeof d !== 'object' || d === null || !hasOwnKey(d, key)) return true
|
|
391
412
|
for (let i = 0; i < deps.length; i++) {
|
|
392
|
-
if (!(deps[i]
|
|
413
|
+
if (!hasOwnKey(d, deps[i])) return false
|
|
393
414
|
}
|
|
394
415
|
return true
|
|
395
416
|
})
|
|
@@ -872,6 +893,44 @@ const FORMAT_CHECKS = {
|
|
|
872
893
|
}
|
|
873
894
|
|
|
874
895
|
// Dangerous JS property names that exist on Object.prototype
|
|
896
|
+
// Presence is an own-property question: `required: ['toString']` is not
|
|
897
|
+
// satisfied by `{}`. `'k' in v` and `v.k !== undefined` both answer yes for a
|
|
898
|
+
// name found on the prototype chain, which accepted documents the interpreted
|
|
899
|
+
// engine rejects. Object.hasOwn would be exact but is not inlined, about 5 ns
|
|
900
|
+
// a key against well under 1 for `in`, so the check is split:
|
|
901
|
+
// - a name on Object.prototype (and `__proto__`) is always checked with
|
|
902
|
+
// hasOwnProperty; these are rare in schemas and known when compiling;
|
|
903
|
+
// - any other name can only be inherited from a prototype that is not
|
|
904
|
+
// Object.prototype, so an ordinary object answers with `in` and one
|
|
905
|
+
// comparison of its prototype, and anything else, a class instance, a
|
|
906
|
+
// null-prototype object, one from another realm, pays for hasOwnProperty.
|
|
907
|
+
// `v.__proto__` is read rather than Object.getPrototypeOf(v) because the engine
|
|
908
|
+
// inlines the accessor and not the call (0.8 ns against 8 for eleven keys). An
|
|
909
|
+
// object whose own `__proto__` is a data property reads that value, which is
|
|
910
|
+
// not Object.prototype, so it takes the exact path.
|
|
911
|
+
const PROTO_NAMES = new Set([...Object.getOwnPropertyNames(Object.prototype), '__proto__'])
|
|
912
|
+
// Declared once per compiled function and called by name: spelled out at each
|
|
913
|
+
// property, the test grew a 50-property module by a tenth. Both are small
|
|
914
|
+
// enough for the engine to inline.
|
|
915
|
+
const OWN_HELPERS = 'const _hop=Object.prototype.hasOwnProperty;' +
|
|
916
|
+
'function _own(o,k){return o.__proto__===Object.prototype?k in o:_hop.call(o,k)}'
|
|
917
|
+
function ownKeyExpr (ctx, v, key) {
|
|
918
|
+
hoistOnce(ctx, '_ownHoisted', OWN_HELPERS)
|
|
919
|
+
const k = JSON.stringify(key)
|
|
920
|
+
return PROTO_NAMES.has(key) ? `_hop.call(${v},${k})` : `_own(${v},${k})`
|
|
921
|
+
}
|
|
922
|
+
// For a value already read as `v[key]` and found not to be undefined: whether
|
|
923
|
+
// it came from `v` itself. Same test; `in` is already known to hold.
|
|
924
|
+
function ownGuard (ctx, v, key) {
|
|
925
|
+
return ownKeyExpr(ctx, v, key)
|
|
926
|
+
}
|
|
927
|
+
// The same test at run time, for the closure compiler.
|
|
928
|
+
const _hop = Object.prototype.hasOwnProperty
|
|
929
|
+
function hasOwnKey (d, key) {
|
|
930
|
+
if (PROTO_NAMES.has(key)) return _hop.call(d, key)
|
|
931
|
+
return key in d && (d.__proto__ === Object.prototype || _hop.call(d, key))
|
|
932
|
+
}
|
|
933
|
+
|
|
875
934
|
const UNSAFE_KEYS = new Set(['__proto__', 'constructor', 'toString', 'valueOf',
|
|
876
935
|
'hasOwnProperty', 'isPrototypeOf', 'propertyIsEnumerable', 'toLocaleString'])
|
|
877
936
|
|
|
@@ -968,6 +1027,23 @@ function hasNestedIdScope(node, isRoot = true) {
|
|
|
968
1027
|
// only safe way out.
|
|
969
1028
|
const DECLINE = Symbol('ata.decline')
|
|
970
1029
|
|
|
1030
|
+
// The error and combined generators compile the subschemas of not, if, contains
|
|
1031
|
+
// and their anyOf/oneOf closures with the boolean generator. That generator
|
|
1032
|
+
// follows a reference back to the root with `_validate` and a reference cycle
|
|
1033
|
+
// with a named function in its preamble, and neither exists inside the other
|
|
1034
|
+
// two generators' functions: the first threw a ReferenceError on the first
|
|
1035
|
+
// document that reached it, and the second cut the cycle, so the reference
|
|
1036
|
+
// checked nothing. While `nestedBoolean` is set, genCode declines at those
|
|
1037
|
+
// points instead, and the caller's generator declines the schema.
|
|
1038
|
+
function nestedGenCode(schema, v, lines, ctx) {
|
|
1039
|
+
ctx.nestedBoolean = (ctx.nestedBoolean || 0) + 1
|
|
1040
|
+
try {
|
|
1041
|
+
genCode(schema, v, lines, ctx)
|
|
1042
|
+
} finally {
|
|
1043
|
+
ctx.nestedBoolean--
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
|
|
971
1047
|
const CYCLE_DEPTH = 512
|
|
972
1048
|
|
|
973
1049
|
// Declarations the guarded functions close over. Emitted once, ahead of them.
|
|
@@ -1878,6 +1954,7 @@ function genCode(schema, v, lines, ctx, knownType) {
|
|
|
1878
1954
|
if (schema.$ref) {
|
|
1879
1955
|
// Self-reference "#" — recursive call to root validator
|
|
1880
1956
|
if (schema.$ref === '#') {
|
|
1957
|
+
if (ctx.nestedBoolean) throw DECLINE
|
|
1881
1958
|
ctx.usesRecursion = true
|
|
1882
1959
|
lines.push(`if(!_validate(${v}))return false`)
|
|
1883
1960
|
if (!hasSiblings) return
|
|
@@ -1920,7 +1997,7 @@ function genCode(schema, v, lines, ctx, knownType) {
|
|
|
1920
1997
|
}
|
|
1921
1998
|
lines.push(`if(!${fnName}(${v}))return false`)
|
|
1922
1999
|
if (!hasSiblings) return
|
|
1923
|
-
} else if (ctx.refStack.has(schema.$ref)) { if (!hasSiblings) return }
|
|
2000
|
+
} else if (ctx.refStack.has(schema.$ref)) { if (ctx.nestedBoolean) throw DECLINE; if (!hasSiblings) return }
|
|
1924
2001
|
else {
|
|
1925
2002
|
ctx.refStack.add(schema.$ref)
|
|
1926
2003
|
genCode(ctx.rootDefs[defName], v, lines, ctx, knownType)
|
|
@@ -1932,7 +2009,7 @@ function genCode(schema, v, lines, ctx, knownType) {
|
|
|
1932
2009
|
const entry = ctx.rootDefs && ctx.rootDefs[schema.$ref]
|
|
1933
2010
|
const anchorTarget = entry && entry.raw ? entry.raw : (ctx.anchors && ctx.anchors[schema.$ref])
|
|
1934
2011
|
if (anchorTarget) {
|
|
1935
|
-
if (ctx.refStack.has(schema.$ref)) { if (!hasSiblings) return }
|
|
2012
|
+
if (ctx.refStack.has(schema.$ref)) { if (ctx.nestedBoolean) throw DECLINE; if (!hasSiblings) return }
|
|
1936
2013
|
else {
|
|
1937
2014
|
ctx.refStack.add(schema.$ref)
|
|
1938
2015
|
genCode(anchorTarget, v, lines, ctx, knownType)
|
|
@@ -1975,11 +2052,13 @@ function genCode(schema, v, lines, ctx, knownType) {
|
|
|
1975
2052
|
const target = ctx.anchors[anchorKey]
|
|
1976
2053
|
if (target === ctx.rootSchema) {
|
|
1977
2054
|
// Self-recursive: generate _validate(v) call
|
|
2055
|
+
if (ctx.nestedBoolean) throw DECLINE
|
|
1978
2056
|
ctx.usesRecursion = true
|
|
1979
2057
|
lines.push(`if(!_validate(${v}))return false`)
|
|
1980
2058
|
} else {
|
|
1981
2059
|
// Different schema: inline the target validation
|
|
1982
2060
|
const refKey = '$dynamicRef:' + anchorKey
|
|
2061
|
+
if (ctx.refStack.has(refKey) && ctx.nestedBoolean) throw DECLINE
|
|
1983
2062
|
if (!ctx.refStack.has(refKey)) {
|
|
1984
2063
|
ctx.refStack.add(refKey)
|
|
1985
2064
|
genCode(target, v, lines, ctx, knownType)
|
|
@@ -2057,24 +2136,34 @@ function genCode(schema, v, lines, ctx, knownType) {
|
|
|
2057
2136
|
const hoisted = {} // key -> access expression
|
|
2058
2137
|
if (schema.required && schema.properties && isObj) {
|
|
2059
2138
|
const reqChecks = []
|
|
2139
|
+
const typed = []
|
|
2060
2140
|
for (const key of schema.required) {
|
|
2061
2141
|
hoisted[key] = `${v}[${JSON.stringify(key)}]`
|
|
2062
2142
|
const prop = schema.properties[key]
|
|
2063
2143
|
const hasTypeCheck = prop && (prop.type || prop.enum || prop.const !== undefined)
|
|
2064
|
-
|
|
2065
|
-
|
|
2144
|
+
// A typed property needs no separate presence check on an ordinary
|
|
2145
|
+
// object: its type check fails on undefined. A name on Object.prototype
|
|
2146
|
+
// is never absent that way, and it is checked explicitly.
|
|
2147
|
+
if (!hasTypeCheck || PROTO_NAMES.has(key)) {
|
|
2148
|
+
reqChecks.push(`!${ownKeyExpr(ctx, v, key)}`)
|
|
2149
|
+
} else {
|
|
2150
|
+
typed.push(key)
|
|
2066
2151
|
}
|
|
2067
2152
|
}
|
|
2068
2153
|
if (reqChecks.length > 0) {
|
|
2069
2154
|
lines.push(`if(${reqChecks.join('||')})return false`)
|
|
2070
2155
|
}
|
|
2156
|
+
// On an object with another prototype the typed ones may be inherited.
|
|
2157
|
+
if (typed.length > 0) {
|
|
2158
|
+
hoistOnce(ctx, '_ownHoisted', OWN_HELPERS)
|
|
2159
|
+
lines.push(`if(${v}.__proto__!==Object.prototype&&!(${typed.map((k) => `_hop.call(${v},${JSON.stringify(k)})`).join('&&')}))return false`)
|
|
2160
|
+
}
|
|
2071
2161
|
} else if (schema.required && schema.required.length > 0) {
|
|
2162
|
+
const checks = schema.required.map(key => `!${ownKeyExpr(ctx, v, key)}`)
|
|
2072
2163
|
if (isObj) {
|
|
2073
|
-
const checks = schema.required.map(key => `${v}[${JSON.stringify(key)}]===undefined`)
|
|
2074
2164
|
lines.push(`if(${checks.join('||')})return false`)
|
|
2075
2165
|
} else {
|
|
2076
2166
|
// required applies to objects only; other types are ignored.
|
|
2077
|
-
const checks = schema.required.map(key => `${v}[${JSON.stringify(key)}]===undefined`)
|
|
2078
2167
|
lines.push(`if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})&&(${checks.join('||')}))return false`)
|
|
2079
2168
|
}
|
|
2080
2169
|
}
|
|
@@ -2267,8 +2356,8 @@ function genCode(schema, v, lines, ctx, knownType) {
|
|
|
2267
2356
|
// dependentRequired
|
|
2268
2357
|
if (schema.dependentRequired) {
|
|
2269
2358
|
for (const [key, deps] of Object.entries(schema.dependentRequired)) {
|
|
2270
|
-
const depChecks = deps.map(d =>
|
|
2271
|
-
lines.push(`if(${objGuard}
|
|
2359
|
+
const depChecks = deps.map(d => `!${ownKeyExpr(ctx, v, d)}`).join('||')
|
|
2360
|
+
lines.push(`if(${objGuard}${ownKeyExpr(ctx, v, key)}&&(${depChecks}))return false`)
|
|
2272
2361
|
}
|
|
2273
2362
|
}
|
|
2274
2363
|
|
|
@@ -2405,7 +2494,7 @@ function genCode(schema, v, lines, ctx, knownType) {
|
|
|
2405
2494
|
if (schema.dependentSchemas) {
|
|
2406
2495
|
for (const [key, depSchema] of Object.entries(schema.dependentSchemas)) {
|
|
2407
2496
|
const guard = isObj ? '' : `typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})&&`
|
|
2408
|
-
lines.push(`if(${guard}${
|
|
2497
|
+
lines.push(`if(${guard}${ownKeyExpr(ctx, v, key)}){`)
|
|
2409
2498
|
ctx.condDepth = (ctx.condDepth || 0) + 1
|
|
2410
2499
|
genCode(depSchema, v, lines, ctx, effectiveType)
|
|
2411
2500
|
ctx.condDepth--
|
|
@@ -2471,7 +2560,7 @@ function genCode(schema, v, lines, ctx, knownType) {
|
|
|
2471
2560
|
// Optional — hoist to local, check undefined
|
|
2472
2561
|
const oi = ctx.varCounter++
|
|
2473
2562
|
const local = `_o${oi}`
|
|
2474
|
-
lines.push(`{const ${local}=${v}[${JSON.stringify(key)}];if(${local}!==undefined){`)
|
|
2563
|
+
lines.push(`{const ${local}=${v}[${JSON.stringify(key)}];if(${local}!==undefined&&${ownGuard(ctx, v, key)}){`)
|
|
2475
2564
|
const combined = tryGenCombined(prop, local, ctx)
|
|
2476
2565
|
if (combined) {
|
|
2477
2566
|
lines.push(combined)
|
|
@@ -2480,7 +2569,7 @@ function genCode(schema, v, lines, ctx, knownType) {
|
|
|
2480
2569
|
}
|
|
2481
2570
|
lines.push(`}}`)
|
|
2482
2571
|
} else {
|
|
2483
|
-
lines.push(`if(typeof ${v}==='object'&&${v}!==null&&${
|
|
2572
|
+
lines.push(`if(typeof ${v}==='object'&&${v}!==null&&${ownKeyExpr(ctx, v, key)}){`)
|
|
2484
2573
|
genCode(prop, `${v}[${JSON.stringify(key)}]`, lines, ctx)
|
|
2485
2574
|
lines.push(`}`)
|
|
2486
2575
|
}
|
|
@@ -2844,7 +2933,7 @@ function genCode(schema, v, lines, ctx, knownType) {
|
|
|
2844
2933
|
for (const [trigger, depSchema] of Object.entries(schema.dependentSchemas)) {
|
|
2845
2934
|
const depResult = collectEvaluated(depSchema, ctx.schemaMap, ctx.rootDefs)
|
|
2846
2935
|
if (depResult.props && depResult.props.length > 0) {
|
|
2847
|
-
lines.push(`if(${
|
|
2936
|
+
lines.push(`if(${ownKeyExpr(ctx, v, trigger)}){${depResult.props.map(k => `${evVar}[${JSON.stringify(k)}]=1`).join(';')}}`)
|
|
2848
2937
|
}
|
|
2849
2938
|
}
|
|
2850
2939
|
const inner = `for(var _k in ${v}){if(!${evVar}[_k])return false}`
|
|
@@ -3442,7 +3531,7 @@ function compileToJSCodegenWithErrors(schema, schemaMap, userFormats, sourceOpts
|
|
|
3442
3531
|
if (typeof schema === 'boolean') {
|
|
3443
3532
|
return schema
|
|
3444
3533
|
? () => ({ valid: true, errors: [] })
|
|
3445
|
-
: () => ({ valid: false, errors: [{ keyword: '
|
|
3534
|
+
: () => ({ valid: false, errors: [{ keyword: 'not', instancePath: '', schemaPath: '#', params: {}, message: 'boolean schema is false' }] })
|
|
3446
3535
|
}
|
|
3447
3536
|
if (typeof schema !== 'object' || schema === null) return null
|
|
3448
3537
|
if (!sharedCodegenGate(schema, schemaMap)) return null
|
|
@@ -3803,7 +3892,7 @@ function genCodeE(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
|
3803
3892
|
const reqSp = `${schemaPrefix}/required`
|
|
3804
3893
|
const reqLit = buildErrorLiteral({ keyword: 'required', schemaPath: reqSp, sourceMap: ctx.sourceMap })
|
|
3805
3894
|
for (const key of schema.required) {
|
|
3806
|
-
lines.push(`if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})
|
|
3895
|
+
lines.push(`if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})&&!${ownKeyExpr(ctx, v, key)}){_e.push({code:'${reqLit.codeStr}',keyword:'required',instancePath:${pathExpr||'""'},schemaPath:'${reqSp}'${ordinalField(ctx, `${reqSp}`)},params:{missingProperty:'${esc(key)}'},message:"must have required property '${esc(key)}'",docUrl:'${reqLit.docUrl}'${reqLit.frame}});if(!_all)return{valid:false,errors:_e}}`)
|
|
3807
3896
|
}
|
|
3808
3897
|
}
|
|
3809
3898
|
|
|
@@ -4020,7 +4109,7 @@ function genCodeE(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
|
4020
4109
|
const drLit = buildErrorLiteral({ keyword: 'dependentRequired', schemaPath: drSp, sourceMap: ctx.sourceMap })
|
|
4021
4110
|
for (const [key, deps] of Object.entries(schema.dependentRequired)) {
|
|
4022
4111
|
for (const dep of deps) {
|
|
4023
|
-
lines.push(`if(typeof ${v}==='object'&&${v}!==null&&${
|
|
4112
|
+
lines.push(`if(typeof ${v}==='object'&&${v}!==null&&${ownKeyExpr(ctx, v, key)}&&!${ownKeyExpr(ctx, v, dep)}){_e.push({code:'${drLit.codeStr}',keyword:'required',instancePath:${pathExpr||'""'},schemaPath:'${drSp}'${ordinalField(ctx, `${drSp}`)},params:{missingProperty:'${esc(dep)}'},message:"must have required property '${esc(dep)}'",docUrl:'${drLit.docUrl}'${drLit.frame}});if(!_all)return{valid:false,errors:_e}}`)
|
|
4024
4113
|
}
|
|
4025
4114
|
}
|
|
4026
4115
|
}
|
|
@@ -4029,7 +4118,7 @@ function genCodeE(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
|
4029
4118
|
if (schema.properties) {
|
|
4030
4119
|
for (const [key, prop] of Object.entries(schema.properties)) {
|
|
4031
4120
|
const childPath = childPathExpr(pathExpr, ptrSeg(key))
|
|
4032
|
-
lines.push(`if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})&&${
|
|
4121
|
+
lines.push(`if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})&&${ownKeyExpr(ctx, v, key)}){`)
|
|
4033
4122
|
genCodeE(prop, `${v}[${JSON.stringify(key)}]`, childPath, lines, ctx, schemaPrefix+'/properties/'+ptrSeg(key))
|
|
4034
4123
|
lines.push(`}`)
|
|
4035
4124
|
}
|
|
@@ -4098,7 +4187,7 @@ function genCodeE(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
|
4098
4187
|
// dependentSchemas
|
|
4099
4188
|
if (schema.dependentSchemas) {
|
|
4100
4189
|
for (const [key, depSchema] of Object.entries(schema.dependentSchemas)) {
|
|
4101
|
-
lines.push(`if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})&&${
|
|
4190
|
+
lines.push(`if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})&&${ownKeyExpr(ctx, v, key)}){`)
|
|
4102
4191
|
genCodeE(depSchema, v, pathExpr, lines, ctx, schemaPrefix+'/dependentSchemas/'+ptrSeg(key))
|
|
4103
4192
|
lines.push(`}`)
|
|
4104
4193
|
}
|
|
@@ -4178,7 +4267,7 @@ function genCodeE(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
|
4178
4267
|
if (schema.contains !== undefined) {
|
|
4179
4268
|
const ci = ctx.varCounter++
|
|
4180
4269
|
const subLines = []
|
|
4181
|
-
|
|
4270
|
+
nestedGenCode(schema.contains, `_cv`, subLines, ctx)
|
|
4182
4271
|
const fnBody = subLines.length === 0 ? `return true` : `${subLines.join(';')};return true`
|
|
4183
4272
|
const minC = schema.minContains !== undefined ? schema.minContains : 1
|
|
4184
4273
|
const maxC = schema.maxContains
|
|
@@ -4210,7 +4299,7 @@ function genCodeE(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
|
4210
4299
|
// not
|
|
4211
4300
|
if (schema.not !== undefined) {
|
|
4212
4301
|
const subLines = []
|
|
4213
|
-
|
|
4302
|
+
nestedGenCode(schema.not, '_nv', subLines, ctx)
|
|
4214
4303
|
const nfn = subLines.length === 0 ? `function(_nv){return true}` : `function(_nv){${subLines.join(';')};return true}`
|
|
4215
4304
|
const fi = ctx.varCounter++
|
|
4216
4305
|
lines.push(`{const _nf${fi}=${nfn};if(_nf${fi}(${v})){${fail('not', 'not', '{}', "'must NOT be valid'")}}}`)
|
|
@@ -4219,7 +4308,7 @@ function genCodeE(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
|
4219
4308
|
// if/then/else
|
|
4220
4309
|
if (schema.if !== undefined) {
|
|
4221
4310
|
const ifLines = []
|
|
4222
|
-
|
|
4311
|
+
nestedGenCode(schema.if, '_iv', ifLines, ctx)
|
|
4223
4312
|
const fi = ctx.varCounter++
|
|
4224
4313
|
const ifFn = ifLines.length === 0
|
|
4225
4314
|
? `function(_iv){return true}`
|
|
@@ -4266,7 +4355,7 @@ function compileToJSCombined(schema, VALID_RESULT, schemaMap, userFormats) {
|
|
|
4266
4355
|
if (typeof schema === 'boolean') {
|
|
4267
4356
|
return schema
|
|
4268
4357
|
? () => VALID_RESULT
|
|
4269
|
-
: () => ({ valid: false, errors: [{ keyword: '
|
|
4358
|
+
: () => ({ valid: false, errors: [{ keyword: 'not', instancePath: '', schemaPath: '#', params: {}, message: 'boolean schema is false' }] })
|
|
4270
4359
|
}
|
|
4271
4360
|
if (typeof schema !== 'object' || schema === null) return null
|
|
4272
4361
|
if (!sharedCodegenGate(schema, schemaMap)) return null
|
|
@@ -4324,22 +4413,34 @@ function compileToJSCombined(schema, VALID_RESULT, schemaMap, userFormats) {
|
|
|
4324
4413
|
const ctx = { varCounter: 0, helperCode: [], shared: [], closureVars: ['_cpLen'], closureVals: [_cpLen],
|
|
4325
4414
|
rootDefs: cRootDefs, refStack: new Set(), schemaMap: schemaMap || null, anchors: cAnchors, rootSchema: schema, userFormats: userFormats || null }
|
|
4326
4415
|
const lines = []
|
|
4327
|
-
|
|
4416
|
+
try {
|
|
4417
|
+
genCodeC(schema, 'd', '', lines, ctx, '#')
|
|
4418
|
+
} catch (e) {
|
|
4419
|
+
if (e === DECLINE) return null
|
|
4420
|
+
throw e
|
|
4421
|
+
}
|
|
4328
4422
|
if (lines.length === 0) return () => VALID_RESULT
|
|
4329
4423
|
|
|
4330
|
-
// Use factory pattern: closure vars (regexes, etc.) created once, not per call
|
|
4331
|
-
|
|
4332
|
-
//
|
|
4424
|
+
// Use factory pattern: closure vars (regexes, etc.) created once, not per call.
|
|
4425
|
+
// The helpers are constants too and are declared in the factory: inside the
|
|
4426
|
+
// function they were rebuilt on every call. `__ataSafeRe` is bound when a
|
|
4427
|
+
// helper compiles a pattern through it, which the boolean generator does for
|
|
4428
|
+
// subschemas under not/if/contains; without the binding those threw a
|
|
4429
|
+
// ReferenceError on the first document that reached them.
|
|
4430
|
+
const closureNames = ctx.usesSafeRe ? [...ctx.closureVars, '__ataSafeRe'] : ctx.closureVars
|
|
4431
|
+
const closureArgs = ctx.usesSafeRe ? [...ctx.closureVals, compileSafe] : ctx.closureVals
|
|
4432
|
+
const closureParams = closureNames.join(',')
|
|
4433
|
+
const helpers = ctx.helperCode.length ? ctx.helperCode.join('\n ') + '\n ' : ''
|
|
4434
|
+
// Lazy error array: no allocation for valid data (the common case)
|
|
4333
4435
|
const inner = `let _e;\n ` +
|
|
4334
|
-
(ctx.helperCode.length ? ctx.helperCode.join('\n ') + '\n ' : '') +
|
|
4335
4436
|
lines.join('\n ') +
|
|
4336
4437
|
`\n return _e?{valid:false,errors:_e}:R`
|
|
4337
4438
|
|
|
4338
4439
|
try {
|
|
4339
|
-
if (typeof process !== 'undefined' && process.env && process.env.ATA_DUMP_CODEGEN) console.log('=== COMBINED CODEGEN ===\n' + inner + '\n=== CLOSURE VARS: ' + ctx.closureVars.length + ' ===')
|
|
4440
|
+
if (typeof process !== 'undefined' && process.env && process.env.ATA_DUMP_CODEGEN) console.log('=== COMBINED CODEGEN ===\n' + helpers + inner + '\n=== CLOSURE VARS: ' + ctx.closureVars.length + ' ===')
|
|
4340
4441
|
const factory = new Function('R' + (closureParams ? ',' + closureParams : ''),
|
|
4341
|
-
|
|
4342
|
-
return factory(VALID_RESULT, ...
|
|
4442
|
+
`${helpers}return function(d){${inner}}`)
|
|
4443
|
+
return factory(VALID_RESULT, ...closureArgs)
|
|
4343
4444
|
} catch (e) {
|
|
4344
4445
|
if (typeof process !== 'undefined' && process.env && process.env.ATA_DEBUG) console.error('compileToJSCombined error:', e.message, '\n', inner.slice(0, 500))
|
|
4345
4446
|
return null
|
|
@@ -4411,17 +4512,17 @@ function genCodeC(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
|
4411
4512
|
const anchorKey = schema.$dynamicRef.startsWith('#') ? schema.$dynamicRef : '#' + schema.$dynamicRef
|
|
4412
4513
|
if (ctx.anchors && ctx.anchors[anchorKey]) {
|
|
4413
4514
|
const target = ctx.anchors[anchorKey]
|
|
4414
|
-
|
|
4415
|
-
|
|
4416
|
-
|
|
4417
|
-
|
|
4418
|
-
|
|
4419
|
-
|
|
4420
|
-
|
|
4421
|
-
|
|
4422
|
-
|
|
4423
|
-
|
|
4424
|
-
|
|
4515
|
+
// This generator has no named functions, so it cannot follow a reference
|
|
4516
|
+
// back to the root or around a cycle. It used to emit nothing in both
|
|
4517
|
+
// cases, which the function then answered as valid: a document the other
|
|
4518
|
+
// generators reject passed through validateJSON. It declines instead,
|
|
4519
|
+
// and the schema takes the error generator.
|
|
4520
|
+
if (target === ctx.rootSchema) throw DECLINE
|
|
4521
|
+
const refKey = '$dynamicRef:' + anchorKey
|
|
4522
|
+
if (ctx.refStack.has(refKey)) throw DECLINE
|
|
4523
|
+
ctx.refStack.add(refKey)
|
|
4524
|
+
genCodeC(target, v, pathExpr, lines, ctx, schemaPrefix)
|
|
4525
|
+
ctx.refStack.delete(refKey)
|
|
4425
4526
|
}
|
|
4426
4527
|
}
|
|
4427
4528
|
|
|
@@ -4441,14 +4542,28 @@ function genCodeC(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
|
4441
4542
|
// Static error: pre-allocate as frozen closure variable
|
|
4442
4543
|
const ei = ctx.varCounter++
|
|
4443
4544
|
const errVar = `_E${ei}`
|
|
4444
|
-
|
|
4445
|
-
|
|
4545
|
+
// The path and message are source literals; evaluate them rather than
|
|
4546
|
+
// strip the quotes, so an escaped quote or backslash in a key comes
|
|
4547
|
+
// out as the character and not as the escape.
|
|
4548
|
+
let pathVal, msgVal
|
|
4549
|
+
try {
|
|
4550
|
+
pathVal = pathExpr ? Function('return ' + pathExpr)() : ''
|
|
4551
|
+
msgVal = Function('return ' + msgCode)()
|
|
4552
|
+
} catch {
|
|
4553
|
+
// Not a literal that parses on its own (a key with a raw line break):
|
|
4554
|
+
// leave it to the inline form below, which declines the same way the
|
|
4555
|
+
// whole function would.
|
|
4556
|
+
pathVal = undefined
|
|
4557
|
+
}
|
|
4558
|
+
if (pathVal !== undefined) {
|
|
4559
|
+
const spVal = unescapeSp(sp)
|
|
4446
4560
|
ctx.closureVars.push(errVar)
|
|
4447
|
-
const o = ordinalFor(ctx.rootSchema,
|
|
4448
|
-
const lit = {keyword, instancePath: pathVal, schemaPath:
|
|
4561
|
+
const o = ordinalFor(ctx.rootSchema, spVal)
|
|
4562
|
+
const lit = {keyword, instancePath: pathVal, schemaPath: spVal, params: Object.freeze(paramsVal), message: msgVal}
|
|
4449
4563
|
if (o !== null) lit._o = o
|
|
4450
4564
|
ctx.closureVals.push(Object.freeze(lit))
|
|
4451
4565
|
return `(_e||(_e=[])).push(${errVar})`
|
|
4566
|
+
}
|
|
4452
4567
|
}
|
|
4453
4568
|
}
|
|
4454
4569
|
// Dynamic path (e.g., array index): inline as before
|
|
@@ -4511,15 +4626,22 @@ function genCodeC(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
|
4511
4626
|
destructKeys.push(`${JSON.stringify(key)}:${lv}`)
|
|
4512
4627
|
}
|
|
4513
4628
|
}
|
|
4514
|
-
if (destructKeys.length > 0)
|
|
4629
|
+
if (destructKeys.length > 0) {
|
|
4630
|
+
lines.push(`let{${destructKeys.join(',')}}=${v}`)
|
|
4631
|
+
// A value the object only inherits reads as absent, here and in the
|
|
4632
|
+
// property checks that use the same local below.
|
|
4633
|
+
for (const key of schema.required) {
|
|
4634
|
+
if (hoisted[key]) lines.push(`if(${hoisted[key]}!==undefined&&!${ownGuard(ctx, v, key)})${hoisted[key]}=undefined`)
|
|
4635
|
+
}
|
|
4636
|
+
}
|
|
4515
4637
|
for (const key of schema.required) {
|
|
4516
|
-
const check = hoisted[key] ? `${hoisted[key]}===undefined` :
|
|
4638
|
+
const check = hoisted[key] ? `${hoisted[key]}===undefined` : `!${ownKeyExpr(ctx, v, key)}`
|
|
4517
4639
|
if (isStaticPath) {
|
|
4518
4640
|
const ei = ctx.varCounter++
|
|
4519
4641
|
const errVar = `_E${ei}`
|
|
4520
|
-
const pathVal = pathExpr ? pathExpr
|
|
4642
|
+
const pathVal = pathExpr ? literalValue(pathExpr) : ''
|
|
4521
4643
|
ctx.closureVars.push(errVar)
|
|
4522
|
-
ctx.closureVals.push(Object.freeze({keyword: 'required', instancePath: pathVal, schemaPath: `${schemaPrefix}/required
|
|
4644
|
+
ctx.closureVals.push(Object.freeze({keyword: 'required', instancePath: pathVal, schemaPath: unescapeSp(`${schemaPrefix}/required`), params: Object.freeze({missingProperty: key}), message: `must have required property '${key}'`}))
|
|
4523
4645
|
lines.push(`if(${check}){(_e||(_e=[])).push(${errVar})}`)
|
|
4524
4646
|
} else {
|
|
4525
4647
|
lines.push(`if(${check}){(_e||(_e=[])).push({keyword:'required',instancePath:${pathExpr||'""'},schemaPath:'${schemaPrefix}/required'${ordinalField(ctx, `${schemaPrefix}/required`)},params:{missingProperty:'${esc(key)}'},message:"must have required property '${esc(key)}'"})}`)
|
|
@@ -4531,12 +4653,12 @@ function genCodeC(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
|
4531
4653
|
if (isStatic) {
|
|
4532
4654
|
const ei = ctx.varCounter++
|
|
4533
4655
|
const errVar = `_E${ei}`
|
|
4534
|
-
const pathVal = pathExpr ? pathExpr
|
|
4656
|
+
const pathVal = pathExpr ? literalValue(pathExpr) : ''
|
|
4535
4657
|
ctx.closureVars.push(errVar)
|
|
4536
|
-
ctx.closureVals.push(Object.freeze({keyword: 'required', instancePath: pathVal, schemaPath: `${schemaPrefix}/required
|
|
4537
|
-
lines.push(`if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})
|
|
4658
|
+
ctx.closureVals.push(Object.freeze({keyword: 'required', instancePath: pathVal, schemaPath: unescapeSp(`${schemaPrefix}/required`), params: Object.freeze({missingProperty: key}), message: `must have required property '${key}'`}))
|
|
4659
|
+
lines.push(`if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})&&!${ownKeyExpr(ctx, v, key)}){(_e||(_e=[])).push(${errVar})}`)
|
|
4538
4660
|
} else {
|
|
4539
|
-
lines.push(`if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})
|
|
4661
|
+
lines.push(`if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})&&!${ownKeyExpr(ctx, v, key)}){(_e||(_e=[])).push({keyword:'required',instancePath:${pathExpr||'""'},schemaPath:'${schemaPrefix}/required'${ordinalField(ctx, `${schemaPrefix}/required`)},params:{missingProperty:'${esc(key)}'},message:"must have required property '${esc(key)}'"})}`)
|
|
4540
4662
|
}
|
|
4541
4663
|
}
|
|
4542
4664
|
}
|
|
@@ -4696,12 +4818,12 @@ function genCodeC(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
|
4696
4818
|
if (isStatic) {
|
|
4697
4819
|
const ei = ctx.varCounter++
|
|
4698
4820
|
const errVar = `_E${ei}`
|
|
4699
|
-
const pathVal = pathExpr ? pathExpr
|
|
4821
|
+
const pathVal = pathExpr ? literalValue(pathExpr) : ''
|
|
4700
4822
|
ctx.closureVars.push(errVar)
|
|
4701
|
-
ctx.closureVals.push(Object.freeze({keyword: 'required', instancePath: pathVal, schemaPath: `${schemaPrefix}/dependentRequired
|
|
4702
|
-
lines.push(`if(typeof ${v}==='object'&&${v}!==null&&${
|
|
4823
|
+
ctx.closureVals.push(Object.freeze({keyword: 'required', instancePath: pathVal, schemaPath: unescapeSp(`${schemaPrefix}/dependentRequired`), params: Object.freeze({missingProperty: dep}), message: `must have required property '${dep}'`}))
|
|
4824
|
+
lines.push(`if(typeof ${v}==='object'&&${v}!==null&&${ownKeyExpr(ctx, v, key)}&&!${ownKeyExpr(ctx, v, dep)}){(_e||(_e=[])).push(${errVar})}`)
|
|
4703
4825
|
} else {
|
|
4704
|
-
lines.push(`if(typeof ${v}==='object'&&${v}!==null&&${
|
|
4826
|
+
lines.push(`if(typeof ${v}==='object'&&${v}!==null&&${ownKeyExpr(ctx, v, key)}&&!${ownKeyExpr(ctx, v, dep)}){(_e||(_e=[])).push({keyword:'required',instancePath:${pathExpr||'""'},schemaPath:'${schemaPrefix}/dependentRequired'${ordinalField(ctx, `${schemaPrefix}/dependentRequired`)},params:{missingProperty:'${esc(dep)}'},message:"must have required property '${esc(dep)}'"})}`)
|
|
4705
4827
|
}
|
|
4706
4828
|
}
|
|
4707
4829
|
}
|
|
@@ -4713,16 +4835,17 @@ function genCodeC(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
|
4713
4835
|
const pv = hoisted[key] || `${v}[${JSON.stringify(key)}]`
|
|
4714
4836
|
const childPath = childPathExpr(pathExpr, ptrSeg(key))
|
|
4715
4837
|
if (requiredSet.has(key) && isObj) {
|
|
4716
|
-
|
|
4838
|
+
// A hoisted local was already cleared if the value is only inherited.
|
|
4839
|
+
lines.push(`if(${pv}!==undefined${hoisted[key] ? '' : '&&' + ownGuard(ctx, v, key)}){`)
|
|
4717
4840
|
genCodeC(prop, pv, childPath, lines, ctx, schemaPrefix+'/properties/'+ptrSeg(key))
|
|
4718
4841
|
lines.push(`}`)
|
|
4719
4842
|
} else if (isObj) {
|
|
4720
4843
|
const oi = ctx.varCounter++
|
|
4721
|
-
lines.push(`{const _o${oi}=${v}[${JSON.stringify(key)}];if(_o${oi}!==undefined){`)
|
|
4844
|
+
lines.push(`{const _o${oi}=${v}[${JSON.stringify(key)}];if(_o${oi}!==undefined&&${ownGuard(ctx, v, key)}){`)
|
|
4722
4845
|
genCodeC(prop, `_o${oi}`, childPath, lines, ctx, schemaPrefix+'/properties/'+ptrSeg(key))
|
|
4723
4846
|
lines.push(`}}`)
|
|
4724
4847
|
} else {
|
|
4725
|
-
lines.push(`if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})&&${
|
|
4848
|
+
lines.push(`if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})&&${ownKeyExpr(ctx, v, key)}){`)
|
|
4726
4849
|
genCodeC(prop, `${v}[${JSON.stringify(key)}]`, childPath, lines, ctx, schemaPrefix+'/properties/'+ptrSeg(key))
|
|
4727
4850
|
lines.push(`}`)
|
|
4728
4851
|
}
|
|
@@ -4838,7 +4961,7 @@ function genCodeC(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
|
4838
4961
|
// dependentSchemas
|
|
4839
4962
|
if (schema.dependentSchemas) {
|
|
4840
4963
|
for (const [key, depSchema] of Object.entries(schema.dependentSchemas)) {
|
|
4841
|
-
lines.push(`if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})&&${
|
|
4964
|
+
lines.push(`if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})&&${ownKeyExpr(ctx, v, key)}){`)
|
|
4842
4965
|
genCodeC(depSchema, v, pathExpr, lines, ctx, schemaPrefix+'/dependentSchemas/'+ptrSeg(key))
|
|
4843
4966
|
lines.push(`}`)
|
|
4844
4967
|
}
|
|
@@ -4904,7 +5027,7 @@ function genCodeC(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
|
4904
5027
|
if (schema.contains !== undefined) {
|
|
4905
5028
|
const ci = ctx.varCounter++
|
|
4906
5029
|
const subLines = []
|
|
4907
|
-
|
|
5030
|
+
nestedGenCode(schema.contains, `_cv`, subLines, ctx)
|
|
4908
5031
|
const fnBody = subLines.length === 0 ? `return true` : `${subLines.join(';')};return true`
|
|
4909
5032
|
const minC = schema.minContains !== undefined ? schema.minContains : 1
|
|
4910
5033
|
const maxC = schema.maxContains
|
|
@@ -4920,20 +5043,20 @@ function genCodeC(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
|
4920
5043
|
// anyOf
|
|
4921
5044
|
if (schema.anyOf) {
|
|
4922
5045
|
const fi = ctx.varCounter++
|
|
4923
|
-
const fns = schema.anyOf.map(sub => { const sl = [];
|
|
5046
|
+
const fns = schema.anyOf.map(sub => { const sl = []; nestedGenCode(sub, '_av', sl, ctx); return sl.length === 0 ? `function(_av){return true}` : `function(_av){${sl.join(';')};return true}` })
|
|
4924
5047
|
lines.push(`{const _af${fi}=[${fns.join(',')}];let _am=false;for(let _ai=0;_ai<_af${fi}.length;_ai++){if(_af${fi}[_ai](${v})){_am=true;break}}if(!_am){${fail('anyOf', 'anyOf', '{}', "'must match a schema in anyOf'")}}}`)
|
|
4925
5048
|
}
|
|
4926
5049
|
|
|
4927
5050
|
// oneOf
|
|
4928
5051
|
if (schema.oneOf) {
|
|
4929
5052
|
const fi = ctx.varCounter++
|
|
4930
|
-
const fns = schema.oneOf.map(sub => { const sl = [];
|
|
5053
|
+
const fns = schema.oneOf.map(sub => { const sl = []; nestedGenCode(sub, '_ov', sl, ctx); return sl.length === 0 ? `function(_ov){return true}` : `function(_ov){${sl.join(';')};return true}` })
|
|
4931
5054
|
lines.push(`{const _of${fi}=[${fns.join(',')}];let _oc=0;for(let _oi=0;_oi<_of${fi}.length;_oi++){if(_of${fi}[_oi](${v}))_oc++;if(_oc>1)break}if(_oc!==1){${fail('oneOf', 'oneOf', '{}', "'must match exactly one schema in oneOf'")}}}`)
|
|
4932
5055
|
}
|
|
4933
5056
|
|
|
4934
5057
|
// not
|
|
4935
5058
|
if (schema.not !== undefined) {
|
|
4936
|
-
const sl = [];
|
|
5059
|
+
const sl = []; nestedGenCode(schema.not, '_nv', sl, ctx)
|
|
4937
5060
|
const nfn = sl.length === 0 ? `function(_nv){return true}` : `function(_nv){${sl.join(';')};return true}`
|
|
4938
5061
|
const fi = ctx.varCounter++
|
|
4939
5062
|
lines.push(`{const _nf${fi}=${nfn};if(_nf${fi}(${v})){${fail('not', 'not', '{}', "'must NOT be valid'")}}}`)
|
|
@@ -4941,7 +5064,7 @@ function genCodeC(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
|
4941
5064
|
|
|
4942
5065
|
// if/then/else
|
|
4943
5066
|
if (schema.if !== undefined) {
|
|
4944
|
-
const sl = [];
|
|
5067
|
+
const sl = []; nestedGenCode(schema.if, '_iv', sl, ctx)
|
|
4945
5068
|
const fi = ctx.varCounter++
|
|
4946
5069
|
const ifFn = sl.length === 0 ? `function(_iv){return true}` : `function(_iv){${sl.join(';')};return true}`
|
|
4947
5070
|
lines.push(`{const _if${fi}=${ifFn}`)
|
package/lib/shape-classifier.js
CHANGED
|
@@ -69,6 +69,15 @@ function isTier0Object(schema) {
|
|
|
69
69
|
for (const r of schema.required) if (typeof r !== 'string') return false;
|
|
70
70
|
}
|
|
71
71
|
const props = schema.properties;
|
|
72
|
+
// The tier-0 validator checks presence through the per-property constraints,
|
|
73
|
+
// so a required name with no entry in properties was never checked at all:
|
|
74
|
+
// `{ type: 'object', required: ['id'] }` accepted `{}` until the schema moved
|
|
75
|
+
// to generated code, and for good when the generator declined it.
|
|
76
|
+
if (schema.required !== undefined) {
|
|
77
|
+
for (const r of schema.required) {
|
|
78
|
+
if (props === undefined || props === null || typeof props !== 'object' || !Object.prototype.hasOwnProperty.call(props, r)) return false;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
72
81
|
if (props === undefined) return true;
|
|
73
82
|
if (typeof props !== 'object' || props === null || Array.isArray(props)) return false;
|
|
74
83
|
const keys = Object.keys(props);
|
package/lib/tier0.js
CHANGED
|
@@ -41,6 +41,12 @@ const F_MULT = 16;
|
|
|
41
41
|
|
|
42
42
|
// Build a constraint tuple for one primitive-typed property.
|
|
43
43
|
// All fields have the same layout so every constraint shares one hidden class.
|
|
44
|
+
// Names found on Object.prototype. For these a read of data[key] finds the
|
|
45
|
+
// inherited value on every ordinary object, so presence is asked of the object
|
|
46
|
+
// itself; see ownKeyExpr in js-compiler.js.
|
|
47
|
+
const PROTO_NAMES = new Set([...Object.getOwnPropertyNames(Object.prototype), '__proto__']);
|
|
48
|
+
const _hop = Object.prototype.hasOwnProperty;
|
|
49
|
+
|
|
44
50
|
function primConstraint(key, propSchema) {
|
|
45
51
|
const t = propSchema.type;
|
|
46
52
|
const hasEnum = Array.isArray(propSchema.enum);
|
|
@@ -53,6 +59,7 @@ function primConstraint(key, propSchema) {
|
|
|
53
59
|
if (typeof propSchema.multipleOf === 'number') numFlags |= F_MULT;
|
|
54
60
|
return {
|
|
55
61
|
key,
|
|
62
|
+
protoName: PROTO_NAMES.has(key),
|
|
56
63
|
typeMask: TYPE_MASK[t] | 0,
|
|
57
64
|
numFlags,
|
|
58
65
|
hasEnum,
|
|
@@ -159,7 +166,10 @@ function tier0ValidateObject(plan, data) {
|
|
|
159
166
|
let seenMask = 0;
|
|
160
167
|
for (let i = 0; i < n; i++) {
|
|
161
168
|
const c = cs[i];
|
|
162
|
-
|
|
169
|
+
let v = data[c.key];
|
|
170
|
+
// Presence is an own-property question: a value the object inherits is
|
|
171
|
+
// treated as absent, as the interpreted engine treats it.
|
|
172
|
+
if (v !== undefined && (c.protoName || data.__proto__ !== Object.prototype) && !_hop.call(data, c.key)) v = undefined;
|
|
163
173
|
if (v === undefined) {
|
|
164
174
|
if (reqMask & (1 << i)) return false;
|
|
165
175
|
continue;
|
package/lib/version.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ata-validator",
|
|
3
|
-
"version": "1.31.
|
|
3
|
+
"version": "1.31.2",
|
|
4
4
|
"description": "JSON Schema validation that compiles for speed and still runs where code generation is blocked. Compiled and interpreted engines answer identically at 100% of the official suite. TypeScript inference, Standard Schema V1, and a build step that emits dependency-free modules.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"module": "index.mjs",
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
"release:check": "node scripts/regen-safe-regex-source.js && node tests/test_pack_purity.js && node scripts/check-doc-coverage.js && node tests/test_error_codes_lock.js && node tests/test_safe_regex_source_sync.js && node tests/test_version_sync.js",
|
|
52
52
|
"build": "cmake-js build --target ata",
|
|
53
53
|
"rebuild": "cmake-js rebuild --target ata",
|
|
54
|
-
"test": "node test.js && node tests/test_removed_aot_methods.js && node tests/test_no_native.js && node tests/test_no_eval.js && node tests/test_property_dependencies.js && node tests/test_v1_dialect.js && node tests/test_buffer_path_parity.js && node tests/test_buffer_gate.js && node tests/test_scanner_differential.js && node tests/test_validate_json_scanner_cost.js && node tests/test_buffer_reject_cost.js && node tests/test_nan_verdict.js && node tests/test_remove_additional_nested.js && node tests/test_exclusive_bounds.js && node tests/test_engine_differential.js && node tests/test_error_shape_differential.js && node tests/test_output_format.js && node tests/test_retry_message.js && node tests/test_describe_schema.js && node tests/test_aot_parse.js && node tests/test_draft7_semantics.js && node tests/test_metaschema_ref.js && node tests/test_pure_js_unsupported.js && node tests/test_native_load_order.js && node tests/test_pack_purity.js && node tests/test_make_native_package.js && node tests/test_browser_nofs.js && node tests/test_browser_imports_guard.js && node tests/test_version_sync.js && node tests/test_native_loaded.js && node tests/test_safe_regex_source_sync.js && node tests/test_t_builder.js && node tests/test_async_refine.js && node tests/test_safe_regex.js && node tests/test_safe_regex_integration.js && node tests/test_aot_build.js && node tests/test_aot_differential.js && node tests/test_aot_cli_build.js && node tests/test_aot_cli_smoke.js && node tests/test_bundle_standalone.js && node tests/test_standalone_anyof.js && node tests/test_standalone_formats.js && node tests/test_aot_format_mode.js && node tests/test_aot_additional_props_errors.js && node tests/test_id_anchor_refs.js && node tests/test_engine_routing.js && node tests/test_engine_option.js && node tests/test_compile_cache_order.js && node tests/test_engine_diagnostic.js && node tests/test_format_engine_parity.js && node tests/test_error_accumulator_collision.js && node tests/test_uri_helper_parity.js && node tests/test_uri_fast_path.js && node tests/test_extend_verdict.js && node tests/test_ipv4_regex.js && node tests/test_ipv6_fast_path.js && node tests/test_formats_single_pass.js && node tests/test_email_format_cost.js && node tests/test_format_single_error.js && node tests/test_defs_pointer_alias.js && node tests/test_cross_doc_root_ref.js && node tests/test_codegen_entrypoint_agreement.js && node tests/test_hybrid_agreement.js && node tests/test_codegen_edge_shapes.js && node tests/test_deferred_def_scope.js && node tests/test_node_scoped_ctx_flags.js && node tests/test_fast_slot_exhaustion.js && node tests/test_ajv_errors.js && node tests/test_custom_keywords.js && node tests/test_strict_schema.js && node tests/test_aot_external_checks.js && node tests/test_ajv_parity.js && node tests/test_user_format_error_path.js && node tests/test_unevaluated_error_path.js && node tests/test_unevaluated_error_shape.js && node tests/test_pattern_properties_errors.js && node tests/test_additional_properties_scaling.js && node tests/test_additional_props_combined_cost.js && node tests/test_no_input_mutation.js && node tests/test_typed_validator_runner.js && node tests/test_define_schema.js && node tests/test_error_codes_lock.js && node tests/test_error_code_lookup.js && node tests/test_error_order.js && node tests/test_error_order_ordinal.js && node tests/test_rejection_shape.js && node tests/test_lazy_normalization.js && node tests/test_value_equality.js && node tests/test_vocabulary.js && node tests/test_schema_scan.js && node tests/test_lazy_errors.js && node tests/test_single_pass_errors.js && node tests/test_lazy_json_errors.js && node tests/test_lazy_instance.js && node tests/test_cyclic_input.js && node tests/test_native_error_codes.js && node tests/test_verdict_preprocess.js && node tests/test_defaults_own_keys.js && node tests/test_plan_compiler.js && node tests/test_nullable.js && node tests/test_validate_and_parse.js && node tests/test_validate_data.js && node tests/test_enrich_error.js && node tests/test_enrich_received.js && node tests/test_rich_errors_optout.js && node tests/test_error_messages.js && node tests/test_source_positions.js && node tests/fuzz_positions.js && node tests/test_malformed_json_termination.js && node tests/test_data_positions.js && node tests/test_targeted_positions.js && node tests/test_position_map_cost.js && node tests/test_targeted_positions_cost.js && node tests/test_aot_staleness.js && node tests/test_file_positions.js && node tests/test_aot_positions_cost.js && node tests/test_schema_hash.js && node tests/test_render_shared.js && node tests/test_renderers.js && node tests/test_additive_fields.js && node tests/test_diagnostic_source.js && node tests/test_diagnose.js && node tests/test_correlate.js && node tests/test_diagnostics_score.js && node tests/test_runtime_error_dx.js && node tests/test_aot_error_dx.js && node tests/test_abort_early.js && node tests/test_branch_collapse.js && node tests/test_suggestions.js && node tests/test_cli_validate.js && node tests/test_cli_version.js && node benchmark/bench_aot_size.mjs",
|
|
54
|
+
"test": "node test.js && node tests/test_removed_aot_methods.js && node tests/test_no_native.js && node tests/test_no_eval.js && node tests/test_property_dependencies.js && node tests/test_v1_dialect.js && node tests/test_buffer_path_parity.js && node tests/test_buffer_gate.js && node tests/test_scanner_differential.js && node tests/test_validate_json_scanner_cost.js && node tests/test_buffer_reject_cost.js && node tests/test_nan_verdict.js && node tests/test_remove_additional_nested.js && node tests/test_exclusive_bounds.js && node tests/test_engine_differential.js && node tests/test_error_shape_differential.js && node tests/test_output_format.js && node tests/test_retry_message.js && node tests/test_describe_schema.js && node tests/test_aot_parse.js && node tests/test_draft7_semantics.js && node tests/test_metaschema_ref.js && node tests/test_pure_js_unsupported.js && node tests/test_native_load_order.js && node tests/test_pack_purity.js && node tests/test_make_native_package.js && node tests/test_browser_nofs.js && node tests/test_browser_imports_guard.js && node tests/test_version_sync.js && node tests/test_native_loaded.js && node tests/test_safe_regex_source_sync.js && node tests/test_t_builder.js && node tests/test_async_refine.js && node tests/test_safe_regex.js && node tests/test_safe_regex_integration.js && node tests/test_aot_build.js && node tests/test_aot_differential.js && node tests/test_aot_cli_build.js && node tests/test_aot_cli_smoke.js && node tests/test_bundle_standalone.js && node tests/test_standalone_anyof.js && node tests/test_standalone_formats.js && node tests/test_aot_format_mode.js && node tests/test_aot_additional_props_errors.js && node tests/test_id_anchor_refs.js && node tests/test_engine_routing.js && node tests/test_engine_option.js && node tests/test_compile_cache_order.js && node tests/test_engine_diagnostic.js && node tests/test_format_engine_parity.js && node tests/test_error_accumulator_collision.js && node tests/test_uri_helper_parity.js && node tests/test_uri_fast_path.js && node tests/test_extend_verdict.js && node tests/test_ipv4_regex.js && node tests/test_ipv6_fast_path.js && node tests/test_nested_recursion_decline.js && node tests/test_generator_error_content.js && node tests/test_inherited_key_presence.js && node tests/test_formats_single_pass.js && node tests/test_email_format_cost.js && node tests/test_format_single_error.js && node tests/test_defs_pointer_alias.js && node tests/test_cross_doc_root_ref.js && node tests/test_codegen_entrypoint_agreement.js && node tests/test_hybrid_agreement.js && node tests/test_codegen_edge_shapes.js && node tests/test_deferred_def_scope.js && node tests/test_node_scoped_ctx_flags.js && node tests/test_fast_slot_exhaustion.js && node tests/test_ajv_errors.js && node tests/test_custom_keywords.js && node tests/test_strict_schema.js && node tests/test_aot_external_checks.js && node tests/test_ajv_parity.js && node tests/test_user_format_error_path.js && node tests/test_unevaluated_error_path.js && node tests/test_unevaluated_error_shape.js && node tests/test_pattern_properties_errors.js && node tests/test_additional_properties_scaling.js && node tests/test_additional_props_combined_cost.js && node tests/test_no_input_mutation.js && node tests/test_typed_validator_runner.js && node tests/test_define_schema.js && node tests/test_error_codes_lock.js && node tests/test_error_code_lookup.js && node tests/test_error_order.js && node tests/test_error_order_ordinal.js && node tests/test_rejection_shape.js && node tests/test_lazy_normalization.js && node tests/test_value_equality.js && node tests/test_vocabulary.js && node tests/test_schema_scan.js && node tests/test_lazy_errors.js && node tests/test_single_pass_errors.js && node tests/test_lazy_json_errors.js && node tests/test_lazy_instance.js && node tests/test_cyclic_input.js && node tests/test_native_error_codes.js && node tests/test_verdict_preprocess.js && node tests/test_defaults_own_keys.js && node tests/test_plan_compiler.js && node tests/test_nullable.js && node tests/test_validate_and_parse.js && node tests/test_validate_data.js && node tests/test_enrich_error.js && node tests/test_enrich_received.js && node tests/test_rich_errors_optout.js && node tests/test_error_messages.js && node tests/test_source_positions.js && node tests/fuzz_positions.js && node tests/test_malformed_json_termination.js && node tests/test_data_positions.js && node tests/test_targeted_positions.js && node tests/test_position_map_cost.js && node tests/test_targeted_positions_cost.js && node tests/test_aot_staleness.js && node tests/test_file_positions.js && node tests/test_aot_positions_cost.js && node tests/test_schema_hash.js && node tests/test_render_shared.js && node tests/test_renderers.js && node tests/test_additive_fields.js && node tests/test_diagnostic_source.js && node tests/test_diagnose.js && node tests/test_correlate.js && node tests/test_diagnostics_score.js && node tests/test_runtime_error_dx.js && node tests/test_aot_error_dx.js && node tests/test_abort_early.js && node tests/test_branch_collapse.js && node tests/test_suggestions.js && node tests/test_cli_validate.js && node tests/test_cli_version.js && node benchmark/bench_aot_size.mjs",
|
|
55
55
|
"bench:size": "node benchmark/bench_aot_size.mjs",
|
|
56
56
|
"test:suite": "node tests/run_suite.js && node tests/run_suite.js draft7 && node tests/run_suite.js v1",
|
|
57
57
|
"test:compat": "node tests/test_compat.js",
|
|
@@ -121,13 +121,13 @@
|
|
|
121
121
|
"LICENSE"
|
|
122
122
|
],
|
|
123
123
|
"optionalDependencies": {
|
|
124
|
-
"@ata-validator/native-darwin-arm64": "1.31.
|
|
125
|
-
"@ata-validator/native-darwin-x64": "1.31.
|
|
126
|
-
"@ata-validator/native-linux-arm64-gnu": "1.31.
|
|
127
|
-
"@ata-validator/native-linux-arm64-musl": "1.31.
|
|
128
|
-
"@ata-validator/native-linux-x64-gnu": "1.31.
|
|
129
|
-
"@ata-validator/native-linux-x64-musl": "1.31.
|
|
130
|
-
"@ata-validator/native-win32-x64": "1.31.
|
|
124
|
+
"@ata-validator/native-darwin-arm64": "1.31.2",
|
|
125
|
+
"@ata-validator/native-darwin-x64": "1.31.2",
|
|
126
|
+
"@ata-validator/native-linux-arm64-gnu": "1.31.2",
|
|
127
|
+
"@ata-validator/native-linux-arm64-musl": "1.31.2",
|
|
128
|
+
"@ata-validator/native-linux-x64-gnu": "1.31.2",
|
|
129
|
+
"@ata-validator/native-linux-x64-musl": "1.31.2",
|
|
130
|
+
"@ata-validator/native-win32-x64": "1.31.2"
|
|
131
131
|
},
|
|
132
132
|
"peerDependencies": {
|
|
133
133
|
"yaml": "^2.0.0"
|