ata-validator 1.34.0 → 1.35.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -15
- package/compiled.d.ts +17 -0
- package/compiled.js +7 -0
- package/compiled.mjs +3 -0
- package/index.browser.mjs +3 -2
- package/index.js +606 -2991
- package/index.mjs +1 -1
- package/lib/aot-build.js +40 -0
- package/lib/buffer-gate.js +0 -1
- package/lib/compiled.js +84 -0
- package/lib/enrich-error.js +7 -0
- package/lib/formats-source.js +323 -0
- package/lib/formats.js +1 -317
- package/lib/interpreter.js +4 -4
- package/lib/js-compiler.js +5 -5
- package/lib/rejections.js +309 -0
- package/lib/validator-core.js +2052 -0
- package/lib/version.js +1 -1
- package/lib/vocabularies.js +1 -2
- package/package.json +17 -9
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// The rejection objects validate() and validateJSON() return, and the helpers
|
|
4
|
+
// that build their errors on first read: declaration-order sorting,
|
|
5
|
+
// enrichment, typo correlation and source frames. Shared by the validator core
|
|
6
|
+
// and by the wrapper around a compiled module, so both report errors through
|
|
7
|
+
// the same code.
|
|
8
|
+
|
|
9
|
+
const { ordinalFor: schemaOrdinal } = require('./schema-order');
|
|
10
|
+
const { setDiagnosticSource: attachDiagnosticSource } = require('./diagnostic-source');
|
|
11
|
+
|
|
12
|
+
// Rejection result with errors materialized on first read. The accessor
|
|
13
|
+
// lives on the prototype so constructing one is a plain allocation; an
|
|
14
|
+
// object-literal getter would create a closure and define an accessor
|
|
15
|
+
// property on every rejection, which showed up as the single largest cost
|
|
16
|
+
// on the rejection path. `toJSON` keeps JSON.stringify output identical to
|
|
17
|
+
// the eager shape. Note for tests: deepStrictEqual against a plain object
|
|
18
|
+
// compares prototypes; read `.errors` and compare that.
|
|
19
|
+
class LazyRejection {
|
|
20
|
+
constructor(build, data, buildRaw) {
|
|
21
|
+
this.valid = false;
|
|
22
|
+
this._build = build;
|
|
23
|
+
this._data = data;
|
|
24
|
+
this._errors = null;
|
|
25
|
+
this._buildRaw = buildRaw;
|
|
26
|
+
}
|
|
27
|
+
toJSON() {
|
|
28
|
+
return { valid: false, errors: this.errors };
|
|
29
|
+
}
|
|
30
|
+
// Raw shape for consumers that carry only message and path, such as the
|
|
31
|
+
// Standard Schema bridge: schema order, no enrichment. Reading `errors`
|
|
32
|
+
// afterwards still enriches through its own build.
|
|
33
|
+
_ataRaw() {
|
|
34
|
+
return this._buildRaw ? this._buildRaw(this._data) : this.errors;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
Object.defineProperty(LazyRejection.prototype, 'errors', {
|
|
38
|
+
enumerable: true,
|
|
39
|
+
configurable: true,
|
|
40
|
+
get() {
|
|
41
|
+
if (this._errors === null) this._errors = this._build(this._data);
|
|
42
|
+
return this._errors;
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
let _enrichFn = null;
|
|
47
|
+
function _enrichLazy(e, opts) {
|
|
48
|
+
if (_enrichFn === null) _enrichFn = require('./enrich-error').enrich;
|
|
49
|
+
return _enrichFn(e, opts);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// The rejection the rich-errors wrapper returns. `errors` is built on first
|
|
53
|
+
// read and cached; `_ataRaw()` is the raw, schema-ordered list for consumers
|
|
54
|
+
// that carry only message and path, such as the Standard Schema bridge.
|
|
55
|
+
// Prototype accessors, not per-instance ones: see LazyRejection.
|
|
56
|
+
class RichRejection {
|
|
57
|
+
// `rawInput` is the JSON text validateJSON was given, or null for validate(data).
|
|
58
|
+
// The position map it implies is built in the `errors` getter, not here: it is
|
|
59
|
+
// a full walk of the document, it is only ever read through an error's
|
|
60
|
+
// dataFrame, and building it on every rejection cost a caller that reads
|
|
61
|
+
// `.valid` about 460 microseconds on a 50 KB document. Holding the text rather
|
|
62
|
+
// than reading `self._lastRawInput` later also keeps the result independent of
|
|
63
|
+
// what the instance does after this call returns.
|
|
64
|
+
constructor(result, data, rawInput, self, root, enrich) {
|
|
65
|
+
this.valid = false;
|
|
66
|
+
this._result = result;
|
|
67
|
+
this._data = data;
|
|
68
|
+
this._rawInput = rawInput;
|
|
69
|
+
this._self = self;
|
|
70
|
+
this._root = root;
|
|
71
|
+
this._enrich = enrich;
|
|
72
|
+
this._cached = null;
|
|
73
|
+
}
|
|
74
|
+
toJSON() {
|
|
75
|
+
return { valid: false, errors: this.errors };
|
|
76
|
+
}
|
|
77
|
+
_ataRaw() {
|
|
78
|
+
let raw = this._result.errors || [];
|
|
79
|
+
if (raw.length > 1) raw = sortErrorsBySchemaOrder(this._root, raw);
|
|
80
|
+
return raw;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
Object.defineProperty(RichRejection.prototype, 'errors', {
|
|
84
|
+
enumerable: true,
|
|
85
|
+
configurable: true,
|
|
86
|
+
get() {
|
|
87
|
+
if (this._cached === null) {
|
|
88
|
+
const self = this._self;
|
|
89
|
+
const enrich = this._enrich;
|
|
90
|
+
let raw = this._result.errors || [];
|
|
91
|
+
if (raw.length > 1) raw = sortErrorsBySchemaOrder(this._root, raw);
|
|
92
|
+
// The position map, resolved now that an error is actually being read.
|
|
93
|
+
let positions = null;
|
|
94
|
+
if (enrich && raw.length && this._rawInput != null) {
|
|
95
|
+
positions = self._pos().targeted(this._rawInput, wantedPointersFor(raw));
|
|
96
|
+
if (positions) self._posCache.reset();
|
|
97
|
+
}
|
|
98
|
+
// One options object for the whole list, not one per error.
|
|
99
|
+
const opts = enrich && raw.length
|
|
100
|
+
? {
|
|
101
|
+
data: this._data,
|
|
102
|
+
positions,
|
|
103
|
+
schemaPositions: self._schemaPositions,
|
|
104
|
+
schemaFile: self._source ? self._source.path : undefined,
|
|
105
|
+
}
|
|
106
|
+
: null;
|
|
107
|
+
const cached = opts
|
|
108
|
+
? raw.map((e) => enrich(e, opts))
|
|
109
|
+
// The v0.14 shape is a fixed key set; the ordering key the
|
|
110
|
+
// generated code carries is dropped from it here.
|
|
111
|
+
: raw.map(stripOrdinal);
|
|
112
|
+
// Correlation is published, never applied. Both halves of a typo pair
|
|
113
|
+
// stay in the array; `related` only says they are one mistake, so a
|
|
114
|
+
// wrong pairing costs a sentence rather than a hidden violation.
|
|
115
|
+
if (enrich && cached.length > 1) attachRelated(cached);
|
|
116
|
+
// No diagnostic payload here. validate(data) is the library hot path,
|
|
117
|
+
// and attaching one cost about 100 ns per rejection for a consumer
|
|
118
|
+
// that never renders. The text path attaches it, and a renderer given
|
|
119
|
+
// `{ data }` builds frames for object input on request.
|
|
120
|
+
this._cached = cached;
|
|
121
|
+
}
|
|
122
|
+
return this._cached;
|
|
123
|
+
},
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
// The rejection validateJSON returns. Everything the text path adds over
|
|
127
|
+
// validate(data), the value tree enrichment needs, the position map, the
|
|
128
|
+
// diagnostic payload, happens on first access to `.errors`. Reading `.valid`
|
|
129
|
+
// touches none of it. The JSON text is held here rather than read back off the
|
|
130
|
+
// validator, so the result does not depend on what the instance does next.
|
|
131
|
+
class LazyJsonRejection {
|
|
132
|
+
constructor(result, jsonStr, self, enrich) {
|
|
133
|
+
this.valid = false;
|
|
134
|
+
this._result = result;
|
|
135
|
+
this._jsonStr = jsonStr;
|
|
136
|
+
this._self = self;
|
|
137
|
+
this._enrich = enrich;
|
|
138
|
+
this._cached = null;
|
|
139
|
+
}
|
|
140
|
+
toJSON() {
|
|
141
|
+
return { valid: false, errors: this.errors };
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
Object.defineProperty(LazyJsonRejection.prototype, 'errors', {
|
|
145
|
+
enumerable: true,
|
|
146
|
+
configurable: true,
|
|
147
|
+
get() {
|
|
148
|
+
if (this._cached !== null) return this._cached;
|
|
149
|
+
const self = this._self;
|
|
150
|
+
const enrich = this._enrich;
|
|
151
|
+
const jsonStr = this._jsonStr;
|
|
152
|
+
// Reading the inner errors realizes the inner lazy layer, if there was one.
|
|
153
|
+
const raw = this._result.errors || [];
|
|
154
|
+
if (!raw.length) { this._cached = raw; return raw; }
|
|
155
|
+
|
|
156
|
+
// The enrich pass plucks `received` from the value tree and the suggestion
|
|
157
|
+
// engine (required-typo, format hints, coercion nudges) walks it too.
|
|
158
|
+
let parsedData;
|
|
159
|
+
try { parsedData = JSON.parse(jsonStr); } catch { parsedData = undefined; }
|
|
160
|
+
// What was checked is the document after defaults, coercion and removal,
|
|
161
|
+
// so `received` has to come from that, as it does for validate(). A
|
|
162
|
+
// default that failed its own schema has no value in the text at all.
|
|
163
|
+
if (parsedData !== undefined && self._preprocess) self._preprocess(parsedData);
|
|
164
|
+
|
|
165
|
+
// Errors the inner path already enriched carry a `received` key: enrich()
|
|
166
|
+
// always sets one, even when there is no value to show. `code` is not a
|
|
167
|
+
// safe signal, because branch-collapse attaches codes to raw errors, and
|
|
168
|
+
// `docUrl` is not either, because the generated error functions stamp it
|
|
169
|
+
// on raw errors; taking it as the signal left those errors without
|
|
170
|
+
// `received` or a suggestion on the text path while validate() had both.
|
|
171
|
+
if (!raw[0] || !('received' in raw[0])) {
|
|
172
|
+
const positions = self._pos().targeted(jsonStr, wantedPointersFor(raw));
|
|
173
|
+
// Declaration order, as validate() applies it; the text path used to
|
|
174
|
+
// enrich in emission order.
|
|
175
|
+
const ordered = raw.length > 1 ? sortErrorsBySchemaOrder(self._schemaObj, raw) : raw;
|
|
176
|
+
const enrichOpts = {
|
|
177
|
+
data: parsedData,
|
|
178
|
+
positions,
|
|
179
|
+
schemaPositions: self._schemaPositions,
|
|
180
|
+
schemaFile: self._source ? self._source.path : undefined,
|
|
181
|
+
};
|
|
182
|
+
const enriched = ordered.map((e) => enrich(e, enrichOpts));
|
|
183
|
+
if (enriched.length > 1) attachRelated(enriched);
|
|
184
|
+
attachDiagnosticSource(enriched, {
|
|
185
|
+
data: parsedData,
|
|
186
|
+
text: jsonStr,
|
|
187
|
+
positions,
|
|
188
|
+
schema: self._schemaObj,
|
|
189
|
+
mutatesInput: self._mutatesInput === true,
|
|
190
|
+
});
|
|
191
|
+
if (positions) self._posCache.reset();
|
|
192
|
+
this._cached = enriched;
|
|
193
|
+
return enriched;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Already enriched, so the frames are usually attached too. Only a gap in
|
|
197
|
+
// them is worth another walk of the document: resolving the map to discover
|
|
198
|
+
// there was nothing to fill cost a second full walk on every rejection.
|
|
199
|
+
if (raw.some((e) => e && !e.dataFrame)) {
|
|
200
|
+
const positions = self._pos().targeted(jsonStr, wantedPointersFor(raw.filter((e) => e && !e.dataFrame)));
|
|
201
|
+
if (positions) {
|
|
202
|
+
for (const e of raw) {
|
|
203
|
+
if (e && !e.dataFrame) {
|
|
204
|
+
const path = e.path != null ? e.path : (e.instancePath || '');
|
|
205
|
+
const p = positions[path];
|
|
206
|
+
if (p) e.dataFrame = { byteOffset: p.byteOffset, length: p.length, line: p.line, col: p.col, text: p.text };
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
self._posCache.reset();
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
if (raw.length > 1) attachRelated(raw);
|
|
213
|
+
attachDiagnosticSource(raw, {
|
|
214
|
+
data: parsedData,
|
|
215
|
+
text: jsonStr,
|
|
216
|
+
schema: self._schemaObj,
|
|
217
|
+
mutatesInput: self._mutatesInput === true,
|
|
218
|
+
});
|
|
219
|
+
this._cached = raw;
|
|
220
|
+
return raw;
|
|
221
|
+
},
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
// The pointers a set of errors will ask the position map about. lib/enrich-error
|
|
225
|
+
// looks up the error's own path, and for an additional or unevaluated property
|
|
226
|
+
// the child pointer named in `params`, unescaped, which is the form it asks for.
|
|
227
|
+
// The escaped form goes in too: including a pointer that is never read costs
|
|
228
|
+
// nothing, and the cache answers anything outside this set from the full map
|
|
229
|
+
// rather than reporting no position.
|
|
230
|
+
function wantedPointersFor (errors) {
|
|
231
|
+
const wanted = new Set();
|
|
232
|
+
for (const e of errors) {
|
|
233
|
+
if (!e) continue;
|
|
234
|
+
const path = e.path != null ? e.path : (e.instancePath || '');
|
|
235
|
+
wanted.add(path);
|
|
236
|
+
if (e.instancePath != null && e.instancePath !== path) wanted.add(e.instancePath);
|
|
237
|
+
const params = e.params;
|
|
238
|
+
const named = params && (params.additionalProperty || params.unevaluatedProperty);
|
|
239
|
+
if (typeof named === 'string') {
|
|
240
|
+
wanted.add(path + '/' + named);
|
|
241
|
+
if (named.indexOf('~') !== -1 || named.indexOf('/') !== -1) {
|
|
242
|
+
wanted.add(path + '/' + named.replace(/~/g, '~0').replace(/\//g, '~1'));
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
return wanted;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// A raw error without the `_o` ordering key, for the legacy error shape.
|
|
250
|
+
function stripOrdinal(e) {
|
|
251
|
+
if (e === null || typeof e !== 'object' || e._o === undefined) return e;
|
|
252
|
+
const out = {};
|
|
253
|
+
for (const k in e) if (k !== '_o') out[k] = e[k];
|
|
254
|
+
return out;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Errors in schema declaration order. Each error's key is its schemaPath's
|
|
258
|
+
// pre-order ordinal in the root schema: written into the literal by the code
|
|
259
|
+
// generator (`_o`), looked up once per path otherwise. Most rejections come
|
|
260
|
+
// out already ordered, and those return without sorting or allocating.
|
|
261
|
+
function sortErrorsBySchemaOrder(rootSchema, errors) {
|
|
262
|
+
const n = errors.length;
|
|
263
|
+
const keys = new Array(n);
|
|
264
|
+
let sorted = true;
|
|
265
|
+
let prev = -1;
|
|
266
|
+
for (let i = 0; i < n; i++) {
|
|
267
|
+
const e = errors[i];
|
|
268
|
+
let o = typeof e._o === 'number' ? e._o : schemaOrdinal(rootSchema, e.schemaPath);
|
|
269
|
+
// An error with no place in this document (an appended custom-keyword
|
|
270
|
+
// error, a path into another schema) stays next to the error before it,
|
|
271
|
+
// which is where the rank comparison left it too.
|
|
272
|
+
if (o === null) o = prev < 0 ? 0 : prev;
|
|
273
|
+
keys[i] = o;
|
|
274
|
+
if (o < prev) sorted = false;
|
|
275
|
+
prev = o;
|
|
276
|
+
}
|
|
277
|
+
if (sorted) return errors;
|
|
278
|
+
// Error lists are short. A stable insertion sort over the integer keys
|
|
279
|
+
// moves the errors in tandem with no comparator calls and no index array.
|
|
280
|
+
const out = errors.slice();
|
|
281
|
+
for (let i = 1; i < n; i++) {
|
|
282
|
+
const k = keys[i];
|
|
283
|
+
const e = out[i];
|
|
284
|
+
let j = i - 1;
|
|
285
|
+
while (j >= 0 && keys[j] > k) { keys[j + 1] = keys[j]; out[j + 1] = out[j]; j--; }
|
|
286
|
+
keys[j + 1] = k;
|
|
287
|
+
out[j + 1] = e;
|
|
288
|
+
}
|
|
289
|
+
return out;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// Resolved once. A require() inside the function was re-resolving the path
|
|
293
|
+
// on every rejection, which the profile showed as internalModuleStat at the
|
|
294
|
+
// top of the reject path, above the correlation it was loading.
|
|
295
|
+
let _correlateTypos = null;
|
|
296
|
+
function attachRelated (errors) {
|
|
297
|
+
if (_correlateTypos === null) _correlateTypos = require('./correlate').correlateTypos;
|
|
298
|
+
const pairs = _correlateTypos(errors);
|
|
299
|
+
if (pairs === null) return errors;
|
|
300
|
+
for (const [from, to] of pairs) {
|
|
301
|
+
const e = errors[from];
|
|
302
|
+
if (!e) continue;
|
|
303
|
+
if (e.related) { if (!e.related.includes(to)) e.related.push(to); }
|
|
304
|
+
else e.related = [to];
|
|
305
|
+
}
|
|
306
|
+
return errors;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
module.exports = { LazyRejection, RichRejection, LazyJsonRejection, _enrichLazy, attachRelated, sortErrorsBySchemaOrder, stripOrdinal, wantedPointersFor };
|