ata-validator 1.34.0 → 1.36.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/build.d.ts +23 -0
- package/build.mjs +4 -0
- package/compiled.d.ts +17 -0
- package/compiled.js +7 -0
- package/compiled.mjs +3 -0
- package/index.browser.mjs +3 -2
- package/index.d.ts +6 -0
- package/index.js +741 -3035
- package/index.mjs +1 -1
- package/index.node.mjs +10 -0
- package/lib/aot-build.js +46 -0
- package/lib/buffer-gate.js +0 -1
- package/lib/compiled.js +96 -0
- package/lib/defaults.js +88 -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 +216 -128
- package/lib/rejections.js +329 -0
- package/lib/schema-order.js +53 -3
- package/lib/validator-core.js +2031 -0
- package/lib/version.js +1 -1
- package/lib/vocabularies.js +1 -2
- package/package.json +24 -11
|
@@ -0,0 +1,329 @@
|
|
|
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, rankFor: schemaRank } = 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. Errors behind the same
|
|
260
|
+
// `$ref` share the ordinal of that `$ref`, and among themselves go by their
|
|
261
|
+
// place in the target (rankFor), so they come out in the order they would
|
|
262
|
+
// inline, whichever engine produced them. Most rejections come out already
|
|
263
|
+
// ordered, and those return without sorting or allocating.
|
|
264
|
+
function sortErrorsBySchemaOrder(rootSchema, errors) {
|
|
265
|
+
const n = errors.length;
|
|
266
|
+
const keys = new Array(n);
|
|
267
|
+
let sorted = true;
|
|
268
|
+
let prev = -1;
|
|
269
|
+
let prevPath = null;
|
|
270
|
+
for (let i = 0; i < n; i++) {
|
|
271
|
+
const e = errors[i];
|
|
272
|
+
let o = typeof e._o === 'number' ? e._o : schemaOrdinal(rootSchema, e.schemaPath);
|
|
273
|
+
// An error with no place in this document (an appended custom-keyword
|
|
274
|
+
// error, a path into another schema) stays next to the error before it,
|
|
275
|
+
// which is where the rank comparison left it too.
|
|
276
|
+
if (o === null) o = prev < 0 ? 0 : prev;
|
|
277
|
+
keys[i] = o;
|
|
278
|
+
if (o < prev || (o === prev && sorted && e.schemaPath !== prevPath && tieOrder(rootSchema, prevPath, e.schemaPath) > 0)) sorted = false;
|
|
279
|
+
prev = o;
|
|
280
|
+
prevPath = e.schemaPath;
|
|
281
|
+
}
|
|
282
|
+
if (sorted) return errors;
|
|
283
|
+
// Error lists are short. A stable insertion sort over the integer keys
|
|
284
|
+
// moves the errors in tandem with no index array; equal keys fall back to
|
|
285
|
+
// the rank only when the paths differ.
|
|
286
|
+
const out = errors.slice();
|
|
287
|
+
for (let i = 1; i < n; i++) {
|
|
288
|
+
const k = keys[i];
|
|
289
|
+
const e = out[i];
|
|
290
|
+
let j = i - 1;
|
|
291
|
+
while (j >= 0 && (keys[j] > k || (keys[j] === k && tieOrder(rootSchema, out[j].schemaPath, e.schemaPath) > 0))) {
|
|
292
|
+
keys[j + 1] = keys[j]; out[j + 1] = out[j]; j--;
|
|
293
|
+
}
|
|
294
|
+
keys[j + 1] = k;
|
|
295
|
+
out[j + 1] = e;
|
|
296
|
+
}
|
|
297
|
+
return out;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// Order of two paths with the same ordinal: positive when `a` belongs after
|
|
301
|
+
// `b`. Zero keeps them as they are, for identical paths and for paths
|
|
302
|
+
// outside this document.
|
|
303
|
+
function tieOrder(rootSchema, a, b) {
|
|
304
|
+
if (a === b) return 0;
|
|
305
|
+
const ra = schemaRank(rootSchema, a), rb = schemaRank(rootSchema, b);
|
|
306
|
+
if (ra === null || rb === null) return 0;
|
|
307
|
+
const m = Math.min(ra.length, rb.length);
|
|
308
|
+
for (let k = 0; k < m; k++) if (ra[k] !== rb[k]) return ra[k] - rb[k];
|
|
309
|
+
return ra.length - rb.length;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// Resolved once. A require() inside the function was re-resolving the path
|
|
313
|
+
// on every rejection, which the profile showed as internalModuleStat at the
|
|
314
|
+
// top of the reject path, above the correlation it was loading.
|
|
315
|
+
let _correlateTypos = null;
|
|
316
|
+
function attachRelated (errors) {
|
|
317
|
+
if (_correlateTypos === null) _correlateTypos = require('./correlate').correlateTypos;
|
|
318
|
+
const pairs = _correlateTypos(errors);
|
|
319
|
+
if (pairs === null) return errors;
|
|
320
|
+
for (const [from, to] of pairs) {
|
|
321
|
+
const e = errors[from];
|
|
322
|
+
if (!e) continue;
|
|
323
|
+
if (e.related) { if (!e.related.includes(to)) e.related.push(to); }
|
|
324
|
+
else e.related = [to];
|
|
325
|
+
}
|
|
326
|
+
return errors;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
module.exports = { LazyRejection, RichRejection, LazyJsonRejection, _enrichLazy, attachRelated, sortErrorsBySchemaOrder, stripOrdinal, wantedPointersFor };
|
package/lib/schema-order.js
CHANGED
|
@@ -11,6 +11,11 @@
|
|
|
11
11
|
// generator can compute it once per error site instead of the reader
|
|
12
12
|
// deriving it from the path string on every rejection.
|
|
13
13
|
//
|
|
14
|
+
// A path that runs on through a `$ref` names keys the referencing node does
|
|
15
|
+
// not have. Its ordinal is that of the `$ref` key, which several paths then
|
|
16
|
+
// share; its rank carries on inside the target, and the reader uses the rank
|
|
17
|
+
// to order the paths that share an ordinal.
|
|
18
|
+
//
|
|
14
19
|
// Both are memoized per root schema; a validator's root never changes.
|
|
15
20
|
|
|
16
21
|
const _keyIndexCache = new WeakMap();
|
|
@@ -56,12 +61,12 @@ function computeRank(rootSchema, schemaPath) {
|
|
|
56
61
|
const rank = [];
|
|
57
62
|
let node = rootSchema;
|
|
58
63
|
let start = 1;
|
|
64
|
+
let hops = 0;
|
|
59
65
|
while (start <= schemaPath.length) {
|
|
60
66
|
let end = schemaPath.indexOf('/', start);
|
|
61
67
|
if (end < 0) end = schemaPath.length;
|
|
62
68
|
if (end === start) { start = end + 1; continue; }
|
|
63
69
|
const seg = unescapePointerSegment(schemaPath.slice(start, end));
|
|
64
|
-
start = end + 1;
|
|
65
70
|
if (node == null || typeof node !== 'object') break;
|
|
66
71
|
if (Array.isArray(node)) {
|
|
67
72
|
const idx = Number(seg);
|
|
@@ -70,14 +75,53 @@ function computeRank(rootSchema, schemaPath) {
|
|
|
70
75
|
node = node[idx];
|
|
71
76
|
} else {
|
|
72
77
|
const idx = keyIndex(node, seg);
|
|
73
|
-
if (idx < 0)
|
|
78
|
+
if (idx < 0) {
|
|
79
|
+
// An evaluation path runs on through a `$ref` into its target without
|
|
80
|
+
// naming it. Ranking the rest inside the target, as if the target sat
|
|
81
|
+
// under the `$ref` key, orders errors behind a reference by the
|
|
82
|
+
// target's declaration order, as they are ordered inline.
|
|
83
|
+
const target = hops < 64 ? localTarget(rootSchema, node) : null;
|
|
84
|
+
if (target === null) break;
|
|
85
|
+
hops++;
|
|
86
|
+
rank.push(keyIndex(node, '$ref'));
|
|
87
|
+
node = target;
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
74
90
|
rank.push(idx);
|
|
75
91
|
node = node[seg];
|
|
76
92
|
}
|
|
93
|
+
start = end + 1;
|
|
77
94
|
}
|
|
78
95
|
return rank;
|
|
79
96
|
}
|
|
80
97
|
|
|
98
|
+
// The schema a node's `$ref` names, when it is a pointer into this document.
|
|
99
|
+
function localTarget(rootSchema, node) {
|
|
100
|
+
const ref = node.$ref;
|
|
101
|
+
if (typeof ref !== 'string' || ref.charCodeAt(0) !== 35) return null;
|
|
102
|
+
if (ref.length > 1 && ref.charCodeAt(1) !== 47) return null;
|
|
103
|
+
let t = rootSchema;
|
|
104
|
+
let start = 2;
|
|
105
|
+
while (start <= ref.length && ref.length > 1) {
|
|
106
|
+
let end = ref.indexOf('/', start);
|
|
107
|
+
if (end < 0) end = ref.length;
|
|
108
|
+
let seg = ref.slice(start, end);
|
|
109
|
+
if (seg.indexOf('%') >= 0) { try { seg = decodeURIComponent(seg); } catch { return null; } }
|
|
110
|
+
seg = unescapePointerSegment(seg);
|
|
111
|
+
if (t === null || typeof t !== 'object') return null;
|
|
112
|
+
// Normalization renames draft-07 `definitions` to `$defs` and leaves the
|
|
113
|
+
// pointers as written; the resolvers read either name as the other.
|
|
114
|
+
if (!Object.prototype.hasOwnProperty.call(t, seg)) {
|
|
115
|
+
const alias = seg === 'definitions' ? '$defs' : seg === '$defs' ? 'definitions' : null;
|
|
116
|
+
if (alias === null || !Object.prototype.hasOwnProperty.call(t, alias)) return null;
|
|
117
|
+
seg = alias;
|
|
118
|
+
}
|
|
119
|
+
t = t[seg];
|
|
120
|
+
start = end + 1;
|
|
121
|
+
}
|
|
122
|
+
return t !== null && typeof t === 'object' && t !== node ? t : null;
|
|
123
|
+
}
|
|
124
|
+
|
|
81
125
|
// Pre-order numbering of every node in the schema, keyed by its pointer as
|
|
82
126
|
// it appears in a schemaPath (segments escaped). Built once per root, on the
|
|
83
127
|
// first ask. Cyclic structures are guarded; a schema object can contain
|
|
@@ -115,13 +159,19 @@ function ordinalFor(rootSchema, schemaPath) {
|
|
|
115
159
|
// Walk back to the longest prefix that exists. Paths through a keyword's
|
|
116
160
|
// value that is a primitive, or through a key the schema does not have,
|
|
117
161
|
// land on the nearest enclosing node.
|
|
162
|
+
// A path that runs on through a `$ref` lands on its `$ref` key, so errors
|
|
163
|
+
// behind a reference sort where the `$ref` is declared among its siblings;
|
|
164
|
+
// rankFor orders them among themselves.
|
|
118
165
|
let p = schemaPath;
|
|
119
166
|
while (true) {
|
|
120
167
|
const cut = p.lastIndexOf('/');
|
|
121
168
|
if (cut < 0) return 0;
|
|
122
169
|
p = p.slice(0, cut);
|
|
123
170
|
const o = map.get(p);
|
|
124
|
-
if (o !== undefined)
|
|
171
|
+
if (o !== undefined) {
|
|
172
|
+
const r = map.get(p + '/$ref');
|
|
173
|
+
return r !== undefined ? r : o;
|
|
174
|
+
}
|
|
125
175
|
}
|
|
126
176
|
}
|
|
127
177
|
|