ata-validator 1.38.0 → 1.39.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -9
- package/index.d.ts +2 -1
- package/index.js +12 -28
- package/lib/aot-impl.js +40 -15
- package/lib/buffer-gate.js +6 -7
- package/lib/enrich-error.js +4 -2
- package/lib/js-compiler.js +329 -46
- package/lib/rejections.js +84 -39
- package/lib/render-pretty.js +15 -11
- package/lib/safe-regex.js +424 -413
- package/lib/schema-order.js +18 -2
- package/lib/validator-core.js +99 -13
- package/lib/version.js +1 -1
- package/package.json +11 -11
- package/lib/safe-regex-source.js +0 -8
package/lib/rejections.js
CHANGED
|
@@ -76,53 +76,57 @@ class RichRejection {
|
|
|
76
76
|
}
|
|
77
77
|
_ataRaw() {
|
|
78
78
|
let raw = this._result.errors || [];
|
|
79
|
-
if (raw
|
|
79
|
+
if (needsOrdering(raw)) raw = sortErrorsBySchemaOrder(this._root, raw);
|
|
80
80
|
return raw;
|
|
81
81
|
}
|
|
82
82
|
}
|
|
83
|
+
// Orders a raw error list and gives it the shape the caller asked for: the
|
|
84
|
+
// legacy key set, or enriched. Shared by both rejection classes below.
|
|
85
|
+
function presentErrors(raw, data, rawInput, self, root, enrich) {
|
|
86
|
+
if (needsOrdering(raw)) raw = sortErrorsBySchemaOrder(root, raw);
|
|
87
|
+
// The position map, resolved now that an error is actually being read.
|
|
88
|
+
let positions = null;
|
|
89
|
+
if (enrich && raw.length && rawInput != null) {
|
|
90
|
+
positions = self._pos().targeted(rawInput, wantedPointersFor(raw));
|
|
91
|
+
if (positions) self._posCache.reset();
|
|
92
|
+
}
|
|
93
|
+
// One options object for the whole list, not one per error.
|
|
94
|
+
const opts = enrich && raw.length
|
|
95
|
+
? {
|
|
96
|
+
data,
|
|
97
|
+
positions,
|
|
98
|
+
schemaPositions: self._schemaPositions,
|
|
99
|
+
schemaFile: self._source ? self._source.path : undefined,
|
|
100
|
+
}
|
|
101
|
+
: null;
|
|
102
|
+
const cached = opts
|
|
103
|
+
? raw.map((e) => enrich(e, opts))
|
|
104
|
+
// The v0.14 shape is a fixed key set; the ordering key the
|
|
105
|
+
// generated code carries is dropped from it here.
|
|
106
|
+
: raw.map(stripOrdinal);
|
|
107
|
+
// Correlation is published, never applied. Both halves of a typo pair
|
|
108
|
+
// stay in the array; `related` only says they are one mistake, so a
|
|
109
|
+
// wrong pairing costs a sentence rather than a hidden violation.
|
|
110
|
+
if (enrich && cached.length > 1) attachRelated(cached);
|
|
111
|
+
// No diagnostic payload here. validate(data) is the library hot path,
|
|
112
|
+
// and attaching one cost about 100 ns per rejection for a consumer
|
|
113
|
+
// that never renders. The text path attaches it, and a renderer given
|
|
114
|
+
// `{ data }` builds frames for object input on request.
|
|
115
|
+
return cached;
|
|
116
|
+
}
|
|
117
|
+
|
|
83
118
|
Object.defineProperty(RichRejection.prototype, 'errors', {
|
|
84
119
|
enumerable: true,
|
|
85
120
|
configurable: true,
|
|
86
121
|
get() {
|
|
87
122
|
if (this._cached === null) {
|
|
88
|
-
|
|
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;
|
|
123
|
+
this._cached = presentErrors(this._result.errors || [], this._data, this._rawInput, this._self, this._root, this._enrich);
|
|
121
124
|
}
|
|
122
125
|
return this._cached;
|
|
123
126
|
},
|
|
124
127
|
});
|
|
125
128
|
|
|
129
|
+
|
|
126
130
|
// The rejection validateJSON returns. Everything the text path adds over
|
|
127
131
|
// validate(data), the value tree enrichment needs, the position map, the
|
|
128
132
|
// diagnostic payload, happens on first access to `.errors`. Reading `.valid`
|
|
@@ -172,7 +176,7 @@ Object.defineProperty(LazyJsonRejection.prototype, 'errors', {
|
|
|
172
176
|
const positions = self._pos().targeted(jsonStr, wantedPointersFor(raw));
|
|
173
177
|
// Declaration order, as validate() applies it; the text path used to
|
|
174
178
|
// enrich in emission order.
|
|
175
|
-
const ordered = raw
|
|
179
|
+
const ordered = needsOrdering(raw) ? sortErrorsBySchemaOrder(self._schemaObj, raw) : raw;
|
|
176
180
|
const enrichOpts = {
|
|
177
181
|
data: parsedData,
|
|
178
182
|
positions,
|
|
@@ -246,11 +250,26 @@ function wantedPointersFor (errors) {
|
|
|
246
250
|
return wanted;
|
|
247
251
|
}
|
|
248
252
|
|
|
249
|
-
// A raw error
|
|
253
|
+
// A raw error in the legacy shape. The error generator writes `code` and
|
|
254
|
+
// `docUrl` into its literals because a standalone module returns them as they
|
|
255
|
+
// are; the runtime's legacy shape never had them, and the interpreter leaves
|
|
256
|
+
// them out, so they are dropped here with the ordering key `_o`. A collapsed
|
|
257
|
+
// oneOf/anyOf error keeps its code, which both engines give it, and its
|
|
258
|
+
// branch errors get the same treatment, so the shape does not depend on
|
|
259
|
+
// which engine answered.
|
|
260
|
+
const COLLAPSE_CODES = new Set(['ATA4001', 'ATA4002', 'ATA4003']);
|
|
250
261
|
function stripOrdinal(e) {
|
|
251
|
-
if (e === null || typeof e !== 'object'
|
|
262
|
+
if (e === null || typeof e !== 'object') return e;
|
|
263
|
+
// Generated code keeps errors at a fixed path as frozen literals shared by
|
|
264
|
+
// every call. The caller gets its own copy, as it always has, so an error it
|
|
265
|
+
// edits (a translated message) is its own and editing it does not throw.
|
|
266
|
+
if (e._o === undefined && e.docUrl === undefined && (e.code === undefined || COLLAPSE_CODES.has(e.code)) && !e.branchErrors) return Object.isFrozen(e) ? { ...e } : e;
|
|
252
267
|
const out = {};
|
|
253
|
-
for (const k in e)
|
|
268
|
+
for (const k in e) {
|
|
269
|
+
if (k === '_o' || k === 'docUrl') continue;
|
|
270
|
+
if (k === 'code' && !COLLAPSE_CODES.has(e.code)) continue;
|
|
271
|
+
out[k] = k === 'branchErrors' && Array.isArray(e[k]) ? e[k].map(stripOrdinal) : e[k];
|
|
272
|
+
}
|
|
254
273
|
return out;
|
|
255
274
|
}
|
|
256
275
|
|
|
@@ -261,7 +280,33 @@ function stripOrdinal(e) {
|
|
|
261
280
|
// place in the target (rankFor), so they come out in the order they would
|
|
262
281
|
// inline, whichever engine produced them. Most rejections come out already
|
|
263
282
|
// ordered, and those return without sorting or allocating.
|
|
283
|
+
// A list of one can still hold a collapsed error whose branch errors, at any
|
|
284
|
+
// depth, need ordering.
|
|
285
|
+
function needsOrdering(raw) {
|
|
286
|
+
if (raw.length > 1) return true;
|
|
287
|
+
const e = raw[0];
|
|
288
|
+
return raw.length === 1 && e !== null && typeof e === 'object' && Array.isArray(e.branchErrors) && needsOrdering(e.branchErrors);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// A collapsed oneOf/anyOf error carries the closest branch's errors, which
|
|
292
|
+
// each engine produces in its own walk order. They go in schema order like the
|
|
293
|
+
// list they sit in, so which engine answered cannot be read off them.
|
|
294
|
+
function sortBranchErrors(rootSchema, errors) {
|
|
295
|
+
let out = null;
|
|
296
|
+
for (let i = 0; i < errors.length; i++) {
|
|
297
|
+
const e = errors[i];
|
|
298
|
+
const b = e !== null && typeof e === 'object' ? e.branchErrors : undefined;
|
|
299
|
+
if (!Array.isArray(b) || b.length === 0) continue;
|
|
300
|
+
const sb = sortErrorsBySchemaOrder(rootSchema, b);
|
|
301
|
+
if (sb === b) continue;
|
|
302
|
+
if (out === null) out = errors.slice();
|
|
303
|
+
out[i] = Object.assign({}, e, { branchErrors: sb });
|
|
304
|
+
}
|
|
305
|
+
return out === null ? errors : out;
|
|
306
|
+
}
|
|
307
|
+
|
|
264
308
|
function sortErrorsBySchemaOrder(rootSchema, errors) {
|
|
309
|
+
errors = sortBranchErrors(rootSchema, errors);
|
|
265
310
|
const n = errors.length;
|
|
266
311
|
const keys = new Array(n);
|
|
267
312
|
let sorted = true;
|
|
@@ -326,4 +371,4 @@ function attachRelated (errors) {
|
|
|
326
371
|
return errors;
|
|
327
372
|
}
|
|
328
373
|
|
|
329
|
-
module.exports = { LazyRejection, RichRejection, LazyJsonRejection, _enrichLazy, attachRelated, sortErrorsBySchemaOrder, stripOrdinal, wantedPointersFor };
|
|
374
|
+
module.exports = { LazyRejection, RichRejection, presentErrors, needsOrdering, LazyJsonRejection, _enrichLazy, attachRelated, sortErrorsBySchemaOrder, stripOrdinal, wantedPointersFor };
|
package/lib/render-pretty.js
CHANGED
|
@@ -42,7 +42,11 @@ function caretSuffix (d, raw, useColor) {
|
|
|
42
42
|
function renderOne (d, useColor, opts) {
|
|
43
43
|
const lines = [];
|
|
44
44
|
const width = terminalWidth();
|
|
45
|
-
|
|
45
|
+
// The bar sits one column past the widest line number, so the source line,
|
|
46
|
+
// the blank rows and the caret row share it and a caret lands on the first
|
|
47
|
+
// character of the value, not one column to its left.
|
|
48
|
+
const bar = (line) => ' '.repeat(Math.max(3, String(line).length + 1)) + '|';
|
|
49
|
+
const src = (line, text) => String(line).padStart(Math.max(2, String(line).length)) + ' | ' + text;
|
|
46
50
|
|
|
47
51
|
// Headline carries the code when there is one. A code-less error, which the
|
|
48
52
|
// LazyRejection fallback can produce, must not render as "error[undefined]".
|
|
@@ -57,12 +61,12 @@ function renderOne (d, useColor, opts) {
|
|
|
57
61
|
if (raw && raw.schemaSource) {
|
|
58
62
|
const f = trimCwd(raw.schemaSource.file, opts.cwd);
|
|
59
63
|
lines.push(` --> ${color(useColor, ANSI.cyan, `${f}:${raw.schemaSource.line}:${raw.schemaSource.col}`)}`);
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
lines.push(
|
|
64
|
+
const sl = raw.schemaSource.line;
|
|
65
|
+
lines.push(bar(sl));
|
|
66
|
+
lines.push(src(sl, truncateLine(raw.schemaSource.text, width - 8)));
|
|
63
67
|
const inlineHint = raw.expected ? ' ' + color(useColor, ANSI.dim, `expected ${raw.expected}`) : '';
|
|
64
|
-
lines.push(caretLine(raw.schemaSource.col, 1,
|
|
65
|
-
lines.push(
|
|
68
|
+
lines.push(caretLine(raw.schemaSource.col, 1, bar(sl).length - 1) + inlineHint);
|
|
69
|
+
lines.push(bar(sl));
|
|
66
70
|
}
|
|
67
71
|
|
|
68
72
|
// Data location, always, even without a frame. This is the single most
|
|
@@ -70,11 +74,11 @@ function renderOne (d, useColor, opts) {
|
|
|
70
74
|
if (d.frame) {
|
|
71
75
|
const where = `input:${d.frame.line}:${d.frame.col}`;
|
|
72
76
|
lines.push(` --> ${color(useColor, ANSI.cyan, where)} ${color(useColor, ANSI.dim, '(' + d.dotted + ')')}`);
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
lines.push(
|
|
76
|
-
lines.push(caretLine(d.frame.col, d.frame.length,
|
|
77
|
-
lines.push(
|
|
77
|
+
const fl = d.frame.line;
|
|
78
|
+
lines.push(bar(fl));
|
|
79
|
+
lines.push(src(fl, truncateLine(d.frame.text, width - 8)));
|
|
80
|
+
lines.push(caretLine(d.frame.col, d.frame.length, bar(fl).length - 1) + caretSuffix(d, raw, useColor));
|
|
81
|
+
lines.push(bar(fl));
|
|
78
82
|
} else {
|
|
79
83
|
lines.push(` --> ${color(useColor, ANSI.cyan, 'at ' + d.dotted)}`);
|
|
80
84
|
}
|