ata-validator 1.30.3 → 1.31.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/index.js +322 -48
- package/lib/formats.js +36 -4
- package/lib/js-compiler.js +122 -36
- package/lib/t.js +38 -18
- package/lib/version.js +1 -1
- package/package.json +9 -9
package/index.js
CHANGED
|
@@ -694,17 +694,31 @@ function sortErrorsBySchemaOrder(rootSchema, errors) {
|
|
|
694
694
|
|
|
695
695
|
// Paths repeat across rejections of the same shape, so the parsed segment
|
|
696
696
|
// list is cached per path string. Entries are frozen: the same array is
|
|
697
|
-
// handed to every issue that names the path.
|
|
698
|
-
//
|
|
699
|
-
|
|
700
|
-
|
|
697
|
+
// handed to every issue that names the path.
|
|
698
|
+
//
|
|
699
|
+
// The cache is bucketed by length and searched with ===, not keyed in a Map.
|
|
700
|
+
// A path with an array index in it is concatenated afresh by every rejection,
|
|
701
|
+
// and a Map has to hash each such string from scratch before it can look
|
|
702
|
+
// anything up; that hashing was 30% of a Standard Schema rejection with
|
|
703
|
+
// sixteen issues. Comparing against the few paths of the same length costs
|
|
704
|
+
// less. Both levels are bounded so a stream of array indexes cannot grow it
|
|
705
|
+
// without limit: a full bucket stops caching, too many lengths clear it.
|
|
706
|
+
const _pathBuckets = new Map();
|
|
707
|
+
const PATH_BUCKET_MAX = 32;
|
|
708
|
+
const PATH_LENGTHS_MAX = 256;
|
|
701
709
|
function parsePointerPath(path) {
|
|
702
710
|
if (!path) return EMPTY_PATH;
|
|
703
|
-
const
|
|
704
|
-
|
|
711
|
+
const n = path.length;
|
|
712
|
+
let bucket = _pathBuckets.get(n);
|
|
713
|
+
if (bucket !== undefined) {
|
|
714
|
+
for (let i = 0; i < bucket.length; i += 2) if (bucket[i] === path) return bucket[i + 1];
|
|
715
|
+
} else {
|
|
716
|
+
if (_pathBuckets.size >= PATH_LENGTHS_MAX) _pathBuckets.clear();
|
|
717
|
+
bucket = [];
|
|
718
|
+
_pathBuckets.set(n, bucket);
|
|
719
|
+
}
|
|
705
720
|
const segs = Object.freeze(parsePointerPathUncached(path));
|
|
706
|
-
if (
|
|
707
|
-
_pathCache.set(path, segs);
|
|
721
|
+
if (bucket.length < PATH_BUCKET_MAX * 2) bucket.push(path, segs);
|
|
708
722
|
return segs;
|
|
709
723
|
}
|
|
710
724
|
const EMPTY_PATH = Object.freeze([]);
|
|
@@ -1109,6 +1123,11 @@ class Validator {
|
|
|
1109
1123
|
this._lastRawInput = null;
|
|
1110
1124
|
// undefined: not built yet. null: this schema has no scanner.
|
|
1111
1125
|
this._scanner = undefined;
|
|
1126
|
+
// Checks a wrapper registered through _extendChecks. Declared here so
|
|
1127
|
+
// registering one does not change the instance's shape.
|
|
1128
|
+
this._verdictTail = null;
|
|
1129
|
+
this._validateTail = null;
|
|
1130
|
+
this._entryExt = null;
|
|
1112
1131
|
|
|
1113
1132
|
// Public methods start as memoized accessors on the prototype; nothing is
|
|
1114
1133
|
// allocated per instance until one is first read. See _defineLazyMethod
|
|
@@ -1117,10 +1136,12 @@ class Validator {
|
|
|
1117
1136
|
// "~standard" (Standard Schema V1) is a lazy prototype accessor too;
|
|
1118
1137
|
// see below the class. Consumers only pay for it if they read it.
|
|
1119
1138
|
|
|
1120
|
-
//
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1139
|
+
// The identity cache, which lets a later `new Validator(sameSchema)` return
|
|
1140
|
+
// this instance, is filled on the first compile rather than here. A WeakMap
|
|
1141
|
+
// entry is an ephemeron the collector has to trace separately, and setting
|
|
1142
|
+
// one cost about 780 ns against 150 for the rest of this constructor, five
|
|
1143
|
+
// times over for an instance that may never validate anything. Once an
|
|
1144
|
+
// instance has compiled, the shortcut behaves as before.
|
|
1124
1145
|
}
|
|
1125
1146
|
|
|
1126
1147
|
// `$vocabulary` says which keywords the dialect has, and answering needs the
|
|
@@ -1385,7 +1406,7 @@ class Validator {
|
|
|
1385
1406
|
return this._compiled.validate(x);
|
|
1386
1407
|
});
|
|
1387
1408
|
}
|
|
1388
|
-
return _errOnlyImpl(d);
|
|
1409
|
+
return _mustReject(_errOnlyImpl(d));
|
|
1389
1410
|
};
|
|
1390
1411
|
|
|
1391
1412
|
// Best path: combined validator (single pass, validates + collects errors)
|
|
@@ -1429,7 +1450,7 @@ class Validator {
|
|
|
1429
1450
|
let _errPreferredImpl = null;
|
|
1430
1451
|
const errPreferCombined = (d) => {
|
|
1431
1452
|
if (_errPreferredImpl === null) _errPreferredImpl = combinedIfSafe() || errOnly;
|
|
1432
|
-
return _errPreferredImpl(d);
|
|
1453
|
+
return _mustReject(_errPreferredImpl(d));
|
|
1433
1454
|
};
|
|
1434
1455
|
|
|
1435
1456
|
// The boolean engine is the verdict authority for these paths; the
|
|
@@ -1492,7 +1513,7 @@ class Validator {
|
|
|
1492
1513
|
let impl = null;
|
|
1493
1514
|
const onReject = (data) => {
|
|
1494
1515
|
const combined = combinedIfSafe();
|
|
1495
|
-
if (combined) { impl = combined; return combined(data); }
|
|
1516
|
+
if (combined) { impl = combined; return _mustReject(combined(data)); }
|
|
1496
1517
|
return errOnly(data);
|
|
1497
1518
|
};
|
|
1498
1519
|
const hybridFn = jsFn._hybridFactory(VALID_RESULT, onReject);
|
|
@@ -1556,9 +1577,9 @@ class Validator {
|
|
|
1556
1577
|
// The verdict methods answer validate()'s question without building the
|
|
1557
1578
|
// error list, so they run the same preprocess pass. Skipping it made the
|
|
1558
1579
|
// two disagree on input that coercion or a default would have fixed.
|
|
1559
|
-
this
|
|
1580
|
+
_bindVerdict(this, preprocess
|
|
1560
1581
|
? (data) => { preprocess(data); return jsFn(data) }
|
|
1561
|
-
: jsFn;
|
|
1582
|
+
: jsFn);
|
|
1562
1583
|
// Same preference as the object path: the combined function first, since
|
|
1563
1584
|
// it validates and collects in one pass, and the error generator behind
|
|
1564
1585
|
// it. `errPreferCombined` is that order, resolved on the first rejection
|
|
@@ -1771,9 +1792,9 @@ class Validator {
|
|
|
1771
1792
|
return _validate(data);
|
|
1772
1793
|
}
|
|
1773
1794
|
: _validate;
|
|
1774
|
-
this
|
|
1795
|
+
_bindVerdict(this, this._fastVerdict
|
|
1775
1796
|
? this._fastVerdict
|
|
1776
|
-
: (data) => _validate(data).valid;
|
|
1797
|
+
: (data) => _validate(data).valid);
|
|
1777
1798
|
this.validateAndParse = (jsonStr) => this._compiled.validateAndParse(jsonStr);
|
|
1778
1799
|
{
|
|
1779
1800
|
const slot = this._fastSlot;
|
|
@@ -1831,9 +1852,9 @@ class Validator {
|
|
|
1831
1852
|
? (data) => { preprocess(data); return interp.validate(data); }
|
|
1832
1853
|
: (data) => interp.validate(data));
|
|
1833
1854
|
this.validate = run;
|
|
1834
|
-
this
|
|
1855
|
+
_bindVerdict(this, this._fastVerdict
|
|
1835
1856
|
? this._fastVerdict
|
|
1836
|
-
: (data) => run(data).valid;
|
|
1857
|
+
: (data) => run(data).valid);
|
|
1837
1858
|
this.validateJSON = (jsonStr) => {
|
|
1838
1859
|
try {
|
|
1839
1860
|
return run(JSON.parse(jsonStr));
|
|
@@ -1962,30 +1983,54 @@ class Validator {
|
|
|
1962
1983
|
// schema coerces or defaults (preprocess mutates before the verdict),
|
|
1963
1984
|
// under abortEarly (already a frozen stub), and for $dynamicRef (the
|
|
1964
1985
|
// boolean engine is not the authority there).
|
|
1986
|
+
// A check registered through _extendValidate joins here when it can: the
|
|
1987
|
+
// verdict comes from the generated function with the check compiled in,
|
|
1988
|
+
// and the check's own errors are appended when somebody reads them. A
|
|
1989
|
+
// rejection then costs what it costs without the check. Where this layer
|
|
1990
|
+
// is not installed, the end of this method wraps validate() instead.
|
|
1991
|
+
const _vx = this._validateTail !== null ? this._validateTail() : null;
|
|
1992
|
+
let _vxApplied = false;
|
|
1965
1993
|
if (this._fastVerdict && !preprocess && !options.abortEarly && this.validate) {
|
|
1966
1994
|
const _full = this.validate;
|
|
1967
|
-
const _fast = this._fastVerdict;
|
|
1995
|
+
const _fast = _vx ? _fuseTail(this._fastVerdict, _vx.check) : this._fastVerdict;
|
|
1996
|
+
const _extra = _vx ? _vx.errors : null;
|
|
1997
|
+
_vxApplied = true;
|
|
1968
1998
|
const EMPTY_ERRORS = Object.freeze([]);
|
|
1969
1999
|
const _verdictFallback = [{ keyword: 'validation', instancePath: '', schemaPath: '#', params: {}, message: 'schema validation failed' }];
|
|
2000
|
+
const _withExtra = (own, data) => {
|
|
2001
|
+
const more = _extra === null ? null : _extra(data);
|
|
2002
|
+
if (more && more.length) return own ? own.concat(more) : more;
|
|
2003
|
+
// The data changed between the verdict and this read; keep the
|
|
2004
|
+
// verdict and say so rather than inventing a specific error.
|
|
2005
|
+
return own || _verdictFallback;
|
|
2006
|
+
};
|
|
1970
2007
|
const _buildErrors = (data) => {
|
|
1971
2008
|
const r = _full(data);
|
|
1972
|
-
return (r && r.valid === false && r.errors && r.errors.length)
|
|
1973
|
-
? r.errors
|
|
1974
|
-
// The data changed between the verdict and this read; keep the
|
|
1975
|
-
// verdict and say so rather than inventing a specific error.
|
|
1976
|
-
: _verdictFallback;
|
|
2009
|
+
return _withExtra((r && r.valid === false && r.errors && r.errors.length) ? r.errors : null, data);
|
|
1977
2010
|
};
|
|
1978
2011
|
const _buildRawErrors = (data) => {
|
|
1979
2012
|
const r = _full(data);
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
2013
|
+
let raw = null;
|
|
2014
|
+
if (r && r.valid === false) {
|
|
2015
|
+
raw = typeof r._ataRaw === 'function' ? r._ataRaw() : r.errors;
|
|
2016
|
+
if (!raw || !raw.length) raw = null;
|
|
2017
|
+
}
|
|
2018
|
+
return _withExtra(raw, data);
|
|
1983
2019
|
};
|
|
1984
2020
|
this.validate = (data) => {
|
|
1985
2021
|
if (_fast(data)) return { valid: true, data, errors: EMPTY_ERRORS };
|
|
1986
2022
|
return new LazyRejection(_buildErrors, data, _buildRawErrors);
|
|
1987
2023
|
};
|
|
1988
2024
|
}
|
|
2025
|
+
if (_vx && !_vxApplied && this.validate) {
|
|
2026
|
+
const inner = this.validate;
|
|
2027
|
+
const { check, errors } = _vx;
|
|
2028
|
+
this.validate = (data) => {
|
|
2029
|
+
const r = inner(data);
|
|
2030
|
+
if (r.valid && check(data)) return r;
|
|
2031
|
+
return new ExtendedRejection(r, data, errors);
|
|
2032
|
+
};
|
|
2033
|
+
}
|
|
1989
2034
|
|
|
1990
2035
|
// The buffer APIs answer from the native walker, which disagrees with
|
|
1991
2036
|
// validate() on shapes listed in lib/buffer-gate.js. For those schemas
|
|
@@ -2033,8 +2078,8 @@ class Validator {
|
|
|
2033
2078
|
this.isValidJSON = (jsonStr) => {
|
|
2034
2079
|
const scan = self._ensureScanner();
|
|
2035
2080
|
if (scan === undefined) return byParsing(jsonStr);
|
|
2036
|
-
if (scan === null) { self
|
|
2037
|
-
self
|
|
2081
|
+
if (scan === null) { _bindEntry(self, 'isValidJSON', byParsing); return byParsing(jsonStr); }
|
|
2082
|
+
_bindEntry(self, 'isValidJSON', memoizable
|
|
2038
2083
|
? (text) => {
|
|
2039
2084
|
if (typeof text !== 'string') return byParsing(text);
|
|
2040
2085
|
if (text === _memoText) return _memoVerdict;
|
|
@@ -2049,7 +2094,7 @@ class Validator {
|
|
|
2049
2094
|
const r = scan(text);
|
|
2050
2095
|
if (r === -1) return byParsing(text);
|
|
2051
2096
|
return r === 1;
|
|
2052
|
-
};
|
|
2097
|
+
});
|
|
2053
2098
|
return self.isValidJSON(jsonStr);
|
|
2054
2099
|
};
|
|
2055
2100
|
// validateJSON gets the same short-circuit isValidJSON has. The verdict is
|
|
@@ -2072,8 +2117,8 @@ class Validator {
|
|
|
2072
2117
|
this.validateJSON = (jsonStr) => {
|
|
2073
2118
|
const scan = self._ensureScanner();
|
|
2074
2119
|
if (scan === undefined) return validateByParsing(jsonStr);
|
|
2075
|
-
if (scan === null) { self
|
|
2076
|
-
self
|
|
2120
|
+
if (scan === null) { _bindEntry(self, 'validateJSON', validateByParsing); return validateByParsing(jsonStr); }
|
|
2121
|
+
_bindEntry(self, 'validateJSON', (text) => {
|
|
2077
2122
|
if (typeof text === 'string') {
|
|
2078
2123
|
const r = scan(text);
|
|
2079
2124
|
if (r === 1) return VALID_RESULT;
|
|
@@ -2088,21 +2133,25 @@ class Validator {
|
|
|
2088
2133
|
}
|
|
2089
2134
|
}
|
|
2090
2135
|
return validateByParsing(text);
|
|
2091
|
-
};
|
|
2136
|
+
});
|
|
2092
2137
|
return self.validateJSON(jsonStr);
|
|
2093
2138
|
};
|
|
2094
2139
|
}
|
|
2095
2140
|
}
|
|
2096
2141
|
|
|
2097
|
-
|
|
2098
|
-
//
|
|
2099
|
-
//
|
|
2100
|
-
//
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2142
|
+
// An extension registered through _extendValidate covers the JSON entry
|
|
2143
|
+
// points too. They are final here except for the scanner stubs above,
|
|
2144
|
+
// which rebind themselves on first use through _bindEntry, so the
|
|
2145
|
+
// extension is applied to whatever each one is now and again on rebind.
|
|
2146
|
+
if (_vx) {
|
|
2147
|
+
this._entryExt = _jsonEntryWrappers(_vx);
|
|
2148
|
+
for (const name of ['validateJSON', 'isValidJSON', 'validateAndParse']) {
|
|
2149
|
+
if (Object.prototype.hasOwnProperty.call(this, name) && typeof this[name] === 'function') _bindEntry(this, name, this[name]);
|
|
2150
|
+
}
|
|
2105
2151
|
}
|
|
2152
|
+
|
|
2153
|
+
|
|
2154
|
+
_rememberInstance(this);
|
|
2106
2155
|
}
|
|
2107
2156
|
|
|
2108
2157
|
// Which engine answers validate() for this schema: 'codegen' (generated
|
|
@@ -2205,7 +2254,8 @@ class Validator {
|
|
|
2205
2254
|
const cached = (this._userFormats || this._usesKeywords) ? null : _compileCache.get(mapKey);
|
|
2206
2255
|
if (cached && cached.jsFn) {
|
|
2207
2256
|
this._jsFn = cached.jsFn;
|
|
2208
|
-
this
|
|
2257
|
+
_bindVerdict(this, cached.jsFn);
|
|
2258
|
+
_rememberInstance(this);
|
|
2209
2259
|
return;
|
|
2210
2260
|
}
|
|
2211
2261
|
const uf = this._userFormats;
|
|
@@ -2213,7 +2263,8 @@ class Validator {
|
|
|
2213
2263
|
const jsFn = _cg || compileToJS(this._schemaObj, null, sm);
|
|
2214
2264
|
this._jsFn = jsFn;
|
|
2215
2265
|
if (jsFn) {
|
|
2216
|
-
this
|
|
2266
|
+
_bindVerdict(this, jsFn);
|
|
2267
|
+
_rememberInstance(this);
|
|
2217
2268
|
// A partial entry: the verdict function is real, the other two are not
|
|
2218
2269
|
// built yet rather than declined. `undefined` is the not-built marker
|
|
2219
2270
|
// the full compile's _buildDeferred looks for; `null` would read as
|
|
@@ -2615,6 +2666,229 @@ Object.defineProperty(Validator.prototype, "~standard", {
|
|
|
2615
2666
|
},
|
|
2616
2667
|
});
|
|
2617
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
|
+
|
|
2682
|
+
// Install the verdict method. Every place that binds isValidObject comes
|
|
2683
|
+
// through here, so a check registered with _extendVerdict survives the method
|
|
2684
|
+
// being replaced as the validator compiles further, which it does more than
|
|
2685
|
+
// once over its life.
|
|
2686
|
+
function _bindVerdict(self, fn) {
|
|
2687
|
+
const resolve = self._verdictTail;
|
|
2688
|
+
if (resolve !== null && typeof fn === 'function') {
|
|
2689
|
+
const tail = resolve();
|
|
2690
|
+
if (typeof tail === 'function') fn = _fuseTail(fn, tail);
|
|
2691
|
+
}
|
|
2692
|
+
self.isValidObject = fn;
|
|
2693
|
+
}
|
|
2694
|
+
|
|
2695
|
+
// Bind one of the JSON entry points, through the extension wrapper when there is one.
|
|
2696
|
+
function _bindEntry(self, name, fn) {
|
|
2697
|
+
const ext = self._entryExt;
|
|
2698
|
+
self[name] = ext !== null && ext[name] ? ext[name](fn) : fn;
|
|
2699
|
+
}
|
|
2700
|
+
|
|
2701
|
+
// The JSON entry points under an extension: the schema answers first, and only
|
|
2702
|
+
// text it accepts is parsed for the check, so a rejection costs nothing extra.
|
|
2703
|
+
function _jsonEntryWrappers({ check, errors }) {
|
|
2704
|
+
const parse = (text) => JSON.parse(typeof text === 'string' ? text : new TextDecoder().decode(text));
|
|
2705
|
+
return {
|
|
2706
|
+
validateJSON: (inner) => (text) => {
|
|
2707
|
+
const res = inner(text);
|
|
2708
|
+
if (!res.valid) return res;
|
|
2709
|
+
let data;
|
|
2710
|
+
try { data = parse(text); } catch { return res; }
|
|
2711
|
+
if (check(data)) return res;
|
|
2712
|
+
const e = errors(data);
|
|
2713
|
+
return e && e.length ? { valid: false, errors: e } : { valid: false, errors: [_EXT_FALLBACK] };
|
|
2714
|
+
},
|
|
2715
|
+
isValidJSON: (inner) => (text) => {
|
|
2716
|
+
if (!inner(text)) return false;
|
|
2717
|
+
let data;
|
|
2718
|
+
try { data = parse(text); } catch { return true; }
|
|
2719
|
+
return check(data);
|
|
2720
|
+
},
|
|
2721
|
+
validateAndParse: (inner) => (text) => {
|
|
2722
|
+
const res = inner(text);
|
|
2723
|
+
if (!res.valid) return res;
|
|
2724
|
+
if (check(res.value)) return res;
|
|
2725
|
+
const e = errors(res.value);
|
|
2726
|
+
return { valid: false, value: res.value, errors: e && e.length ? e : [_EXT_FALLBACK] };
|
|
2727
|
+
},
|
|
2728
|
+
};
|
|
2729
|
+
}
|
|
2730
|
+
const _EXT_FALLBACK = Object.freeze({ keyword: 'validation', instancePath: '', schemaPath: '#', params: {}, message: 'schema validation failed' });
|
|
2731
|
+
|
|
2732
|
+
// A verdict function that also runs `tail` on what it accepts. The generated
|
|
2733
|
+
// function can take the check in place of its final `return true`, one call
|
|
2734
|
+
// per document; anything else is composed.
|
|
2735
|
+
function _fuseTail(fn, tail) {
|
|
2736
|
+
const fused = typeof fn._withTail === 'function' ? fn._withTail(tail) : null;
|
|
2737
|
+
return fused || ((d) => fn(d) && tail(d));
|
|
2738
|
+
}
|
|
2739
|
+
|
|
2740
|
+
// The rejection validate() returns on the paths where an extension check could
|
|
2741
|
+
// not join the lazy layer: the inner result, plus the check's errors appended
|
|
2742
|
+
// on first read. `inner` may itself be valid, when only the check failed.
|
|
2743
|
+
class ExtendedRejection {
|
|
2744
|
+
constructor(inner, data, collect) {
|
|
2745
|
+
this.valid = false;
|
|
2746
|
+
this._inner = inner;
|
|
2747
|
+
this._data = data;
|
|
2748
|
+
this._collect = collect;
|
|
2749
|
+
this._errors = null;
|
|
2750
|
+
}
|
|
2751
|
+
toJSON() {
|
|
2752
|
+
return { valid: false, errors: this.errors };
|
|
2753
|
+
}
|
|
2754
|
+
_ataRaw() {
|
|
2755
|
+
const inner = this._inner;
|
|
2756
|
+
const more = this._collect(this._data) || [];
|
|
2757
|
+
const raw = inner.valid ? more : (typeof inner._ataRaw === 'function' ? inner._ataRaw() : inner.errors).concat(more);
|
|
2758
|
+
return raw.length ? raw : [_EXT_FALLBACK];
|
|
2759
|
+
}
|
|
2760
|
+
}
|
|
2761
|
+
Object.defineProperty(ExtendedRejection.prototype, 'errors', {
|
|
2762
|
+
enumerable: true,
|
|
2763
|
+
configurable: true,
|
|
2764
|
+
get() {
|
|
2765
|
+
if (this._errors === null) {
|
|
2766
|
+
const inner = this._inner;
|
|
2767
|
+
const more = this._collect(this._data) || [];
|
|
2768
|
+
const all = inner.valid ? more : inner.errors.concat(more);
|
|
2769
|
+
this._errors = all.length ? all : [_EXT_FALLBACK];
|
|
2770
|
+
}
|
|
2771
|
+
return this._errors;
|
|
2772
|
+
},
|
|
2773
|
+
});
|
|
2774
|
+
|
|
2775
|
+
// For wrappers that enforce a check the schema does not carry, such as the
|
|
2776
|
+
// `instanceof` keyword of @ata-project/keywords. `resolve` is called whenever
|
|
2777
|
+
// the verdict method is bound, which is after the schema has been normalized,
|
|
2778
|
+
// and returns the check, a function of the document that answers true or
|
|
2779
|
+
// false, or null when there is nothing to add. Only isValidObject takes it;
|
|
2780
|
+
// the other entry points, which report errors, stay the wrapper's to handle.
|
|
2781
|
+
//
|
|
2782
|
+
// Before this, such a wrapper had to hold isValidObject behind an accessor so
|
|
2783
|
+
// that the validator's own rebinding could not drop its check, and every call
|
|
2784
|
+
// paid for the accessor and two more calls: 10.1 ns against 4.2 on a document
|
|
2785
|
+
// the schema rejects at its third property.
|
|
2786
|
+
Validator.prototype._verdictTail = null;
|
|
2787
|
+
Validator.prototype._validateTail = null;
|
|
2788
|
+
Validator.prototype._entryExt = null;
|
|
2789
|
+
|
|
2790
|
+
// Let `new Validator(schema)` with the same schema object return this instance.
|
|
2791
|
+
// Only an instance built without options may answer that call: one built with
|
|
2792
|
+
// options (richErrors: false, coerceTypes, formats, ...) would hand its options
|
|
2793
|
+
// to a caller that asked for none, and an extended one would enforce checks the
|
|
2794
|
+
// caller never registered. Both the caller's object and the normalized one are
|
|
2795
|
+
// keys, since a later caller passes the former.
|
|
2796
|
+
function _rememberInstance(self) {
|
|
2797
|
+
if (!self._noOpts || self._verdictTail !== null || self._validateTail !== null) return;
|
|
2798
|
+
const raw = self._rawSchema;
|
|
2799
|
+
if (raw && typeof raw === 'object' && !_identityCache.has(raw)) _identityCache.set(raw, self);
|
|
2800
|
+
const obj = self._schemaObj;
|
|
2801
|
+
if (obj !== raw && obj && typeof obj === 'object' && !_identityCache.has(obj)) _identityCache.set(obj, self);
|
|
2802
|
+
}
|
|
2803
|
+
|
|
2804
|
+
// An extended validator answers differently from a plain one for the same
|
|
2805
|
+
// schema, so it must not be the instance `new Validator(sameSchema)` hands out.
|
|
2806
|
+
function _leaveIdentityCache(self) {
|
|
2807
|
+
self._noOpts = false;
|
|
2808
|
+
// Nothing is registered before the first compile, so there is nothing to
|
|
2809
|
+
// take back.
|
|
2810
|
+
if (!self._initialized && self._jsFn === null) return;
|
|
2811
|
+
const raw = self._rawSchema;
|
|
2812
|
+
if (raw && typeof raw === 'object' && _identityCache.get(raw) === self) _identityCache.delete(raw);
|
|
2813
|
+
// The compiled form is cached too, at the end of the first compile. Read it
|
|
2814
|
+
// only if it is already materialized: reading it otherwise builds it.
|
|
2815
|
+
if (Object.prototype.hasOwnProperty.call(self, '_schemaObj')) {
|
|
2816
|
+
const obj = self._schemaObj;
|
|
2817
|
+
if (obj && typeof obj === 'object' && _identityCache.get(obj) === self) _identityCache.delete(obj);
|
|
2818
|
+
}
|
|
2819
|
+
}
|
|
2820
|
+
|
|
2821
|
+
// The same kind of extension for validate(): `resolve` returns { check, errors }
|
|
2822
|
+
// or null, where `errors(data)` lists the check's own errors, or returns null
|
|
2823
|
+
// when there are none. The check's errors come after the schema's, and a value
|
|
2824
|
+
// that fails only the check is rejected with the check's errors alone. Must be
|
|
2825
|
+
// called before validate() is first used, which is when it is compiled; later
|
|
2826
|
+
// calls throw rather than being silently ignored.
|
|
2827
|
+
Validator.prototype._extendValidate = function (resolve) {
|
|
2828
|
+
if (typeof resolve !== 'function') throw new TypeError('_extendValidate expects a function');
|
|
2829
|
+
if (this._initialized) throw new Error('_extendValidate must be called before the validator compiles');
|
|
2830
|
+
_leaveIdentityCache(this);
|
|
2831
|
+
const prev = this._validateTail;
|
|
2832
|
+
this._validateTail = prev === null ? resolve : () => {
|
|
2833
|
+
const a = prev(), b = resolve();
|
|
2834
|
+
if (!a) return b;
|
|
2835
|
+
if (!b) return a;
|
|
2836
|
+
return {
|
|
2837
|
+
check: (d) => a.check(d) && b.check(d),
|
|
2838
|
+
errors: (d) => {
|
|
2839
|
+
const x = a.errors(d), y = b.errors(d);
|
|
2840
|
+
if (!x) return y;
|
|
2841
|
+
if (!y) return x;
|
|
2842
|
+
return x.concat(y);
|
|
2843
|
+
},
|
|
2844
|
+
};
|
|
2845
|
+
};
|
|
2846
|
+
return this;
|
|
2847
|
+
};
|
|
2848
|
+
|
|
2849
|
+
// Both extensions in one call, from one resolver that returns { check, errors }
|
|
2850
|
+
// or null. This is the form @ata-project/keywords uses: registering costs one
|
|
2851
|
+
// closure, where wrapping the five entry points cost a closure per entry point,
|
|
2852
|
+
// the wrappers themselves and an accessor, most of what building a wrapped
|
|
2853
|
+
// validator took.
|
|
2854
|
+
Validator.prototype._extendChecks = function (resolve) {
|
|
2855
|
+
if (typeof resolve !== 'function') throw new TypeError('_extendChecks expects a function');
|
|
2856
|
+
this._extendValidate(resolve);
|
|
2857
|
+
return this._extendVerdict(() => {
|
|
2858
|
+
const x = resolve();
|
|
2859
|
+
return x ? x.check : null;
|
|
2860
|
+
});
|
|
2861
|
+
};
|
|
2862
|
+
|
|
2863
|
+
// Whether this instance enforces a check its schema does not carry. The
|
|
2864
|
+
// ahead-of-time emitters build a module from the schema alone, so they refuse
|
|
2865
|
+
// an instance that says yes rather than emit one that accepts too much.
|
|
2866
|
+
// Reading it resolves the registered checks; only an emitter reads it.
|
|
2867
|
+
Object.defineProperty(Validator.prototype, '_externalChecks', {
|
|
2868
|
+
configurable: true,
|
|
2869
|
+
get() {
|
|
2870
|
+
if (this._validateTail !== null && this._validateTail()) return true;
|
|
2871
|
+
if (this._verdictTail !== null && typeof this._verdictTail() === 'function') return true;
|
|
2872
|
+
return false;
|
|
2873
|
+
},
|
|
2874
|
+
});
|
|
2875
|
+
|
|
2876
|
+
Validator.prototype._extendVerdict = function (resolve) {
|
|
2877
|
+
if (typeof resolve !== 'function') throw new TypeError('_extendVerdict expects a function');
|
|
2878
|
+
_leaveIdentityCache(this);
|
|
2879
|
+
const prev = this._verdictTail;
|
|
2880
|
+
this._verdictTail = prev === null ? resolve : () => {
|
|
2881
|
+
const a = prev(), b = resolve();
|
|
2882
|
+
if (typeof a !== 'function') return b;
|
|
2883
|
+
if (typeof b !== 'function') return a;
|
|
2884
|
+
return (d) => a(d) && b(d);
|
|
2885
|
+
};
|
|
2886
|
+
// A method bound before this call was bound without the check. Rebind it.
|
|
2887
|
+
// An unbound one binds through _bindVerdict on its first call.
|
|
2888
|
+
if (Object.prototype.hasOwnProperty.call(this, 'isValidObject')) _bindVerdict(this, this.isValidObject);
|
|
2889
|
+
return this;
|
|
2890
|
+
};
|
|
2891
|
+
|
|
2618
2892
|
function _defineLazyMethod(name, maker) {
|
|
2619
2893
|
Object.defineProperty(Validator.prototype, name, {
|
|
2620
2894
|
configurable: true,
|
|
@@ -2660,13 +2934,13 @@ _defineLazyMethod('isValidObject', (self) => (data) => {
|
|
|
2660
2934
|
if (_tier.tier === 0) {
|
|
2661
2935
|
const _plan = buildTier0Plan(self._schemaObj);
|
|
2662
2936
|
let _n = 0;
|
|
2663
|
-
self
|
|
2937
|
+
_bindVerdict(self, (d) => {
|
|
2664
2938
|
const r = tier0Validate(_plan, d);
|
|
2665
2939
|
if (++_n === 2) {
|
|
2666
2940
|
try { self._ensureCodegen(); } catch {}
|
|
2667
2941
|
}
|
|
2668
2942
|
return r;
|
|
2669
|
-
};
|
|
2943
|
+
});
|
|
2670
2944
|
} else {
|
|
2671
2945
|
// `new Function` is a property of the realm, not of the schema: under a
|
|
2672
2946
|
// strict CSP or `--disallow-code-generation-from-strings` this throws
|
package/lib/formats.js
CHANGED
|
@@ -117,8 +117,16 @@ function dateTime (s) {
|
|
|
117
117
|
// Also used for the dotted tail of an IPv4-mapped IPv6 address, which is why
|
|
118
118
|
// the work is done over a range rather than a whole string: taking a slice for
|
|
119
119
|
// it would allocate on a path that exists to avoid allocating.
|
|
120
|
+
// Four dotted decimal octets, 0 to 255, no leading zeros: the same language
|
|
121
|
+
// ipv4Range walks, as one regular expression. The engine answers a dotted quad
|
|
122
|
+
// in about 12 ns where the walk takes 17, for the reason `uri` above found:
|
|
123
|
+
// its character-class loop beats a charCodeAt loop. ipv4Range stays for the
|
|
124
|
+
// embedded form inside an IPv6 address, where the octets are a slice.
|
|
125
|
+
// tests/test_ipv4_regex.js holds the two to the same answers.
|
|
126
|
+
const IPV4 = /^(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)\.){3}(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)$/;
|
|
127
|
+
|
|
120
128
|
function ipv4 (s) {
|
|
121
|
-
return
|
|
129
|
+
return IPV4.test(s);
|
|
122
130
|
}
|
|
123
131
|
|
|
124
132
|
function ipv4Range (s, from, to) {
|
|
@@ -147,8 +155,17 @@ function ipv4Range (s, from, to) {
|
|
|
147
155
|
// allocated twice and accepted things like "12345::1"; they also disagreed
|
|
148
156
|
// with each other about an IPv4 tail. Answers match Node's own `net.isIPv6`,
|
|
149
157
|
// which the test uses as its oracle.
|
|
158
|
+
// Eight groups of one to four hex digits and nothing else: the uncompressed
|
|
159
|
+
// form, which is always a valid address. The walk below takes 64 ns on one;
|
|
160
|
+
// this answers in about 30, and anything it does not match, compressed forms
|
|
161
|
+
// and embedded IPv4 included, goes to the walk as before. It is only tried from
|
|
162
|
+
// 15 characters, the shortest string it can match, so a short rejection does
|
|
163
|
+
// not pay for it. tests/test_ipv6_fast_path.js holds the subset relation.
|
|
164
|
+
const IPV6_FULL = /^(?:[0-9A-Fa-f]{1,4}:){7}[0-9A-Fa-f]{1,4}$/;
|
|
165
|
+
|
|
150
166
|
function ipv6 (s) {
|
|
151
167
|
const n = s.length;
|
|
168
|
+
if (n >= 15 && IPV6_FULL.test(s)) return true;
|
|
152
169
|
if (n < 2 || n > 45) return false;
|
|
153
170
|
|
|
154
171
|
let end = n;
|
|
@@ -256,7 +273,22 @@ SCHEME_CHAR[43] = 1; SCHEME_CHAR[45] = 1; SCHEME_CHAR[46] = 1;
|
|
|
256
273
|
// string or scanned again. Reading it out with `slice` and asking the copy for
|
|
257
274
|
// `lastIndexOf` and two regular expressions allocated a string per URI, and a
|
|
258
275
|
// document carrying a handful of URLs paid that per field.
|
|
276
|
+
// The shape almost every URI in a document has, as one regular expression: a
|
|
277
|
+
// scheme, then either "//" and a host with no userinfo, port, brackets or
|
|
278
|
+
// percent escapes, or no authority at all, then a tail of characters RFC 3986
|
|
279
|
+
// allows, again without "%". Everything it accepts the walk below accepts too,
|
|
280
|
+
// and it answers the common case in about 25 ns where the walk takes 43 on a
|
|
281
|
+
// 34-character URL, because the engine scans a character class faster than a
|
|
282
|
+
// charCodeAt loop does. Anything it declines, the walk decides as before.
|
|
283
|
+
//
|
|
284
|
+
// The two branches after the colon are exclusive on purpose. With the "//"
|
|
285
|
+
// group merely optional, "http://[::1" could skip it and match "//[::1" as a
|
|
286
|
+
// path, accepting a string the walk rejects for its unclosed bracket.
|
|
287
|
+
// tests/test_uri_fast_path.js holds the subset relation.
|
|
288
|
+
const URI_FAST = /^[A-Za-z][A-Za-z0-9+.\-]*:(?:\/\/[!$&-.0-9;=A-Z_a-z~]*(?:[\/?#][!#$&-;=?-\[\]_a-z~]*)?|(?!\/\/)[!#$&-;=?-\[\]_a-z~]*)$/;
|
|
289
|
+
|
|
259
290
|
function uri (s) {
|
|
291
|
+
if (URI_FAST.test(s)) return true;
|
|
260
292
|
const n = s.length;
|
|
261
293
|
if (n === 0) return false;
|
|
262
294
|
let c = s.charCodeAt(0);
|
|
@@ -762,8 +794,8 @@ const URI_HELPER_TABLES = 'const _uct=new Uint8Array(128);for(let _i=33;_i<127;_
|
|
|
762
794
|
'for(let _i=97;_i<103;_i++)_uch[_i]=1;for(let _i=65;_i<71;_i++)_uch[_i]=1;' +
|
|
763
795
|
'const _ucs=new Uint8Array(128);for(let _i=48;_i<58;_i++)_ucs[_i]=1;' +
|
|
764
796
|
'for(let _i=97;_i<123;_i++)_ucs[_i]=1;for(let _i=65;_i<91;_i++)_ucs[_i]=1;' +
|
|
765
|
-
'_ucs[43]=1;_ucs[45]=1;_ucs[46]=1';
|
|
766
|
-
const URI_HELPER_BODY = 'const _n=_s.length;if(_n===0)return false;let _c=_s.charCodeAt(0);if(_c>127||_ucs[_c]===0||(_c>=48&&_c<=57)||_c===43||_c===45||_c===46)return false;let _co=-1;for(let _i=1;_i<_n;_i++){_c=_s.charCodeAt(_i);if(_c===58){_co=_i;break}if(_c>127||_ucs[_c]===0)return false}if(_co===-1)return false;let _i=_co+1;let _ae=_n;if(_s.charCodeAt(_i)===47&&_s.charCodeAt(_i+1)===47){const _as=_i+2;let _at=-1,_fc=-1,_lc=-1,_br=0,_ba=0,_hs=_as;_ae=-1;for(_i=_as;_i<_n;_i++){_c=_s.charCodeAt(_i);if(_c>127||_uct[_c]===0)return false;if(_c===47||_c===63||_c===35){_ae=_i;break}if(_c===37){const _h1=_s.charCodeAt(_i+1),_h2=_s.charCodeAt(_i+2);if(!(_h1<=127)||!(_h2<=127)||_uch[_h1]===0||_uch[_h2]===0)return false;_i+=2;continue}if(_c===64){_ba=_br;_at=_i;_fc=-1;_lc=-1;_hs=_i+1}else if(_c===58){if(_fc===-1)_fc=_i;_lc=_i}else if(_c===91||_c===93){_br=1}}if(_ae===-1)_ae=_n;if(_at!==-1&&_ba)return false;if(_s.charCodeAt(_hs)===91){let _cl=-1;for(let _j=_hs+1;_j<_ae;_j++){if(_s.charCodeAt(_j)===93){_cl=_j;break}}if(_cl===-1)return false;if(_cl+1!==_ae){if(_s.charCodeAt(_cl+1)!==58)return false;for(let _j=_cl+2;_j<_ae;_j++){const _d=_s.charCodeAt(_j);if(_d<48||_d>57)return false}}}else if(_lc!==-1){if(_fc!==_lc)return false;for(let _j=_lc+1;_j<_ae;_j++){const _d=_s.charCodeAt(_j);if(_d<48||_d>57)return false}}_i=_ae}for(;_i<_n;_i++){_c=_s.charCodeAt(_i);if(_c>127||_uct[_c]===0)return false;if(_c===37){const _h1=_s.charCodeAt(_i+1),_h2=_s.charCodeAt(_i+2);if(!(_h1<=127)||!(_h2<=127)||_uch[_h1]===0||_uch[_h2]===0)return false;_i+=2}}return true;';
|
|
797
|
+
'_ucs[43]=1;_ucs[45]=1;_ucs[46]=1;const _ufa=/' + URI_FAST.source + '/';
|
|
798
|
+
const URI_HELPER_BODY = 'if(_ufa.test(_s))return true;const _n=_s.length;if(_n===0)return false;let _c=_s.charCodeAt(0);if(_c>127||_ucs[_c]===0||(_c>=48&&_c<=57)||_c===43||_c===45||_c===46)return false;let _co=-1;for(let _i=1;_i<_n;_i++){_c=_s.charCodeAt(_i);if(_c===58){_co=_i;break}if(_c>127||_ucs[_c]===0)return false}if(_co===-1)return false;let _i=_co+1;let _ae=_n;if(_s.charCodeAt(_i)===47&&_s.charCodeAt(_i+1)===47){const _as=_i+2;let _at=-1,_fc=-1,_lc=-1,_br=0,_ba=0,_hs=_as;_ae=-1;for(_i=_as;_i<_n;_i++){_c=_s.charCodeAt(_i);if(_c>127||_uct[_c]===0)return false;if(_c===47||_c===63||_c===35){_ae=_i;break}if(_c===37){const _h1=_s.charCodeAt(_i+1),_h2=_s.charCodeAt(_i+2);if(!(_h1<=127)||!(_h2<=127)||_uch[_h1]===0||_uch[_h2]===0)return false;_i+=2;continue}if(_c===64){_ba=_br;_at=_i;_fc=-1;_lc=-1;_hs=_i+1}else if(_c===58){if(_fc===-1)_fc=_i;_lc=_i}else if(_c===91||_c===93){_br=1}}if(_ae===-1)_ae=_n;if(_at!==-1&&_ba)return false;if(_s.charCodeAt(_hs)===91){let _cl=-1;for(let _j=_hs+1;_j<_ae;_j++){if(_s.charCodeAt(_j)===93){_cl=_j;break}}if(_cl===-1)return false;if(_cl+1!==_ae){if(_s.charCodeAt(_cl+1)!==58)return false;for(let _j=_cl+2;_j<_ae;_j++){const _d=_s.charCodeAt(_j);if(_d<48||_d>57)return false}}}else if(_lc!==-1){if(_fc!==_lc)return false;for(let _j=_lc+1;_j<_ae;_j++){const _d=_s.charCodeAt(_j);if(_d<48||_d>57)return false}}_i=_ae}for(;_i<_n;_i++){_c=_s.charCodeAt(_i);if(_c>127||_uct[_c]===0)return false;if(_c===37){const _h1=_s.charCodeAt(_i+1),_h2=_s.charCodeAt(_i+2);if(!(_h1<=127)||!(_h2<=127)||_uch[_h1]===0||_uch[_h2]===0)return false;_i+=2}}return true;';
|
|
767
799
|
const uriHelperSource = (name) =>
|
|
768
800
|
URI_HELPER_TABLES + ';function ' + name + '(_s){' + URI_HELPER_BODY + '}';
|
|
769
801
|
|
|
@@ -1010,4 +1042,4 @@ function durationSource (v, isStr) {
|
|
|
1010
1042
|
return isStr ? inner : `if(typeof ${v}==='string'&&${inner.slice(3)}`;
|
|
1011
1043
|
}
|
|
1012
1044
|
|
|
1013
|
-
module.exports = { uriHelperSource, date, ipv4, dateTime, ipv6, hostname, uri, uriReference, uuid, time, noReserved, jsonPointer, relativeJsonPointer, uriTemplate, email, duration, uriChars, uriAuthority, uriCharsSource, uriAuthoritySource, iri, iriReference, idnEmail, iriSource, iriReferenceSource, idnEmailSource, dateSource, ipv4Source, dateTimeSource, ipv6Source, hostnameSource, uriSource, uuidSource, timeSource, noReservedSource, jsonPointerSource, relativeJsonPointerSource, uriTemplateSource, emailSource, durationSource };
|
|
1045
|
+
module.exports = { IPV4, IPV6_FULL, ipv4Range, URI_FAST, uriHelperSource, date, ipv4, dateTime, ipv6, hostname, uri, uriReference, uuid, time, noReserved, jsonPointer, relativeJsonPointer, uriTemplate, email, duration, uriChars, uriAuthority, uriCharsSource, uriAuthoritySource, iri, iriReference, idnEmail, iriSource, iriReferenceSource, idnEmailSource, dateSource, ipv4Source, dateTimeSource, ipv6Source, hostnameSource, uriSource, uuidSource, timeSource, noReservedSource, jsonPointerSource, relativeJsonPointerSource, uriTemplateSource, emailSource, durationSource };
|
package/lib/js-compiler.js
CHANGED
|
@@ -968,6 +968,23 @@ function hasNestedIdScope(node, isRoot = true) {
|
|
|
968
968
|
// only safe way out.
|
|
969
969
|
const DECLINE = Symbol('ata.decline')
|
|
970
970
|
|
|
971
|
+
// The error and combined generators compile the subschemas of not, if, contains
|
|
972
|
+
// and their anyOf/oneOf closures with the boolean generator. That generator
|
|
973
|
+
// follows a reference back to the root with `_validate` and a reference cycle
|
|
974
|
+
// with a named function in its preamble, and neither exists inside the other
|
|
975
|
+
// two generators' functions: the first threw a ReferenceError on the first
|
|
976
|
+
// document that reached it, and the second cut the cycle, so the reference
|
|
977
|
+
// checked nothing. While `nestedBoolean` is set, genCode declines at those
|
|
978
|
+
// points instead, and the caller's generator declines the schema.
|
|
979
|
+
function nestedGenCode(schema, v, lines, ctx) {
|
|
980
|
+
ctx.nestedBoolean = (ctx.nestedBoolean || 0) + 1
|
|
981
|
+
try {
|
|
982
|
+
genCode(schema, v, lines, ctx)
|
|
983
|
+
} finally {
|
|
984
|
+
ctx.nestedBoolean--
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
|
|
971
988
|
const CYCLE_DEPTH = 512
|
|
972
989
|
|
|
973
990
|
// Declarations the guarded functions close over. Emitted once, ahead of them.
|
|
@@ -1567,7 +1584,7 @@ function compileToJSCodegen(schema, schemaMap, userFormats) {
|
|
|
1567
1584
|
const defSets = ctx.defFns ? Array.from(ctx.defFns.values()) : []
|
|
1568
1585
|
const needsGuard = ctx.usesRecursion || defSets.length > 0
|
|
1569
1586
|
|
|
1570
|
-
let body, hybridBody
|
|
1587
|
+
let body, hybridBody, tailBody
|
|
1571
1588
|
if (ctx.usesRecursion) {
|
|
1572
1589
|
// Self-recursive: a named body reached through the cycle guard, so data
|
|
1573
1590
|
// that points back at itself settles instead of exhausting the stack.
|
|
@@ -1577,15 +1594,21 @@ function compileToJSCodegen(schema, schemaMap, userFormats) {
|
|
|
1577
1594
|
body = `${decl}function _run(d){\n ${run}\n }\n return _run(d)`
|
|
1578
1595
|
// Hybrid: keep the boolean answer, wrap only the outer call
|
|
1579
1596
|
hybridBody = `${decl}function _run(d){\n ${run}\n }\n return _run(d)?R:E(d)`
|
|
1597
|
+
tailBody = `${decl}function _run(d){\n ${run}\n }\n return _run(d)&&__ataTail(d)`
|
|
1580
1598
|
} else if (defSets.length > 0) {
|
|
1581
1599
|
// No self-reference at the root, but a def on a cycle can still be
|
|
1582
1600
|
// re-entered with the same value; the guarded functions need their state.
|
|
1583
1601
|
const run = emitGuardedRun(`return _body(d)`, '', defSets)
|
|
1584
1602
|
body = `function _body(d){\n ${checkStr}\n return true\n }\n function _run(d){\n ${run}\n }\n return _run(d)`
|
|
1585
1603
|
hybridBody = `function _body(d){\n ${checkStr}\n return true\n }\n function _run(d){\n ${run}\n }\n return _run(d)?R:E(d)`
|
|
1604
|
+
tailBody = `function _body(d){\n ${checkStr}\n return true\n }\n function _run(d){\n ${run}\n }\n return _run(d)&&__ataTail(d)`
|
|
1586
1605
|
} else {
|
|
1587
1606
|
body = checkStr + '\n return true'
|
|
1588
1607
|
hybridBody = replaceTopLevel(checkStr + '\n return R')
|
|
1608
|
+
// An early accept inside the checks has to reach the tail too, so every
|
|
1609
|
+
// top-level `return true` becomes the tail call, by the same rewrite the
|
|
1610
|
+
// hybrid above relies on and test_hybrid_agreement holds to the suite.
|
|
1611
|
+
tailBody = replaceTopLevel(checkStr + '\n return true', 'return __ataTail(d)', 'return false')
|
|
1589
1612
|
}
|
|
1590
1613
|
|
|
1591
1614
|
const guardStr = needsGuard ? emitGuardState() : ''
|
|
@@ -1610,6 +1633,26 @@ function compileToJSCodegen(schema, schemaMap, userFormats) {
|
|
|
1610
1633
|
boolFn._hybridFactory = (R, E) => hybridFactory(...closureValues, R, E)
|
|
1611
1634
|
} catch {}
|
|
1612
1635
|
|
|
1636
|
+
// The same function with one more check where it would return true, for a
|
|
1637
|
+
// wrapper that enforces something the schema does not carry (see
|
|
1638
|
+
// Validator#_extendVerdict). Calling the check from here rather than from a
|
|
1639
|
+
// function around this one is the point: this function is too large for
|
|
1640
|
+
// the engine to inline, so a wrapper is a second call on every document,
|
|
1641
|
+
// and a rejection never reaches the check at all. Built on first request,
|
|
1642
|
+
// since almost no validator is extended. Null when code generation is
|
|
1643
|
+
// refused, and the caller composes instead.
|
|
1644
|
+
let tailFactory
|
|
1645
|
+
boolFn._withTail = (tail) => {
|
|
1646
|
+
if (tailFactory === undefined) {
|
|
1647
|
+
try {
|
|
1648
|
+
tailFactory = new Function(...closureNames, '__ataTail', `${preambleStr}return function(d){${tailBody}}`)
|
|
1649
|
+
} catch {
|
|
1650
|
+
tailFactory = null
|
|
1651
|
+
}
|
|
1652
|
+
}
|
|
1653
|
+
return tailFactory === null ? null : tailFactory(...closureValues, tail)
|
|
1654
|
+
}
|
|
1655
|
+
|
|
1613
1656
|
// Store source for standalone compilation. Regex declarations are NOT
|
|
1614
1657
|
// inlined into the body: they are carried by _closures and the emitters
|
|
1615
1658
|
// declare them once at load scope. Inlining them here put a
|
|
@@ -1663,7 +1706,7 @@ function compileToJSCodegen(schema, schemaMap, userFormats) {
|
|
|
1663
1706
|
|
|
1664
1707
|
// Replace top-level `return false` → `return E(d)` and `return true` → `return R`.
|
|
1665
1708
|
// Tracks function nesting depth to preserve nested function internals.
|
|
1666
|
-
function replaceTopLevel(code) {
|
|
1709
|
+
function replaceTopLevel(code, onTrue = 'return R', onFalse = 'return E(d)') {
|
|
1667
1710
|
let fnDepth = 0, result = '', i = 0
|
|
1668
1711
|
while (i < code.length) {
|
|
1669
1712
|
// A nested closure keeps its own returns. Both spellings count: a
|
|
@@ -1690,10 +1733,10 @@ function replaceTopLevel(code) {
|
|
|
1690
1733
|
i++
|
|
1691
1734
|
}
|
|
1692
1735
|
} else if (code.startsWith('return false', i)) {
|
|
1693
|
-
result +=
|
|
1736
|
+
result += onFalse
|
|
1694
1737
|
i += 12
|
|
1695
1738
|
} else if (code.startsWith('return true', i) && (i + 11 >= code.length || !/[a-zA-Z_$]/.test(code[i + 11]))) {
|
|
1696
|
-
result +=
|
|
1739
|
+
result += onTrue
|
|
1697
1740
|
i += 11
|
|
1698
1741
|
} else {
|
|
1699
1742
|
result += code[i]
|
|
@@ -1852,6 +1895,7 @@ function genCode(schema, v, lines, ctx, knownType) {
|
|
|
1852
1895
|
if (schema.$ref) {
|
|
1853
1896
|
// Self-reference "#" — recursive call to root validator
|
|
1854
1897
|
if (schema.$ref === '#') {
|
|
1898
|
+
if (ctx.nestedBoolean) throw DECLINE
|
|
1855
1899
|
ctx.usesRecursion = true
|
|
1856
1900
|
lines.push(`if(!_validate(${v}))return false`)
|
|
1857
1901
|
if (!hasSiblings) return
|
|
@@ -1894,7 +1938,7 @@ function genCode(schema, v, lines, ctx, knownType) {
|
|
|
1894
1938
|
}
|
|
1895
1939
|
lines.push(`if(!${fnName}(${v}))return false`)
|
|
1896
1940
|
if (!hasSiblings) return
|
|
1897
|
-
} else if (ctx.refStack.has(schema.$ref)) { if (!hasSiblings) return }
|
|
1941
|
+
} else if (ctx.refStack.has(schema.$ref)) { if (ctx.nestedBoolean) throw DECLINE; if (!hasSiblings) return }
|
|
1898
1942
|
else {
|
|
1899
1943
|
ctx.refStack.add(schema.$ref)
|
|
1900
1944
|
genCode(ctx.rootDefs[defName], v, lines, ctx, knownType)
|
|
@@ -1906,7 +1950,7 @@ function genCode(schema, v, lines, ctx, knownType) {
|
|
|
1906
1950
|
const entry = ctx.rootDefs && ctx.rootDefs[schema.$ref]
|
|
1907
1951
|
const anchorTarget = entry && entry.raw ? entry.raw : (ctx.anchors && ctx.anchors[schema.$ref])
|
|
1908
1952
|
if (anchorTarget) {
|
|
1909
|
-
if (ctx.refStack.has(schema.$ref)) { if (!hasSiblings) return }
|
|
1953
|
+
if (ctx.refStack.has(schema.$ref)) { if (ctx.nestedBoolean) throw DECLINE; if (!hasSiblings) return }
|
|
1910
1954
|
else {
|
|
1911
1955
|
ctx.refStack.add(schema.$ref)
|
|
1912
1956
|
genCode(anchorTarget, v, lines, ctx, knownType)
|
|
@@ -1949,11 +1993,13 @@ function genCode(schema, v, lines, ctx, knownType) {
|
|
|
1949
1993
|
const target = ctx.anchors[anchorKey]
|
|
1950
1994
|
if (target === ctx.rootSchema) {
|
|
1951
1995
|
// Self-recursive: generate _validate(v) call
|
|
1996
|
+
if (ctx.nestedBoolean) throw DECLINE
|
|
1952
1997
|
ctx.usesRecursion = true
|
|
1953
1998
|
lines.push(`if(!_validate(${v}))return false`)
|
|
1954
1999
|
} else {
|
|
1955
2000
|
// Different schema: inline the target validation
|
|
1956
2001
|
const refKey = '$dynamicRef:' + anchorKey
|
|
2002
|
+
if (ctx.refStack.has(refKey) && ctx.nestedBoolean) throw DECLINE
|
|
1957
2003
|
if (!ctx.refStack.has(refKey)) {
|
|
1958
2004
|
ctx.refStack.add(refKey)
|
|
1959
2005
|
genCode(target, v, lines, ctx, knownType)
|
|
@@ -3147,8 +3193,20 @@ const FORMAT_CODEGEN = {
|
|
|
3147
3193
|
'uri-reference': (v, isStr) => isStr
|
|
3148
3194
|
? `{${_formats.uriCharsSource(v, '0')}}`
|
|
3149
3195
|
: `if(typeof ${v}==='string'){${_formats.uriCharsSource(v, '0')}}`,
|
|
3150
|
-
ipv4:
|
|
3151
|
-
|
|
3196
|
+
ipv4: (v, isStr, ctx) => {
|
|
3197
|
+
if (!ctx) return _formats.ipv4Source(v, isStr)
|
|
3198
|
+
// Hoisted: a regex literal in the body would build a new RegExp per call.
|
|
3199
|
+
hoistOnce(ctx, '_ip4Hoisted', 'const _ip4=/' + _formats.IPV4.source + '/')
|
|
3200
|
+
return isStr ? `if(!_ip4.test(${v}))return false` : `if(typeof ${v}==='string'&&!_ip4.test(${v}))return false`
|
|
3201
|
+
},
|
|
3202
|
+
ipv6: (v, isStr, ctx) => {
|
|
3203
|
+
if (!ctx) return _formats.ipv6Source(v, isStr)
|
|
3204
|
+
hoistOnce(ctx, '_ip6Hoisted', 'const _ip6f=/' + _formats.IPV6_FULL.source + '/')
|
|
3205
|
+
// The fast accept jumps past the walk; the walk's `return false`s are what
|
|
3206
|
+
// the error generators rewrite, so they stay as they are.
|
|
3207
|
+
const body = `_ip6:{if(${v}.length>=15&&_ip6f.test(${v}))break _ip6;${_formats.ipv6Source(v, true).replace(/^\{|\}$/g, '')}}`
|
|
3208
|
+
return isStr ? body : `if(typeof ${v}==='string'){${body}}`
|
|
3209
|
+
},
|
|
3152
3210
|
hostname: _formats.hostnameSource,
|
|
3153
3211
|
}
|
|
3154
3212
|
|
|
@@ -3512,7 +3570,23 @@ function compileToJSCodegenWithErrors(schema, schemaMap, userFormats, sourceOpts
|
|
|
3512
3570
|
// first invalid document threw a ReferenceError instead of reporting.
|
|
3513
3571
|
const cvars = ctx.closureVars
|
|
3514
3572
|
const cvals = ctx.closureVals
|
|
3515
|
-
|
|
3573
|
+
// The helpers are constants: the uri tables, the compiled patterns, the
|
|
3574
|
+
// branch-collapse functions, the name sets. In `body` they sit inside the
|
|
3575
|
+
// function and were rebuilt on every call, which on a product schema with
|
|
3576
|
+
// six URLs and one anyOf cost about 450 ns before a single check ran, more
|
|
3577
|
+
// than every check and every error together. Here they run once, in a
|
|
3578
|
+
// factory, as the verdict function's preamble does. Only for the plain
|
|
3579
|
+
// shape: with $defs or recursion the helpers include functions that read
|
|
3580
|
+
// the per-call guard state declared in the body, so those keep `body` as
|
|
3581
|
+
// it is. `_errSource` below stays `body` either way, for the emitters.
|
|
3582
|
+
const hoistable = !ctx.usesRecursion && defSetsE.length === 0 && helpersE !== ''
|
|
3583
|
+
if (hoistable) {
|
|
3584
|
+
const factoryBody = helpersE + 'return function(d,_all){const _e=[];\n ' + checkStr +
|
|
3585
|
+
'\n return{valid:_e.length===0,errors:_e}}'
|
|
3586
|
+
const params = ctx.usesSafeRe ? [...cvars, '__ataSafeRe'] : [...cvars]
|
|
3587
|
+
const args = ctx.usesSafeRe ? [...cvals, compileSafe] : [...cvals]
|
|
3588
|
+
fn = new Function(...params, factoryBody)(...args)
|
|
3589
|
+
} else if (ctx.usesSafeRe) {
|
|
3516
3590
|
// Inlined helperCode references __ataSafeRe; bind the safe-regex factory.
|
|
3517
3591
|
// Standalone keeps the inline _errSource — the embed defines __ataSafeRe.
|
|
3518
3592
|
const built = new Function(...cvars, '__ataSafeRe', 'd', '_all', body)
|
|
@@ -4124,7 +4198,7 @@ function genCodeE(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
|
4124
4198
|
if (schema.contains !== undefined) {
|
|
4125
4199
|
const ci = ctx.varCounter++
|
|
4126
4200
|
const subLines = []
|
|
4127
|
-
|
|
4201
|
+
nestedGenCode(schema.contains, `_cv`, subLines, ctx)
|
|
4128
4202
|
const fnBody = subLines.length === 0 ? `return true` : `${subLines.join(';')};return true`
|
|
4129
4203
|
const minC = schema.minContains !== undefined ? schema.minContains : 1
|
|
4130
4204
|
const maxC = schema.maxContains
|
|
@@ -4156,7 +4230,7 @@ function genCodeE(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
|
4156
4230
|
// not
|
|
4157
4231
|
if (schema.not !== undefined) {
|
|
4158
4232
|
const subLines = []
|
|
4159
|
-
|
|
4233
|
+
nestedGenCode(schema.not, '_nv', subLines, ctx)
|
|
4160
4234
|
const nfn = subLines.length === 0 ? `function(_nv){return true}` : `function(_nv){${subLines.join(';')};return true}`
|
|
4161
4235
|
const fi = ctx.varCounter++
|
|
4162
4236
|
lines.push(`{const _nf${fi}=${nfn};if(_nf${fi}(${v})){${fail('not', 'not', '{}', "'must NOT be valid'")}}}`)
|
|
@@ -4165,7 +4239,7 @@ function genCodeE(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
|
4165
4239
|
// if/then/else
|
|
4166
4240
|
if (schema.if !== undefined) {
|
|
4167
4241
|
const ifLines = []
|
|
4168
|
-
|
|
4242
|
+
nestedGenCode(schema.if, '_iv', ifLines, ctx)
|
|
4169
4243
|
const fi = ctx.varCounter++
|
|
4170
4244
|
const ifFn = ifLines.length === 0
|
|
4171
4245
|
? `function(_iv){return true}`
|
|
@@ -4270,22 +4344,34 @@ function compileToJSCombined(schema, VALID_RESULT, schemaMap, userFormats) {
|
|
|
4270
4344
|
const ctx = { varCounter: 0, helperCode: [], shared: [], closureVars: ['_cpLen'], closureVals: [_cpLen],
|
|
4271
4345
|
rootDefs: cRootDefs, refStack: new Set(), schemaMap: schemaMap || null, anchors: cAnchors, rootSchema: schema, userFormats: userFormats || null }
|
|
4272
4346
|
const lines = []
|
|
4273
|
-
|
|
4347
|
+
try {
|
|
4348
|
+
genCodeC(schema, 'd', '', lines, ctx, '#')
|
|
4349
|
+
} catch (e) {
|
|
4350
|
+
if (e === DECLINE) return null
|
|
4351
|
+
throw e
|
|
4352
|
+
}
|
|
4274
4353
|
if (lines.length === 0) return () => VALID_RESULT
|
|
4275
4354
|
|
|
4276
|
-
// Use factory pattern: closure vars (regexes, etc.) created once, not per call
|
|
4277
|
-
|
|
4278
|
-
//
|
|
4355
|
+
// Use factory pattern: closure vars (regexes, etc.) created once, not per call.
|
|
4356
|
+
// The helpers are constants too and are declared in the factory: inside the
|
|
4357
|
+
// function they were rebuilt on every call. `__ataSafeRe` is bound when a
|
|
4358
|
+
// helper compiles a pattern through it, which the boolean generator does for
|
|
4359
|
+
// subschemas under not/if/contains; without the binding those threw a
|
|
4360
|
+
// ReferenceError on the first document that reached them.
|
|
4361
|
+
const closureNames = ctx.usesSafeRe ? [...ctx.closureVars, '__ataSafeRe'] : ctx.closureVars
|
|
4362
|
+
const closureArgs = ctx.usesSafeRe ? [...ctx.closureVals, compileSafe] : ctx.closureVals
|
|
4363
|
+
const closureParams = closureNames.join(',')
|
|
4364
|
+
const helpers = ctx.helperCode.length ? ctx.helperCode.join('\n ') + '\n ' : ''
|
|
4365
|
+
// Lazy error array: no allocation for valid data (the common case)
|
|
4279
4366
|
const inner = `let _e;\n ` +
|
|
4280
|
-
(ctx.helperCode.length ? ctx.helperCode.join('\n ') + '\n ' : '') +
|
|
4281
4367
|
lines.join('\n ') +
|
|
4282
4368
|
`\n return _e?{valid:false,errors:_e}:R`
|
|
4283
4369
|
|
|
4284
4370
|
try {
|
|
4285
|
-
if (typeof process !== 'undefined' && process.env && process.env.ATA_DUMP_CODEGEN) console.log('=== COMBINED CODEGEN ===\n' + inner + '\n=== CLOSURE VARS: ' + ctx.closureVars.length + ' ===')
|
|
4371
|
+
if (typeof process !== 'undefined' && process.env && process.env.ATA_DUMP_CODEGEN) console.log('=== COMBINED CODEGEN ===\n' + helpers + inner + '\n=== CLOSURE VARS: ' + ctx.closureVars.length + ' ===')
|
|
4286
4372
|
const factory = new Function('R' + (closureParams ? ',' + closureParams : ''),
|
|
4287
|
-
|
|
4288
|
-
return factory(VALID_RESULT, ...
|
|
4373
|
+
`${helpers}return function(d){${inner}}`)
|
|
4374
|
+
return factory(VALID_RESULT, ...closureArgs)
|
|
4289
4375
|
} catch (e) {
|
|
4290
4376
|
if (typeof process !== 'undefined' && process.env && process.env.ATA_DEBUG) console.error('compileToJSCombined error:', e.message, '\n', inner.slice(0, 500))
|
|
4291
4377
|
return null
|
|
@@ -4357,17 +4443,17 @@ function genCodeC(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
|
4357
4443
|
const anchorKey = schema.$dynamicRef.startsWith('#') ? schema.$dynamicRef : '#' + schema.$dynamicRef
|
|
4358
4444
|
if (ctx.anchors && ctx.anchors[anchorKey]) {
|
|
4359
4445
|
const target = ctx.anchors[anchorKey]
|
|
4360
|
-
|
|
4361
|
-
|
|
4362
|
-
|
|
4363
|
-
|
|
4364
|
-
|
|
4365
|
-
|
|
4366
|
-
|
|
4367
|
-
|
|
4368
|
-
|
|
4369
|
-
|
|
4370
|
-
|
|
4446
|
+
// This generator has no named functions, so it cannot follow a reference
|
|
4447
|
+
// back to the root or around a cycle. It used to emit nothing in both
|
|
4448
|
+
// cases, which the function then answered as valid: a document the other
|
|
4449
|
+
// generators reject passed through validateJSON. It declines instead,
|
|
4450
|
+
// and the schema takes the error generator.
|
|
4451
|
+
if (target === ctx.rootSchema) throw DECLINE
|
|
4452
|
+
const refKey = '$dynamicRef:' + anchorKey
|
|
4453
|
+
if (ctx.refStack.has(refKey)) throw DECLINE
|
|
4454
|
+
ctx.refStack.add(refKey)
|
|
4455
|
+
genCodeC(target, v, pathExpr, lines, ctx, schemaPrefix)
|
|
4456
|
+
ctx.refStack.delete(refKey)
|
|
4371
4457
|
}
|
|
4372
4458
|
}
|
|
4373
4459
|
|
|
@@ -4850,7 +4936,7 @@ function genCodeC(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
|
4850
4936
|
if (schema.contains !== undefined) {
|
|
4851
4937
|
const ci = ctx.varCounter++
|
|
4852
4938
|
const subLines = []
|
|
4853
|
-
|
|
4939
|
+
nestedGenCode(schema.contains, `_cv`, subLines, ctx)
|
|
4854
4940
|
const fnBody = subLines.length === 0 ? `return true` : `${subLines.join(';')};return true`
|
|
4855
4941
|
const minC = schema.minContains !== undefined ? schema.minContains : 1
|
|
4856
4942
|
const maxC = schema.maxContains
|
|
@@ -4866,20 +4952,20 @@ function genCodeC(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
|
4866
4952
|
// anyOf
|
|
4867
4953
|
if (schema.anyOf) {
|
|
4868
4954
|
const fi = ctx.varCounter++
|
|
4869
|
-
const fns = schema.anyOf.map(sub => { const sl = [];
|
|
4955
|
+
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}` })
|
|
4870
4956
|
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'")}}}`)
|
|
4871
4957
|
}
|
|
4872
4958
|
|
|
4873
4959
|
// oneOf
|
|
4874
4960
|
if (schema.oneOf) {
|
|
4875
4961
|
const fi = ctx.varCounter++
|
|
4876
|
-
const fns = schema.oneOf.map(sub => { const sl = [];
|
|
4962
|
+
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}` })
|
|
4877
4963
|
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'")}}}`)
|
|
4878
4964
|
}
|
|
4879
4965
|
|
|
4880
4966
|
// not
|
|
4881
4967
|
if (schema.not !== undefined) {
|
|
4882
|
-
const sl = [];
|
|
4968
|
+
const sl = []; nestedGenCode(schema.not, '_nv', sl, ctx)
|
|
4883
4969
|
const nfn = sl.length === 0 ? `function(_nv){return true}` : `function(_nv){${sl.join(';')};return true}`
|
|
4884
4970
|
const fi = ctx.varCounter++
|
|
4885
4971
|
lines.push(`{const _nf${fi}=${nfn};if(_nf${fi}(${v})){${fail('not', 'not', '{}', "'must NOT be valid'")}}}`)
|
|
@@ -4887,7 +4973,7 @@ function genCodeC(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
|
4887
4973
|
|
|
4888
4974
|
// if/then/else
|
|
4889
4975
|
if (schema.if !== undefined) {
|
|
4890
|
-
const sl = [];
|
|
4976
|
+
const sl = []; nestedGenCode(schema.if, '_iv', sl, ctx)
|
|
4891
4977
|
const fi = ctx.varCounter++
|
|
4892
4978
|
const ifFn = sl.length === 0 ? `function(_iv){return true}` : `function(_iv){${sl.join(';')};return true}`
|
|
4893
4979
|
lines.push(`{const _if${fi}=${ifFn}`)
|
package/lib/t.js
CHANGED
|
@@ -15,15 +15,15 @@ const OPTIONAL = Symbol.for('ata.t.optional');
|
|
|
15
15
|
const { attach: attachRefine } = require('./refine');
|
|
16
16
|
|
|
17
17
|
function string(opts) {
|
|
18
|
-
return
|
|
18
|
+
return opts === undefined ? { type: 'string' } : { type: 'string', ...opts };
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
function number(opts) {
|
|
22
|
-
return
|
|
22
|
+
return opts === undefined ? { type: 'number' } : { type: 'number', ...opts };
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
function integer(opts) {
|
|
26
|
-
return
|
|
26
|
+
return opts === undefined ? { type: 'integer' } : { type: 'integer', ...opts };
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
function boolean() {
|
|
@@ -47,7 +47,7 @@ function enumOf(values) {
|
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
function array(items, opts) {
|
|
50
|
-
return
|
|
50
|
+
return opts === undefined ? { type: 'array', items } : { type: 'array', items, ...opts };
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
function tuple(items, opts) {
|
|
@@ -63,26 +63,46 @@ function tuple(items, opts) {
|
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
function record(values, opts) {
|
|
66
|
-
return
|
|
66
|
+
return opts === undefined ? { type: 'object', additionalProperties: values } : { type: 'object', additionalProperties: values, ...opts };
|
|
67
67
|
}
|
|
68
68
|
|
|
69
69
|
function object(properties, opts) {
|
|
70
|
-
|
|
71
|
-
|
|
70
|
+
// The common case has no optional member: then `properties` is copied as it
|
|
71
|
+
// is and the fresh key list is `required`. The values come from one
|
|
72
|
+
// Object.values call because reading them back key by key was the larger
|
|
73
|
+
// part of this function's cost. A member marked optional sends the whole
|
|
74
|
+
// object down the slower path that rewrites it.
|
|
72
75
|
const keys = Object.keys(properties);
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
76
|
+
const values = Object.values(properties);
|
|
77
|
+
let anyOptional = false;
|
|
78
|
+
for (let i = 0; i < values.length; i++) {
|
|
79
|
+
const schema = values[i];
|
|
80
|
+
if (schema && schema[OPTIONAL] === true) { anyOptional = true; break; }
|
|
81
|
+
}
|
|
82
|
+
let out;
|
|
83
|
+
if (!anyOptional) {
|
|
84
|
+
out = keys.length
|
|
85
|
+
? { type: 'object', properties: { ...properties }, required: keys }
|
|
86
|
+
: { type: 'object', properties: { ...properties } };
|
|
87
|
+
} else {
|
|
88
|
+
const props = {};
|
|
89
|
+
const required = [];
|
|
90
|
+
for (let i = 0; i < keys.length; i++) {
|
|
91
|
+
const key = keys[i];
|
|
92
|
+
const schema = values[i];
|
|
93
|
+
if (schema && schema[OPTIONAL] === true) {
|
|
94
|
+
const { [OPTIONAL]: _drop, ...rest } = schema;
|
|
95
|
+
props[key] = rest;
|
|
96
|
+
} else {
|
|
97
|
+
props[key] = schema;
|
|
98
|
+
required.push(key);
|
|
99
|
+
}
|
|
81
100
|
}
|
|
101
|
+
out = required.length
|
|
102
|
+
? { type: 'object', properties: props, required }
|
|
103
|
+
: { type: 'object', properties: props };
|
|
82
104
|
}
|
|
83
|
-
|
|
84
|
-
if (required.length) out.required = required;
|
|
85
|
-
return Object.assign(out, opts);
|
|
105
|
+
return opts === undefined ? out : Object.assign(out, opts);
|
|
86
106
|
}
|
|
87
107
|
|
|
88
108
|
function optional(schema) {
|
package/lib/version.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ata-validator",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.31.1",
|
|
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_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_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.
|
|
125
|
-
"@ata-validator/native-darwin-x64": "1.
|
|
126
|
-
"@ata-validator/native-linux-arm64-gnu": "1.
|
|
127
|
-
"@ata-validator/native-linux-arm64-musl": "1.
|
|
128
|
-
"@ata-validator/native-linux-x64-gnu": "1.
|
|
129
|
-
"@ata-validator/native-linux-x64-musl": "1.
|
|
130
|
-
"@ata-validator/native-win32-x64": "1.
|
|
124
|
+
"@ata-validator/native-darwin-arm64": "1.31.1",
|
|
125
|
+
"@ata-validator/native-darwin-x64": "1.31.1",
|
|
126
|
+
"@ata-validator/native-linux-arm64-gnu": "1.31.1",
|
|
127
|
+
"@ata-validator/native-linux-arm64-musl": "1.31.1",
|
|
128
|
+
"@ata-validator/native-linux-x64-gnu": "1.31.1",
|
|
129
|
+
"@ata-validator/native-linux-x64-musl": "1.31.1",
|
|
130
|
+
"@ata-validator/native-win32-x64": "1.31.1"
|
|
131
131
|
},
|
|
132
132
|
"peerDependencies": {
|
|
133
133
|
"yaml": "^2.0.0"
|