ata-validator 1.56.0 → 1.58.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -2
- package/bin/ata.js +11 -2
- package/build.d.ts +7 -1
- package/lib/aot-build.js +6 -1
- package/lib/aot-impl.js +59 -11
- package/lib/aot-order.js +115 -0
- package/lib/buffer-gate.js +33 -0
- package/lib/clone-emit.js +11 -25
- package/lib/codegen-engine.js +159 -19
- package/lib/defaults.js +213 -56
- package/lib/error-codes.js +6 -0
- package/lib/formats-source.js +1 -1
- package/lib/interpreter-core.js +39 -2
- package/lib/interpreter.browser.js +34 -0
- package/lib/schema-strip.js +15 -8
- package/lib/tiering.js +88 -1
- package/lib/ts-gen.js +6 -2
- package/lib/validator-core.js +58 -30
- package/lib/version.js +1 -1
- package/package.json +10 -9
package/lib/codegen-engine.js
CHANGED
|
@@ -236,6 +236,10 @@ function installCodegenPaths (ctx) {
|
|
|
236
236
|
// The boolean engine is the verdict authority for these paths; the
|
|
237
237
|
// final lazy wrapper uses it to skip error construction entirely.
|
|
238
238
|
if (!hasDynRef || _isCodegen) this._fastVerdict = preprocess ? null : jsFn;
|
|
239
|
+
// With a preprocess pass the verdict runs it first, for the lazy layer in
|
|
240
|
+
// lib/validator-core.js, which then builds the errors only when they are
|
|
241
|
+
// read, from the document as the pass left it.
|
|
242
|
+
if ((!hasDynRef || _isCodegen) && preprocess && !fusedRemove) { ctx.preVerdict = (data) => { preprocess(data); return jsFn(data) }; ctx.rawVerdict = jsFn; }
|
|
239
243
|
|
|
240
244
|
if (options.abortEarly && jsFn && !hasDynRef) {
|
|
241
245
|
// abortEarly: do NOT enrich. Skip position lookups, suggestions, source maps.
|
|
@@ -319,7 +323,7 @@ function installCodegenPaths (ctx) {
|
|
|
319
323
|
// deciding again: the lazy layer in lib/validator-core.js has its verdict
|
|
320
324
|
// and used to call validate() to get the errors, which ran the verdict a
|
|
321
325
|
// second time and built a second rejection around it.
|
|
322
|
-
|
|
326
|
+
ctx.rejectBase = onReject;
|
|
323
327
|
// The rich combined function, for a validator whose errors are being read
|
|
324
328
|
// with richErrors on. See _ensureCompiled.
|
|
325
329
|
if (!preprocess) {
|
|
@@ -360,7 +364,7 @@ function installCodegenPaths (ctx) {
|
|
|
360
364
|
this.validate = preprocess
|
|
361
365
|
? (data) => { preprocess(data); return safeCombinedFn(data); }
|
|
362
366
|
: safeCombinedFn;
|
|
363
|
-
|
|
367
|
+
ctx.rejectBase = (data) => _mustReject(safeCombinedFn(data));
|
|
364
368
|
} else {
|
|
365
369
|
this.validate = preprocess
|
|
366
370
|
? (data) => {
|
|
@@ -368,7 +372,7 @@ function installCodegenPaths (ctx) {
|
|
|
368
372
|
return jsFn(data) ? VALID_RESULT : errOnly(data);
|
|
369
373
|
}
|
|
370
374
|
: (data) => (jsFn(data) ? VALID_RESULT : errOnly(data));
|
|
371
|
-
|
|
375
|
+
ctx.rejectBase = errOnly;
|
|
372
376
|
}
|
|
373
377
|
}
|
|
374
378
|
// Verbose mode: populate parentSchema, schema and data on each error, the
|
|
@@ -803,18 +807,88 @@ function emitCoercions(node, ov, lines, st, depth) {
|
|
|
803
807
|
}
|
|
804
808
|
st.seen.delete(node);
|
|
805
809
|
}
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
810
|
+
// From the closure pass, on the first reference a defaults walk meets: loaded
|
|
811
|
+
// with this module it joined every process's cold start.
|
|
812
|
+
let _localRef = null;
|
|
813
|
+
const localRef = (root, ref) => (_localRef || (_localRef = require('./defaults').localRef))(root, ref);
|
|
814
|
+
// Whether a default is reachable from a schema through the places the pass
|
|
815
|
+
// goes (lib/defaults.js reachesDefault, which this pass is held to): the
|
|
816
|
+
// same walk, with answers kept per schema object for the whole pass, except
|
|
817
|
+
// one that rests on a schema still being decided (a recursive reference). It
|
|
818
|
+
// started over for every property, quadratic in the depth of the schema.
|
|
819
|
+
function reachState(root) { return { root, memo: new Map() }; }
|
|
820
|
+
function hasDefaultsInside(node, rs) { return reachDefault(node, rs, new Set()) === 1; }
|
|
821
|
+
function reachDefault(node, rs, stack) {
|
|
822
|
+
if (!node || typeof node !== 'object') return 0;
|
|
823
|
+
const known = rs.memo.get(node);
|
|
824
|
+
if (known !== undefined) return known;
|
|
825
|
+
if (stack.has(node)) return 2;
|
|
826
|
+
stack.add(node);
|
|
827
|
+
let open = false;
|
|
828
|
+
const check = (n) => { const r = reachDefault(n, rs, stack); if (r === 2) open = true; return r === 1; };
|
|
829
|
+
let yes = false;
|
|
830
|
+
if (node.properties && typeof node.properties === 'object') {
|
|
831
|
+
for (const prop of Object.values(node.properties)) {
|
|
832
|
+
if (prop && typeof prop === 'object' && (prop.default !== undefined || check(prop))) { yes = true; break; }
|
|
833
|
+
}
|
|
812
834
|
}
|
|
813
|
-
|
|
814
|
-
|
|
835
|
+
if (!yes && typeof node.$ref === 'string') yes = check(localRef(rs.root, node.$ref));
|
|
836
|
+
if (!yes) for (const list of [node.allOf, node.prefixItems]) { if (Array.isArray(list) && list.some(check)) { yes = true; break; } }
|
|
837
|
+
if (!yes) for (const one of [node.items, node.additionalProperties]) { if (check(one)) { yes = true; break; } }
|
|
838
|
+
if (!yes) for (const map of [node.patternProperties, node.dependentSchemas]) { if (map && typeof map === 'object' && Object.keys(map).some((k) => check(map[k]))) { yes = true; break; } }
|
|
839
|
+
stack.delete(node);
|
|
840
|
+
if (yes) { rs.memo.set(node, 1); return 1; }
|
|
841
|
+
if (!open) { rs.memo.set(node, 0); return 0; }
|
|
842
|
+
return 2;
|
|
843
|
+
}
|
|
844
|
+
// Which keys `additionalProperties` covers depends on the patterns of
|
|
845
|
+
// `patternProperties`, which this pass does not test (a RegExp built on
|
|
846
|
+
// every call would cost more than the pass): a schema whose defaults depend
|
|
847
|
+
// on one is left to the closure pass.
|
|
848
|
+
function defaultsNeedPatterns(node, rs) {
|
|
849
|
+
const pp = node.patternProperties;
|
|
850
|
+
if (!pp || typeof pp !== 'object' || Object.keys(pp).length === 0) return false;
|
|
851
|
+
for (const k of Object.keys(pp)) if (hasDefaultsInside(pp[k], rs)) return true;
|
|
852
|
+
return hasDefaultsInside(node.additionalProperties, rs);
|
|
853
|
+
}
|
|
854
|
+
// The defaults of `schema` filled into the value of `expr`, when it holds an
|
|
855
|
+
// object (an array included), as the closure pass fills them.
|
|
856
|
+
function emitDefaultsAt(schema, expr, lines, st) {
|
|
857
|
+
const n = '_d' + st.n++;
|
|
858
|
+
lines.push(`{const ${n}=${expr};if(typeof ${n}==='object'&&${n}!==null){`);
|
|
859
|
+
emitDefaults(schema, n, lines, st);
|
|
860
|
+
lines.push('}}');
|
|
861
|
+
}
|
|
862
|
+
// A pass that would expand to more than this many lines (references written
|
|
863
|
+
// out again at every use) is left to the closure pass.
|
|
864
|
+
const DEFAULTS_MAX_LINES = 4000;
|
|
865
|
+
// How many schema objects emitDefaults would write out, references expanded
|
|
866
|
+
// at every use, or Infinity where it would decline (a cycle, or defaults
|
|
867
|
+
// under patternProperties). Each writes at least one line, so a count over
|
|
868
|
+
// DEFAULTS_MAX_LINES declines where emitDefaults would, before writing
|
|
869
|
+
// anything: SARIF's schema wrote 4000 lines in 2.7 ms and threw them away.
|
|
870
|
+
function defaultsVisits(node, st, stack) {
|
|
871
|
+
const known = st.visits.get(node);
|
|
872
|
+
if (known !== undefined) return known;
|
|
873
|
+
if (stack.has(node)) return Infinity;
|
|
874
|
+
stack.add(node);
|
|
875
|
+
let n = 1;
|
|
876
|
+
const add = (x) => { if (n <= DEFAULTS_MAX_LINES && hasDefaultsInside(x, st.reach)) n += defaultsVisits(x, st, stack); };
|
|
877
|
+
if (defaultsNeedPatterns(node, st.reach)) n = Infinity;
|
|
878
|
+
for (const prop of Object.values(node.properties || {})) if (prop && typeof prop === 'object') add(prop);
|
|
879
|
+
if (Array.isArray(node.prefixItems)) node.prefixItems.forEach(add);
|
|
880
|
+
add(node.items);
|
|
881
|
+
add(node.additionalProperties);
|
|
882
|
+
if (node.dependentSchemas && typeof node.dependentSchemas === 'object') Object.values(node.dependentSchemas).forEach(add);
|
|
883
|
+
if (typeof node.$ref === 'string') add(localRef(st.root, node.$ref));
|
|
884
|
+
if (Array.isArray(node.allOf)) node.allOf.forEach(add);
|
|
885
|
+
stack.delete(node);
|
|
886
|
+
if (n > DEFAULTS_MAX_LINES) n = Infinity;
|
|
887
|
+
st.visits.set(node, n);
|
|
888
|
+
return n;
|
|
815
889
|
}
|
|
816
890
|
function emitDefaults(node, ov, lines, st) {
|
|
817
|
-
if (st.seen.has(node)) { st.cycle = true; return; }
|
|
891
|
+
if (st.seen.has(node) || lines.length > DEFAULTS_MAX_LINES) { st.cycle = true; return; }
|
|
818
892
|
st.seen.add(node);
|
|
819
893
|
// A default is written only where the key is not an own property, and the
|
|
820
894
|
// own-property test used to be Object.hasOwn for every default on every
|
|
@@ -846,7 +920,7 @@ function emitDefaults(node, ov, lines, st) {
|
|
|
846
920
|
// closure pass walks it. Object.prototype's own properties are functions
|
|
847
921
|
// apart from the __proto__ accessor, so on a plain object a read that
|
|
848
922
|
// gives an object is an own property for every other key.
|
|
849
|
-
if (
|
|
923
|
+
if (hasDefaultsInside(prop, st.reach)) {
|
|
850
924
|
const n = '_d' + st.n++;
|
|
851
925
|
const own = inherited ? `Object.hasOwn(${ov},${k})` : `(${pk}||Object.hasOwn(${ov},${k}))`;
|
|
852
926
|
lines.push(`{const ${n}=${ov}[${k}];if(typeof ${n}==='object'&&${n}!==null&&${own}){`);
|
|
@@ -854,12 +928,68 @@ function emitDefaults(node, ov, lines, st) {
|
|
|
854
928
|
lines.push('}}');
|
|
855
929
|
}
|
|
856
930
|
}
|
|
931
|
+
if (defaultsNeedPatterns(node, st.reach)) { st.cycle = true; st.seen.delete(node); return; }
|
|
932
|
+
// Elements of an array: `prefixItems` by position, `items` after them.
|
|
933
|
+
const has = (x) => hasDefaultsInside(x, st.reach);
|
|
934
|
+
const tuple = Array.isArray(node.prefixItems) ? node.prefixItems : [];
|
|
935
|
+
const tupleHas = tuple.map(has);
|
|
936
|
+
const itemsHas = has(node.items);
|
|
937
|
+
if (tupleHas.some(Boolean) || itemsHas) {
|
|
938
|
+
lines.push(`if(Array.isArray(${ov})){`);
|
|
939
|
+
for (let i = 0; i < tuple.length; i++) {
|
|
940
|
+
if (!tupleHas[i]) continue;
|
|
941
|
+
lines.push(`if(${ov}.length>${i})`);
|
|
942
|
+
emitDefaultsAt(tuple[i], `${ov}[${i}]`, lines, st);
|
|
943
|
+
}
|
|
944
|
+
if (itemsHas) {
|
|
945
|
+
const i = '_i' + st.n++;
|
|
946
|
+
lines.push(`for(let ${i}=${tuple.length};${i}<${ov}.length;${i}++)`);
|
|
947
|
+
emitDefaultsAt(node.items, `${ov}[${i}]`, lines, st);
|
|
948
|
+
}
|
|
949
|
+
lines.push('}');
|
|
950
|
+
}
|
|
951
|
+
// Keys of an object that `properties` does not name (no patterns here,
|
|
952
|
+
// see defaultsNeedPatterns).
|
|
953
|
+
if (has(node.additionalProperties)) {
|
|
954
|
+
const k = '_k' + st.n++;
|
|
955
|
+
const declared = Object.keys(node.properties || {});
|
|
956
|
+
lines.push(`if(!Array.isArray(${ov}))for(const ${k} of Object.keys(${ov})){`);
|
|
957
|
+
if (declared.length) lines.push(`if(${declared.map((d) => `${k}===${JSON.stringify(d)}`).join('||')})continue;`);
|
|
958
|
+
emitDefaultsAt(node.additionalProperties, `${ov}[${k}]`, lines, st);
|
|
959
|
+
lines.push('}');
|
|
960
|
+
}
|
|
961
|
+
// A schema for the same object, for each key present.
|
|
962
|
+
if (node.dependentSchemas && typeof node.dependentSchemas === 'object') {
|
|
963
|
+
for (const key of Object.keys(node.dependentSchemas)) {
|
|
964
|
+
const dep = node.dependentSchemas[key];
|
|
965
|
+
if (!has(dep)) continue;
|
|
966
|
+
lines.push(`if(!Array.isArray(${ov})&&Object.hasOwn(${ov},${JSON.stringify(key)})){`);
|
|
967
|
+
emitDefaults(dep, ov, lines, st);
|
|
968
|
+
lines.push('}');
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
// The schemas applied to the same value: a `$ref` into the same document,
|
|
972
|
+
// written out here (one back into what is being written is a cycle, left
|
|
973
|
+
// to the closure pass), then each `allOf` member, in the closure pass's order.
|
|
974
|
+
if (typeof node.$ref === 'string') {
|
|
975
|
+
const target = localRef(st.root, node.$ref);
|
|
976
|
+
if (target && hasDefaultsInside(target, st.reach)) emitDefaults(target, ov, lines, st);
|
|
977
|
+
}
|
|
978
|
+
if (Array.isArray(node.allOf)) {
|
|
979
|
+
for (const sub of node.allOf) if (hasDefaultsInside(sub, st.reach)) emitDefaults(sub, ov, lines, st);
|
|
980
|
+
}
|
|
857
981
|
st.seen.delete(node);
|
|
858
982
|
}
|
|
859
983
|
|
|
860
|
-
//
|
|
861
|
-
|
|
862
|
-
|
|
984
|
+
// The source of the preprocess pass, or null when there is nothing to do or
|
|
985
|
+
// the closure passes must take the schema. A module written ahead of time
|
|
986
|
+
// embeds it as applyDefaults (lib/aot-impl.js), so it runs the runtime's code.
|
|
987
|
+
function preprocessSource(schema, options) {
|
|
988
|
+
if (typeof schema !== 'object' || schema === null) return null;
|
|
989
|
+
// Defaults reach through `$ref` and `allOf` from the root too; the other
|
|
990
|
+
// passes start at `properties`.
|
|
991
|
+
const reach = reachState(schema);
|
|
992
|
+
if (!schema.properties && !(options.useDefaults !== false && hasDefaultsInside(schema, reach))) return null;
|
|
863
993
|
const lines = [];
|
|
864
994
|
|
|
865
995
|
// removeAdditional: strip unknown keys at every level the schema describes,
|
|
@@ -878,24 +1008,33 @@ function buildPreprocessCodegen(schema, options) {
|
|
|
878
1008
|
// used to stop at the top-level properties. A schema object that contains
|
|
879
1009
|
// itself declines here, and the closure passes, which carry a guard for it,
|
|
880
1010
|
// take the schema.
|
|
881
|
-
const st = { n: 0, seen: new Set(), cycle: false, arrayMode: options.coerceTypes === 'array', memo: new Map() };
|
|
1011
|
+
const st = { n: 0, seen: new Set(), cycle: false, arrayMode: options.coerceTypes === 'array', memo: new Map(), root: schema, reach, visits: new Map() };
|
|
882
1012
|
if (options.coerceTypes) emitCoercions(schema, 'd', lines, st, 0);
|
|
883
1013
|
// hasDefaultsInside answers without emitting; most schemas have no default,
|
|
884
1014
|
// and the emitting walk allocates for every property it visits.
|
|
885
|
-
if (options.useDefaults !== false && hasDefaultsInside(schema,
|
|
1015
|
+
if (options.useDefaults !== false && hasDefaultsInside(schema, reach)) {
|
|
1016
|
+
if (defaultsVisits(schema, st, new Set()) > DEFAULTS_MAX_LINES) return null;
|
|
1017
|
+
emitDefaults(schema, 'd', lines, st);
|
|
1018
|
+
}
|
|
886
1019
|
if (st.cycle) return null;
|
|
887
1020
|
|
|
888
1021
|
if (lines.length === 0) return null;
|
|
889
1022
|
// Data may legitimately be null or a non-object (e.g. a `['object','null']`
|
|
890
1023
|
// schema), so the per-property mutations must not run on it.
|
|
891
1024
|
lines.unshift(`if(d===null||typeof d!=='object')return`);
|
|
1025
|
+
return lines.join('\n');
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
// Generate a fast preprocess function via codegen instead of closure arrays
|
|
1029
|
+
function buildPreprocessCodegen(schema, options) {
|
|
1030
|
+
const src = preprocessSource(schema, options);
|
|
1031
|
+
if (src === null) return null;
|
|
892
1032
|
// The pass depends on property names, types, defaults and which objects
|
|
893
1033
|
// are closed, not on the constraints the verdict checks, so routes that
|
|
894
1034
|
// take the same shape with different limits (a page/limit querystring, an
|
|
895
1035
|
// id param) emit the same source. Compiling it is most of what building the
|
|
896
1036
|
// pass costs, so the function is kept by its source and compiled once. It
|
|
897
1037
|
// holds no state: it rewrites the object it is given and nothing else.
|
|
898
|
-
const src = lines.join('\n');
|
|
899
1038
|
let fn = _preprocessBySource.get(src);
|
|
900
1039
|
if (fn === undefined) {
|
|
901
1040
|
try {
|
|
@@ -1020,6 +1159,7 @@ module.exports = {
|
|
|
1020
1159
|
compileVerdict: installCodegenPaths.compileVerdict,
|
|
1021
1160
|
installScanner: installCodegenPaths.installScanner,
|
|
1022
1161
|
buildPreprocess: buildPreprocessCodegen,
|
|
1162
|
+
preprocessSource,
|
|
1023
1163
|
buildParse,
|
|
1024
1164
|
aot: () => require('./aot.js'),
|
|
1025
1165
|
};
|
package/lib/defaults.js
CHANGED
|
@@ -2,19 +2,32 @@
|
|
|
2
2
|
|
|
3
3
|
// The closure pass that fills `default` values, shared by the validator core
|
|
4
4
|
// and by the wrapper around a compiled module, so both fill defaults the same
|
|
5
|
-
// way. The generated pass in
|
|
5
|
+
// way. The generated pass in lib/codegen-engine.js is held to this one by
|
|
6
6
|
// tests/test_nested_defaults_engines.js.
|
|
7
|
+
//
|
|
8
|
+
// A default is filled in every schema applied to the value at that place, as
|
|
9
|
+
// ajv's `useDefaults` fills one: the schemas under `properties`, through a
|
|
10
|
+
// `$ref` into the same document (recursive ones included), each `allOf`
|
|
11
|
+
// member, `items` for every element and `prefixItems` by position,
|
|
12
|
+
// `patternProperties` for the keys they match, `additionalProperties` for the
|
|
13
|
+
// rest, and `dependentSchemas` for the keys present. The pass stopped at
|
|
14
|
+
// `properties`, so a default behind a reference or in an array element was
|
|
15
|
+
// not filled, and a `required` property with one was rejected where ajv
|
|
16
|
+
// accepts it. `anyOf`, `oneOf` and `not` fill nothing, as with ajv;
|
|
17
|
+
// `if`/`then`/`else` fill nothing here, since which branch applies is only
|
|
18
|
+
// known once the value has been checked.
|
|
7
19
|
|
|
8
|
-
//
|
|
9
|
-
//
|
|
20
|
+
// Below a value nested this deep the pass stops: data that contains itself,
|
|
21
|
+
// under a recursive schema, would otherwise never end.
|
|
22
|
+
const MAX_DEPTH = 512;
|
|
23
|
+
|
|
24
|
+
// Build the pass for a schema: a function that fills defaults into a value in
|
|
25
|
+
// place (mutates), or null when no default is reachable from the schema.
|
|
10
26
|
function buildDefaultsApplier(schema) {
|
|
11
27
|
if (typeof schema !== "object" || schema === null) return null;
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
return (data) => {
|
|
16
|
-
for (let i = 0; i < actions.length; i++) actions[i](data);
|
|
17
|
-
};
|
|
28
|
+
const path = [];
|
|
29
|
+
const fn = compileNode(schema, schema, new Map(), new Map(), path);
|
|
30
|
+
return fn === null ? null : (data) => { path.length = 0; fn(data, 0, null); };
|
|
18
31
|
}
|
|
19
32
|
|
|
20
33
|
// Write an own property. Plain assignment of a key named `__proto__` does
|
|
@@ -34,55 +47,199 @@ function setOwn(obj, key, val) {
|
|
|
34
47
|
}
|
|
35
48
|
}
|
|
36
49
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
50
|
+
// The schema a `$ref` names in the same document: `#`, or a JSON pointer
|
|
51
|
+
// after it. Null for anything else (another document, an anchor), which the
|
|
52
|
+
// pass leaves alone. Normalization renames draft-07 `definitions` to `$defs`
|
|
53
|
+
// and leaves the pointers as written, so either name reads as the other.
|
|
54
|
+
function localRef(root, ref) {
|
|
55
|
+
if (typeof ref !== "string" || ref.charCodeAt(0) !== 35) return null;
|
|
56
|
+
if (ref === "#") return root;
|
|
57
|
+
if (ref.charCodeAt(1) !== 47) return null;
|
|
58
|
+
let t = root;
|
|
59
|
+
for (let seg of ref.slice(2).split("/")) {
|
|
60
|
+
if (seg.indexOf("%") >= 0) { try { seg = decodeURIComponent(seg); } catch { return null; } }
|
|
61
|
+
if (seg.indexOf("~") >= 0) seg = seg.replace(/~1/g, "/").replace(/~0/g, "~");
|
|
62
|
+
if (t === null || typeof t !== "object") return null;
|
|
63
|
+
if (!Object.hasOwn(t, seg)) {
|
|
64
|
+
const alias = seg === "definitions" ? "$defs" : seg === "$defs" ? "definitions" : null;
|
|
65
|
+
if (alias === null || !Object.hasOwn(t, alias)) return null;
|
|
66
|
+
seg = alias;
|
|
67
|
+
}
|
|
68
|
+
t = t[seg];
|
|
69
|
+
}
|
|
70
|
+
return t !== null && typeof t === "object" ? t : null;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Whether a default is reachable from a schema, through the same places the
|
|
74
|
+
// pass goes: 1 yes, 0 no, 2 not known yet because the walk came back to a
|
|
75
|
+
// schema it is still deciding (a recursive reference). Answers are kept per
|
|
76
|
+
// schema object in `memo`, except one that rests on an undecided schema,
|
|
77
|
+
// which is worked out again when asked; the walk used to start over for every
|
|
78
|
+
// schema object it compiled, quadratic in the depth of the schema.
|
|
79
|
+
function reachesDefault(node, root, memo, stack) {
|
|
80
|
+
if (node === null || typeof node !== "object") return 0;
|
|
81
|
+
const known = memo.get(node);
|
|
82
|
+
if (known !== undefined) return known;
|
|
83
|
+
if (stack.has(node)) return 2;
|
|
84
|
+
stack.add(node);
|
|
85
|
+
const props = valuesOf(node.properties);
|
|
86
|
+
let yes = props.some((p) => p && typeof p === "object" && p.default !== undefined);
|
|
87
|
+
let open = false;
|
|
88
|
+
const below = props.concat(localRef(root, node.$ref), listOf(node.allOf), listOf(node.prefixItems), node.items, node.additionalProperties, valuesOf(node.patternProperties), valuesOf(node.dependentSchemas));
|
|
89
|
+
for (let i = 0; !yes && i < below.length; i++) {
|
|
90
|
+
const r = reachesDefault(below[i], root, memo, stack);
|
|
91
|
+
if (r === 1) yes = true;
|
|
92
|
+
else if (r === 2) open = true;
|
|
93
|
+
}
|
|
94
|
+
stack.delete(node);
|
|
95
|
+
if (yes || !open) memo.set(node, yes ? 1 : 0);
|
|
96
|
+
return yes ? 1 : open ? 2 : 0;
|
|
97
|
+
}
|
|
98
|
+
function valuesOf(m) {
|
|
99
|
+
return m && typeof m === "object" && !Array.isArray(m) ? Object.values(m) : [];
|
|
100
|
+
}
|
|
101
|
+
function listOf(a) {
|
|
102
|
+
return Array.isArray(a) ? a : [];
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// A pattern of `patternProperties` as the validator reads one.
|
|
106
|
+
function keyPattern(src) {
|
|
107
|
+
try { return new RegExp(src, "u"); } catch { try { return new RegExp(src); } catch { return null; } }
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// A fresh copy of a default for each value it fills. `[]`, the most common
|
|
111
|
+
// object default (2916 of the 4130 SARIF's schema fills into one log), skips
|
|
112
|
+
// the JSON round trip.
|
|
113
|
+
function copyOf(v) {
|
|
114
|
+
return typeof v !== "object" || v === null ? v : Array.isArray(v) && v.length === 0 ? [] : JSON.parse(JSON.stringify(v));
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// The pass for one schema object, built once: its own defaults, then the
|
|
118
|
+
// schemas below its properties, then its `$ref` and its `allOf` members, each
|
|
119
|
+
// applied to the same value. A schema reached again (a recursive reference)
|
|
120
|
+
// takes the function being built for it, so a cycle in the schema is a loop
|
|
121
|
+
// in the calls, bounded by the depth of the data.
|
|
122
|
+
function compileNode(node, root, memo, reach, path) {
|
|
123
|
+
if (node === null || typeof node !== "object") return null;
|
|
124
|
+
if (memo.has(node)) return memo.get(node);
|
|
125
|
+
if (reachesDefault(node, root, reach, new Set()) !== 1) { memo.set(node, null); return null; }
|
|
126
|
+
let body = null;
|
|
127
|
+
// `path`: the schemas applied to the enclosing values, outermost first.
|
|
128
|
+
// `made`: inside a value the pass itself made from a default, the same as a
|
|
129
|
+
// set, and null elsewhere. There a schema already applied to an enclosing
|
|
130
|
+
// value is not applied again: one that refers back to an enclosing schema,
|
|
131
|
+
// with an object default, would otherwise make a value, fill it, make the
|
|
132
|
+
// next one inside it, and never stop. ajv throws a RangeError on such a
|
|
133
|
+
// schema, and Renovate's took this pass to the heap limit.
|
|
134
|
+
const fn = (data, depth, made) => {
|
|
135
|
+
if (depth > MAX_DEPTH) return;
|
|
136
|
+
if (made !== null) {
|
|
137
|
+
if (made.has(node)) return;
|
|
138
|
+
made.add(node);
|
|
139
|
+
body(data, depth, made);
|
|
140
|
+
made.delete(node);
|
|
141
|
+
return;
|
|
80
142
|
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
143
|
+
path.push(node);
|
|
144
|
+
body(data, depth, null);
|
|
145
|
+
path.pop();
|
|
146
|
+
};
|
|
147
|
+
memo.set(node, fn);
|
|
148
|
+
|
|
149
|
+
const own = [];
|
|
150
|
+
const below = [];
|
|
151
|
+
const props = node.properties;
|
|
152
|
+
if (props && typeof props === "object") {
|
|
153
|
+
for (const key of Object.keys(props)) {
|
|
154
|
+
const p = props[key];
|
|
155
|
+
if (!p || typeof p !== "object") continue;
|
|
156
|
+
if (p.default !== undefined) own.push([key, p.default]);
|
|
157
|
+
const child = compileNode(p, root, memo, reach, path);
|
|
158
|
+
if (child !== null) below.push([key, child]);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
// Arrays: every element under `items`, the elements `prefixItems` names by
|
|
162
|
+
// position (then `items` covers only the ones after them).
|
|
163
|
+
const tuple = Array.isArray(node.prefixItems) ? node.prefixItems.map((s) => compileNode(s, root, memo, reach, path)) : [];
|
|
164
|
+
const each = compileNode(node.items, root, memo, reach, path);
|
|
165
|
+
// Objects: keys a pattern matches, and the keys neither `properties` nor a
|
|
166
|
+
// pattern names; and a schema for each key present.
|
|
167
|
+
const patterns = [];
|
|
168
|
+
if (node.patternProperties && typeof node.patternProperties === "object") {
|
|
169
|
+
for (const src of Object.keys(node.patternProperties)) {
|
|
170
|
+
const re = keyPattern(src);
|
|
171
|
+
const t = compileNode(node.patternProperties[src], root, memo, reach, path);
|
|
172
|
+
if (re === null) continue;
|
|
173
|
+
patterns.push([re, t]);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
const extra = compileNode(node.additionalProperties, root, memo, reach, path);
|
|
177
|
+
const declared = props && typeof props === "object" ? props : null;
|
|
178
|
+
const dependent = [];
|
|
179
|
+
if (node.dependentSchemas && typeof node.dependentSchemas === "object") {
|
|
180
|
+
for (const key of Object.keys(node.dependentSchemas)) {
|
|
181
|
+
const t = compileNode(node.dependentSchemas[key], root, memo, reach, path);
|
|
182
|
+
if (t !== null) dependent.push([key, t]);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
const keyed = extra !== null || patterns.some((p) => p[1] !== null);
|
|
186
|
+
|
|
187
|
+
const also = [];
|
|
188
|
+
if (typeof node.$ref === "string") {
|
|
189
|
+
const t = compileNode(localRef(root, node.$ref), root, memo, reach, path);
|
|
190
|
+
if (t !== null) also.push(t);
|
|
191
|
+
}
|
|
192
|
+
if (Array.isArray(node.allOf)) {
|
|
193
|
+
for (const s of node.allOf) {
|
|
194
|
+
const t = compileNode(s, root, memo, reach, path);
|
|
195
|
+
if (t !== null) also.push(t);
|
|
84
196
|
}
|
|
85
197
|
}
|
|
198
|
+
|
|
199
|
+
body = (data, depth, made) => {
|
|
200
|
+
let fresh = null;
|
|
201
|
+
if (typeof data === "object" && data !== null) {
|
|
202
|
+
for (let i = 0; i < own.length; i++) {
|
|
203
|
+
const key = own[i][0];
|
|
204
|
+
if (!Object.hasOwn(data, key)) {
|
|
205
|
+
const v = copyOf(own[i][1]);
|
|
206
|
+
setOwn(data, key, v);
|
|
207
|
+
if (made === null && typeof v === "object" && v !== null) (fresh || (fresh = [])).push(key);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
for (let i = 0; i < below.length; i++) {
|
|
211
|
+
// Own keys only. `data[key]` for an inherited name walks the
|
|
212
|
+
// prototype chain: a property named `__proto__` that the value does
|
|
213
|
+
// not carry resolved to Object.prototype, and the defaults below it
|
|
214
|
+
// were written onto it, for every object in the realm.
|
|
215
|
+
const key = below[i][0];
|
|
216
|
+
if (Object.hasOwn(data, key)) below[i][1](data[key], depth + 1, fresh !== null && fresh.includes(key) ? new Set(path) : made);
|
|
217
|
+
}
|
|
218
|
+
if (Array.isArray(data)) {
|
|
219
|
+
for (let i = 0; i < data.length; i++) {
|
|
220
|
+
const t = i < tuple.length ? tuple[i] : each;
|
|
221
|
+
if (t !== null && t !== undefined) t(data[i], depth + 1, made);
|
|
222
|
+
}
|
|
223
|
+
} else if (keyed) {
|
|
224
|
+
for (const key of Object.keys(data)) {
|
|
225
|
+
let matched = false;
|
|
226
|
+
for (let i = 0; i < patterns.length; i++) {
|
|
227
|
+
if (patterns[i][0].test(key)) { matched = true; if (patterns[i][1] !== null) patterns[i][1](data[key], depth + 1, made); }
|
|
228
|
+
}
|
|
229
|
+
if (extra !== null && !matched && !(declared !== null && Object.hasOwn(declared, key))) extra(data[key], depth + 1, made);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
if (!Array.isArray(data)) {
|
|
233
|
+
for (let i = 0; i < dependent.length; i++) if (Object.hasOwn(data, dependent[i][0])) dependent[i][1](data, depth + 1, made);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
for (let i = 0; i < also.length; i++) also[i](data, depth + 1, made);
|
|
237
|
+
};
|
|
238
|
+
return fn;
|
|
86
239
|
}
|
|
87
240
|
|
|
88
|
-
module
|
|
241
|
+
// The functions a module written ahead of time embeds as its applyDefaults
|
|
242
|
+
// where the generated pass declines a schema (lib/aot-impl.js).
|
|
243
|
+
const EMBED = ['buildDefaultsApplier', 'setOwn', 'localRef', 'reachesDefault', 'valuesOf', 'listOf', 'keyPattern', 'copyOf', 'compileNode'];
|
|
244
|
+
|
|
245
|
+
module.exports = { buildDefaultsApplier, setOwn, localRef, MAX_DEPTH, EMBED };
|
package/lib/error-codes.js
CHANGED
|
@@ -136,6 +136,7 @@ const NATIVE_KEYWORDS = [
|
|
|
136
136
|
'not', // 25 not_failed
|
|
137
137
|
'$ref', // 26 ref_not_found
|
|
138
138
|
'if', // 27 if_then_else_failed
|
|
139
|
+
'__depth__', // 28 depth_exceeded
|
|
139
140
|
]
|
|
140
141
|
|
|
141
142
|
// Keyword and public code for a native ordinal, or null when the ordinal is
|
|
@@ -147,6 +148,11 @@ function fromNative (ordinal, format) {
|
|
|
147
148
|
// codeFor('format') answers with the first format code, which names a
|
|
148
149
|
// specific format. Without knowing which format failed, the generic one is
|
|
149
150
|
// the honest answer.
|
|
151
|
+
// The walker stopped at its depth limit and rejected the document. No
|
|
152
|
+
// public code names that: the APIs that ask the walker route schemas deep or
|
|
153
|
+
// recursive enough to reach it to validate() first (lib/buffer-gate.js), so
|
|
154
|
+
// it is the generic failure.
|
|
155
|
+
if (keyword === '__depth__') return { keyword, code: 'ATA9000' }
|
|
150
156
|
const code = keyword === 'format'
|
|
151
157
|
? (format ? codeFor('format', format) || 'ATA3099' : 'ATA3099')
|
|
152
158
|
: codeFor(keyword) || 'ATA9001'
|
package/lib/formats-source.js
CHANGED
|
@@ -304,4 +304,4 @@ function durationSource (v, isStr) {
|
|
|
304
304
|
const inner = `if(!${_formats().DURATION_RE.toString()}.test(${v}))return false`;
|
|
305
305
|
return isStr ? inner : `if(typeof ${v}==='string'&&${inner.slice(3)}`;
|
|
306
306
|
}
|
|
307
|
-
module.exports = { uriHelperSource, uriHelperCompactSource, uriSource, uriReferenceSource, iriSource, iriReferenceSource, idnEmailSource, dateSource, ipv4Source, dateTimeSource, ipv6Source, hostnameSource,
|
|
307
|
+
module.exports = { uriHelperSource, uriHelperCompactSource, uriSource, uriReferenceSource, iriSource, iriReferenceSource, idnEmailSource, dateSource, ipv4Source, dateTimeSource, ipv6Source, hostnameSource, uuidSource, timeSource, noReservedSource, jsonPointerSource, relativeJsonPointerSource, uriTemplateSource, emailSource, durationSource };
|