ata-validator 1.37.1 → 1.39.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.
@@ -0,0 +1,65 @@
1
+ 'use strict'
2
+
3
+ const { parse } = require('./safe-regex')
4
+
5
+ // True when the platform RegExp, a backtracking engine, also runs `src` in
6
+ // linear time, so it can take the pattern: every quantifier on one character,
7
+ // class or `.`; at most one unbounded, and then anchored with `^`; few bounded
8
+ // choices; only escapes both engines read alike; nothing outside the BMP, so
9
+ // both see the same UTF-16 units. tests/test_regex_native_linear.js holds it.
10
+ const NATIVE_ESCAPES = new Set(['d', 'D', 'w', 'W', 's', 'S', 'n', 'r', 't', 'f', 'v', '.', '\\', '/', '^', '$', '|', '?', '*', '+', '(', ')', '[', ']', '{', '}', '-', 'x', 'u'])
11
+ function nativeIsLinear (src) {
12
+ if (typeof src !== 'string' || src.length > 512) return false
13
+ for (let i = 0; i < src.length; i++) {
14
+ const c = src.charCodeAt(i)
15
+ if (c >= 0xd800 && c <= 0xdfff) return false
16
+ if (src[i] === '\\') {
17
+ const e = src[i + 1]
18
+ if (!NATIVE_ESCAPES.has(e)) return false
19
+ if (e === 'x' && !/^[0-9a-fA-F]{2}$/.test(src.slice(i + 2, i + 4))) return false
20
+ if (e === 'u' && !/^[0-9a-fA-F]{4}$/.test(src.slice(i + 2, i + 6))) return false
21
+ i++
22
+ }
23
+ }
24
+ let ast
25
+ try { ast = parse(src) } catch { return false }
26
+ let unbounded = 0
27
+ let bounded = 0
28
+ let ok = true
29
+ const atom = (n) => n.t === 'char' || n.t === 'class' || n.t === 'any'
30
+ const walk = (n) => {
31
+ if (!ok) return
32
+ switch (n.t) {
33
+ case 'star': case 'plus':
34
+ if (!atom(n.child)) ok = false
35
+ unbounded++
36
+ break
37
+ case 'quest':
38
+ if (!atom(n.child)) ok = false
39
+ bounded += 1
40
+ break
41
+ case 'repeat':
42
+ if (!atom(n.child)) ok = false
43
+ if (n.max === Infinity) unbounded++
44
+ else bounded += n.max - n.min
45
+ break
46
+ case 'group': walk(n.child); break
47
+ case 'concat': n.parts.forEach(walk); break
48
+ case 'alt': n.opts.forEach(walk); break
49
+ default: break
50
+ }
51
+ }
52
+ walk(ast)
53
+ if (!ok || unbounded > 1 || bounded > 64) return false
54
+ if (unbounded === 1) {
55
+ // Anchored at the start on every top-level branch.
56
+ let top = ast
57
+ while (top.t === 'group') top = top.child
58
+ const branches = top.t === 'alt' ? top.opts : [top]
59
+ const first = (n) => { while (n && n.t === 'group') n = n.child; return n && n.t === 'concat' ? first(n.parts[0]) : n }
60
+ if (!branches.every((b) => { const f = first(b); return f && f.t === 'bol' })) return false
61
+ }
62
+ return true
63
+ }
64
+
65
+ module.exports = { nativeIsLinear }
package/lib/rejections.js CHANGED
@@ -76,7 +76,7 @@ class RichRejection {
76
76
  }
77
77
  _ataRaw() {
78
78
  let raw = this._result.errors || [];
79
- if (raw.length > 1) raw = sortErrorsBySchemaOrder(this._root, raw);
79
+ if (needsOrdering(raw)) raw = sortErrorsBySchemaOrder(this._root, raw);
80
80
  return raw;
81
81
  }
82
82
  }
@@ -88,7 +88,7 @@ Object.defineProperty(RichRejection.prototype, 'errors', {
88
88
  const self = this._self;
89
89
  const enrich = this._enrich;
90
90
  let raw = this._result.errors || [];
91
- if (raw.length > 1) raw = sortErrorsBySchemaOrder(this._root, raw);
91
+ if (needsOrdering(raw)) raw = sortErrorsBySchemaOrder(this._root, raw);
92
92
  // The position map, resolved now that an error is actually being read.
93
93
  let positions = null;
94
94
  if (enrich && raw.length && this._rawInput != null) {
@@ -172,7 +172,7 @@ Object.defineProperty(LazyJsonRejection.prototype, 'errors', {
172
172
  const positions = self._pos().targeted(jsonStr, wantedPointersFor(raw));
173
173
  // Declaration order, as validate() applies it; the text path used to
174
174
  // enrich in emission order.
175
- const ordered = raw.length > 1 ? sortErrorsBySchemaOrder(self._schemaObj, raw) : raw;
175
+ const ordered = needsOrdering(raw) ? sortErrorsBySchemaOrder(self._schemaObj, raw) : raw;
176
176
  const enrichOpts = {
177
177
  data: parsedData,
178
178
  positions,
@@ -246,11 +246,23 @@ function wantedPointersFor (errors) {
246
246
  return wanted;
247
247
  }
248
248
 
249
- // A raw error without the `_o` ordering key, for the legacy error shape.
249
+ // A raw error in the legacy shape. The error generator writes `code` and
250
+ // `docUrl` into its literals because a standalone module returns them as they
251
+ // are; the runtime's legacy shape never had them, and the interpreter leaves
252
+ // them out, so they are dropped here with the ordering key `_o`. A collapsed
253
+ // oneOf/anyOf error keeps its code, which both engines give it, and its
254
+ // branch errors get the same treatment, so the shape does not depend on
255
+ // which engine answered.
256
+ const COLLAPSE_CODES = new Set(['ATA4001', 'ATA4002', 'ATA4003']);
250
257
  function stripOrdinal(e) {
251
- if (e === null || typeof e !== 'object' || e._o === undefined) return e;
258
+ if (e === null || typeof e !== 'object') return e;
259
+ if (e._o === undefined && e.docUrl === undefined && (e.code === undefined || COLLAPSE_CODES.has(e.code)) && !e.branchErrors) return e;
252
260
  const out = {};
253
- for (const k in e) if (k !== '_o') out[k] = e[k];
261
+ for (const k in e) {
262
+ if (k === '_o' || k === 'docUrl') continue;
263
+ if (k === 'code' && !COLLAPSE_CODES.has(e.code)) continue;
264
+ out[k] = k === 'branchErrors' && Array.isArray(e[k]) ? e[k].map(stripOrdinal) : e[k];
265
+ }
254
266
  return out;
255
267
  }
256
268
 
@@ -261,7 +273,33 @@ function stripOrdinal(e) {
261
273
  // place in the target (rankFor), so they come out in the order they would
262
274
  // inline, whichever engine produced them. Most rejections come out already
263
275
  // ordered, and those return without sorting or allocating.
276
+ // A list of one can still hold a collapsed error whose branch errors, at any
277
+ // depth, need ordering.
278
+ function needsOrdering(raw) {
279
+ if (raw.length > 1) return true;
280
+ const e = raw[0];
281
+ return raw.length === 1 && e !== null && typeof e === 'object' && Array.isArray(e.branchErrors) && needsOrdering(e.branchErrors);
282
+ }
283
+
284
+ // A collapsed oneOf/anyOf error carries the closest branch's errors, which
285
+ // each engine produces in its own walk order. They go in schema order like the
286
+ // list they sit in, so which engine answered cannot be read off them.
287
+ function sortBranchErrors(rootSchema, errors) {
288
+ let out = null;
289
+ for (let i = 0; i < errors.length; i++) {
290
+ const e = errors[i];
291
+ const b = e !== null && typeof e === 'object' ? e.branchErrors : undefined;
292
+ if (!Array.isArray(b) || b.length === 0) continue;
293
+ const sb = sortErrorsBySchemaOrder(rootSchema, b);
294
+ if (sb === b) continue;
295
+ if (out === null) out = errors.slice();
296
+ out[i] = Object.assign({}, e, { branchErrors: sb });
297
+ }
298
+ return out === null ? errors : out;
299
+ }
300
+
264
301
  function sortErrorsBySchemaOrder(rootSchema, errors) {
302
+ errors = sortBranchErrors(rootSchema, errors);
265
303
  const n = errors.length;
266
304
  const keys = new Array(n);
267
305
  let sorted = true;