ata-validator 1.62.1 → 1.63.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.
Files changed (51) hide show
  1. package/README.md +63 -8
  2. package/build.mjs +1 -0
  3. package/index.browser.mjs +23 -4
  4. package/index.bundle.mjs +14 -4
  5. package/index.edge.mjs +14 -4
  6. package/index.js +7 -2
  7. package/lib/aot-build.js +18 -0
  8. package/lib/codegen-engine.js +72 -3
  9. package/lib/compiled-common.js +2 -1
  10. package/lib/email-literal.js +4 -1
  11. package/lib/formats-source.js +39 -40
  12. package/lib/interpreter-core.js +17 -9
  13. package/lib/ir/emit-deps.mjs +131 -0
  14. package/lib/ir/emit.mjs +560 -0
  15. package/lib/ir/front.mjs +834 -0
  16. package/lib/ir/index.mjs +10 -0
  17. package/lib/ir/refs.mjs +108 -0
  18. package/lib/ir/run.mjs +520 -0
  19. package/lib/ir/table.mjs +233 -0
  20. package/lib/js-compiler-errors.js +9 -4
  21. package/lib/kernels/collapse.mjs +114 -0
  22. package/lib/kernels/custom.mjs +70 -0
  23. package/lib/kernels/fill.mjs +6 -0
  24. package/lib/kernels/formats-small.mjs +40 -0
  25. package/lib/kernels/formats.mjs +29 -0
  26. package/lib/kernels/index.mjs +11 -0
  27. package/lib/kernels/kernels.mjs +285 -0
  28. package/lib/kernels/leaves.mjs +28 -0
  29. package/lib/kernels/order.mjs +25 -0
  30. package/lib/kernels/plan-rich.mjs +17 -0
  31. package/lib/kernels/plan.d.ts +9 -0
  32. package/lib/kernels/plan.mjs +128 -0
  33. package/lib/kernels/preprocess.mjs +223 -0
  34. package/lib/kernels/rank.mjs +97 -0
  35. package/lib/kernels/rt.mjs +249 -0
  36. package/lib/kernels/table.mjs +182 -0
  37. package/lib/kernels/unique.mjs +157 -0
  38. package/lib/native-positions.js +1 -1
  39. package/lib/openapi.d.ts +2 -0
  40. package/lib/openapi.js +128 -0
  41. package/lib/plan-compiler.js +84 -7
  42. package/lib/public-api.js +7 -2
  43. package/lib/rejections.js +3 -1
  44. package/lib/schema-helpers.js +12 -0
  45. package/lib/schema-helpers.mjs +20 -0
  46. package/lib/schema-order.js +4 -2
  47. package/lib/standard-schema.js +1 -1
  48. package/lib/validator-core.js +35 -30
  49. package/lib/version.js +1 -1
  50. package/lite.js +7 -2
  51. package/package.json +26 -8
@@ -0,0 +1,182 @@
1
+ // The table backend's interpreter: a schema the IR's front end has read,
2
+ // written by lib/ir/table.mjs as a plan of steps (one array per schema
3
+ // node), and run here. Paths, schema paths, params and messages are made
4
+ // from the step and the descriptor it names, as the module writer writes
5
+ // them out (lib/ir/emit.mjs), so the errors are the same, field for field and
6
+ // in order. A page with many forms carries this once and each form as data.
7
+ //
8
+ // A step is [handler, ...]: the handler is the function below that runs it,
9
+ // named by the plan, so a bundle carries the handlers its plans use and no
10
+ // other. A node is an array of steps, run in the schema's own key order; P
11
+ // is the plan, its nodes by index, the root first. A handler returns false
12
+ // to stop a verdict at its first failure; collecting errors (e an array), it
13
+ // pushes them and goes on.
14
+ import { own, isObj, keysOf, seg, copyOf, setOwn, E, relate } from './rt.mjs'
15
+ import { type, typeP, required, requiredP, additionalProperties, additionalP, not as notK, falseSchema } from './kernels.mjs'
16
+
17
+ const PN = ({}).__proto__ === Object.prototype
18
+ const NONE = Object.freeze([])
19
+ // Whether x is an object with Object.prototype: then a defined value under a
20
+ // name Object.prototype lacks is an own property.
21
+ const plainOf = (x) => isObj(x) && (PN ? x.__proto__ : Object.getPrototypeOf(x)) === Object.prototype
22
+ // A name is present the current engine's way: an own key, and for a name
23
+ // Object.prototype lacks on such an object, a defined value.
24
+ const has = (x, p, k) => k in Object.prototype ? own(x, k) : p || own(x, k)
25
+ // A name as a JSON pointer segment; most names need no escape.
26
+ const esc = (k) => k.indexOf('~') === -1 && k.indexOf('/') === -1 ? k : seg(k)
27
+ // Paths are built only for a walk that collects errors; a verdict reads none.
28
+ const at = (e, a, b) => e === null ? '' : a + b
29
+ // A node: by its index in P (shared, or reached again), or written in place.
30
+ const nd = (c, r) => typeof r === 'number' ? c.P[r] : r
31
+ // Whether the node's properties step (index j, -1 for none) names k.
32
+ const known = (N, j, k) => j !== -1 && own(N[j][1], k)
33
+
34
+ // [tType, n]
35
+ export const tType = (c, t, x, e, s, path, sp, v) => c.T.ok(x, t[1]) || (e !== null && (c.push(e, c.T, path, sp + '/type', t[1], v, s), true))
36
+ // [tProps, { name: node, ... }], the schema's own order (the plan writer
37
+ // refuses names an object literal would reorder or treat specially).
38
+ export function tProps (c, t, x, e, s, path, sp) {
39
+ if (!isObj(x)) return true
40
+ const p = plainOf(x); const l = t[1]
41
+ for (const k in l) {
42
+ const y = x[k]
43
+ if (y !== undefined && has(x, p, k) && !c.run(nd(c, l[k]), y, e, s, at(e, path + '/', esc(k)), at(e, sp + '/properties/', esc(k)), y) && e === null) return false
44
+ }
45
+ return true
46
+ }
47
+ // [tReq, [name, ...]], or [tReq, mask, the properties step] where the names
48
+ // are the properties step's own, in its order: bit i for its i-th name.
49
+ export function tReq (c, t, x, e, s, path, sp, v, N) {
50
+ if (!isObj(x)) return true
51
+ const p = plainOf(x)
52
+ const l = typeof t[1] === 'number' ? Object.keys(N[t[2]][1]).filter((_, i) => t[1] >>> i & 1) : t[1]
53
+ for (const r of l) if (x[r] === undefined || !has(x, p, r)) { if (e === null) return false; c.push(e, c.R, path, sp + '/required', r, v, s) }
54
+ return true
55
+ }
56
+ // additionalProperties false: [tClosed, the node's properties step or -1]
57
+ export function tClosed (c, t, x, e, s, path, sp, v, N) {
58
+ if (!isObj(x)) return true
59
+ const p = plainOf(x)
60
+ for (const k in x) if (!known(N, t[1], k) && (p || own(x, k))) { if (e === null) return false; c.push(e, c.A, path, sp + '/additionalProperties', k, v, s) }
61
+ return true
62
+ }
63
+ // additionalProperties a schema: [tAddl, node, the properties step or -1]
64
+ export function tAddl (c, t, x, e, s, path, sp, v, N) {
65
+ if (isObj(x)) for (const k of keysOf(x)) if (!known(N, t[2], k) && !c.run(nd(c, t[1]), x[k], e, s, at(e, path + '/', esc(k)), at(e, sp, '/additionalProperties'), x[k]) && e === null) return false
66
+ return true
67
+ }
68
+ // A leaf: [tLeaf, descriptor, n], or for a format [tLeaf, descriptor, n,
69
+ // check, code, hint]. A descriptor that prepares its argument (pattern) has
70
+ // it prepared on first use, kept as t[3].
71
+ export function tLeaf (c, t, x, e, s, path, sp, v) {
72
+ const K = t[1]
73
+ if (K.on && !K.on(x)) return true
74
+ if (t.length < 4) t[3] = K.prepare ? K.prepare(t[2]) : t[2]
75
+ const f = K.fail ? K.fail(x, t[2]) : K.ok(x, t[3]) ? null : 0
76
+ if (f === null) return true
77
+ if (e === null) return false
78
+ c.push(e, K, path, sp + '/' + K.kw, t[2], v, s, f || null, t[4], t[5])
79
+ return true
80
+ }
81
+ // [tItems, node, first index]
82
+ export function tItems (c, t, x, e, s, path, sp) {
83
+ if (Array.isArray(x)) for (let j = t[2]; j < x.length; j++) if (!c.run(nd(c, t[1]), x[j], e, s, at(e, path + '/', j), at(e, sp, '/items'), x[j]) && e === null) return false
84
+ return true
85
+ }
86
+ // [tTuple, [node, ...]]
87
+ export function tTuple (c, t, x, e, s, path, sp) {
88
+ if (Array.isArray(x)) for (let j = 0; j < t[1].length && j < x.length; j++) if (!c.run(nd(c, t[1][j]), x[j], e, s, at(e, path + '/', j), at(e, sp + '/prefixItems/', j), x[j]) && e === null) return false
89
+ return true
90
+ }
91
+ // A call on the same value through $ref or allOf that comes back to a node
92
+ // already running on it is cut, the inner entry adding no error, as the
93
+ // current engine cuts it.
94
+ function here (c, n, x, e, s, path, sp, v) {
95
+ let on = c.a.get(n)
96
+ if (on === undefined) c.a.set(n, on = new Set())
97
+ if (on.has(x)) return true
98
+ on.add(x)
99
+ try { return c.run(nd(c, n), x, e, s, path, sp, v) } finally { on.delete(x) }
100
+ }
101
+ // [tAll, [node, ...]]
102
+ export function tAll (c, t, x, e, s, path, sp, v) {
103
+ for (let j = 0; j < t[1].length; j++) if (!here(c, t[1][j], x, e, s, path, at(e, sp + '/allOf/', j), v) && e === null) return false
104
+ return true
105
+ }
106
+ // $ref: [tRef, node], same value, same paths.
107
+ export const tRef = (c, t, x, e, s, path, sp, v) => here(c, t[1], x, e, s, path, sp, v) || e !== null
108
+ // Made in a pure call, so a bundle whose plans have no `not` drops it: a
109
+ // property read at the top level would be kept.
110
+ const NOT = /* @__PURE__ */ (() => ({ kw: 'not', code: notK.code, rank: notK.rank, params: () => ({}), msg: () => notK.msg, exp: () => void 0 }))()
111
+ // [tNot, node]
112
+ export function tNot (c, t, x, e, s, path, sp, v) {
113
+ if (!c.run(nd(c, t[1]), x, null, true, path, sp + '/not', v)) return true
114
+ if (e === null) return false
115
+ c.push(e, NOT, path, sp + '/not', 0, v, s)
116
+ return true
117
+ }
118
+ // [tIf, if node, then node or -1, else node or -1]
119
+ export function tIf (c, t, x, e, s, path, sp, v) {
120
+ // The branch by the condition's answer, not by which node it is: then and
121
+ // else may be the same schema.
122
+ const ok = c.run(nd(c, t[1]), x, null, true, path, sp + '/if', v); const b = ok ? t[2] : t[3]
123
+ return b === -1 || c.run(nd(c, b), x, e, s, path, sp + (ok ? '/then' : '/else'), v) || e !== null
124
+ }
125
+ // dependentRequired: [tDepReq, [name, [required, ...], ...]]
126
+ export function tDepReq (c, t, x, e, s, path, sp, v) {
127
+ if (!isObj(x)) return true
128
+ const p = plainOf(x)
129
+ for (let j = 0, l = t[1]; j < l.length; j += 2) if (own(x, l[j])) for (const r of l[j + 1]) if (x[r] === undefined || !has(x, p, r)) { if (e === null) return false; c.push(e, c.R, path, sp + '/dependentRequired', r, v, s, null, 'ATA7005') }
130
+ return true
131
+ }
132
+ const FALSE = /* @__PURE__ */ (() => ({ kw: falseSchema.keyword, code: falseSchema.code, rank: falseSchema.rank, params: () => ({}), msg: () => falseSchema.msg, exp: () => void 0 }))()
133
+ // The false schema: [tFalse]
134
+ export const tFalse = (c, t, x, e, s, path, sp, v) => e !== null && (c.push(e, FALSE, path, sp, 0, v, s), true)
135
+
136
+ // push(e, descriptor, path, schemaPath, n, value, seen, failure, code, hint)
137
+ // order: the errors into schema order (kernels.mjs ordered), where the
138
+ // module writer sorts them too (lib/ir/front.mjs needsOrder).
139
+ function make (P, F, push, post, T, R, A, fill, order) {
140
+ const c = {
141
+ P, push, T, R, A, a: new Map(),
142
+ run (N, x, e, s, path, sp, v) {
143
+ for (let i = 0; i < N.length; i++) if (!N[i][0](c, N[i], x, e, s, path, sp, v, N)) return false
144
+ return true
145
+ },
146
+ }
147
+ const isValid = (d) => { if (fill) fill(F, 0, d); return c.run(P[0], d, null, true, '', '#', d) }
148
+ // isValidObject: the name the build plugin's wrapper reads, so a plan it
149
+ // places needs no wrapper of its own.
150
+ return {
151
+ isValid,
152
+ isValidObject: isValid,
153
+ validate (d) {
154
+ if (fill) fill(F, 0, d)
155
+ if (c.run(P[0], d, null, true, '', '#', d)) return { valid: true, data: d, errors: NONE }
156
+ const e = []
157
+ c.run(P[0], d, e, !(!d && d !== 0 && d !== false), '', '#', d)
158
+ const o = order ? order(e) : e
159
+ return { valid: false, errors: post !== null && o.length > 1 ? post(o) : o }
160
+ },
161
+ }
162
+ }
163
+
164
+ // Defaults, for a plan whose schema has any: F, by index, holds for each
165
+ // node [defaults [k, v, ...], properties [k, f, ...], prefixItems [f or -1,
166
+ // ...], items f or -1, dependentSchemas [k, f, ...], in place [f, ...]], in
167
+ // the order and by the rules of the module writer's fill (lib/ir/front.mjs).
168
+ export function tableFill (F, f, d) {
169
+ if (typeof d !== 'object' || d === null) return
170
+ const [defs, props, tuple, each, deps, here] = F[f]
171
+ for (let j = 0; j < defs.length; j += 2) if (!own(d, defs[j])) setOwn(d, defs[j], copyOf(defs[j + 1]))
172
+ for (let j = 0; j < props.length; j += 2) if (own(d, props[j])) tableFill(F, props[j + 1], d[props[j]])
173
+ if (Array.isArray(d) && (tuple.length || each !== -1)) for (let j = 0; j < d.length; j++) { if (j < tuple.length) { if (tuple[j] !== -1) tableFill(F, tuple[j], d[j]) } else if (each !== -1) tableFill(F, each, d[j]) }
174
+ for (let j = 0; j < deps.length; j += 2) if (!Array.isArray(d) && own(d, deps[j])) tableFill(F, deps[j + 1], d)
175
+ for (const g of here) tableFill(F, g, d)
176
+ }
177
+
178
+ // The errors as the plain shape: what richErrors: false gives.
179
+ export const tablePlain = (P, F = [], fill, order) => make(P, F, (e, K, path, sp, n, v, s, f) => { e.push({ keyword: K.kw, instancePath: path, schemaPath: sp, params: f ? f.params : K.params(n), message: f ? f.msg : K.msg(n) }) }, null, typeP, requiredP, additionalP, fill, order)
180
+ // The errors as ata's rich shape (rt.mjs E); `rel` when a node has both
181
+ // required and additionalProperties, whose errors may point at each other.
182
+ export const tableRich = (P, F = [], fill, rel, order) => make(P, F, (e, K, path, sp, n, v, s, f, code, hint) => { e.push(E(code || K.code, K.kw, path, sp, f ? f.params : K.params(n), f ? f.msg : K.msg(n), K.exp(n), K.rank, v, s, K.detail, hint || K.suggest)) }, rel ? relate : null, type, required, additionalProperties, fill, order)
@@ -0,0 +1,157 @@
1
+ // Generated by scripts/gen-kernels-cjs.js from lib/unique-items.js. Do not edit.
2
+
3
+ // uniqueItems for every engine. These are real functions: the interpreted
4
+ // engine calls them, and the code generator emits their source text
5
+ // (`String(fn)`) into the function it builds, so both run the same code.
6
+ // They read only each other, `_deq` and `_uqpI`, which the generator emits
7
+ // beside them.
8
+ //
9
+ // Short arrays compare pairwise and allocate nothing. Longer arrays of
10
+ // primitives key a Set or Map by the value itself. Longer arrays that hold an
11
+ // object key a Map by a numeric structural hash, and only items whose hashes
12
+ // collide are compared with `_deq`. Equal values always hash alike: numbers
13
+ // by their bits, with 0 and -0 as one (1 and 1.0 are one double already),
14
+ // strings by their characters, arrays by position, and objects by the sum of their own key-value hashes,
15
+ // whatever the key order. The hash replaced a sorted-key canonical string
16
+ // built for every item on every call, which was over a quarter of the time a
17
+ // generated verdict spent on SchemaStore's sample documents.
18
+
19
+ // JSON equality, the same function the generator hoists as DEQ_HELPER.
20
+ function _deq(a, b) {
21
+ if (a === b) return true;
22
+ if (a === null || b === null || typeof a !== 'object' || typeof b !== 'object') return false;
23
+ var aa = Array.isArray(a);
24
+ if (aa !== Array.isArray(b)) return false;
25
+ var i, k, x, y;
26
+ if (aa) {
27
+ if (a.length !== b.length) return false;
28
+ for (i = a.length; i-- !== 0;) if (!_deq(a[i], b[i])) return false;
29
+ return true;
30
+ }
31
+ var ka = Object.keys(a);
32
+ if (ka.length !== Object.keys(b).length) return false;
33
+ // Keys and scalar values first, nested values after, from the last key back:
34
+ // two items of a list most often differ in a name, a path or an id, and
35
+ // walking a nested list first made each comparison of rust-project's crates
36
+ // three times what it needed (250 against 78 ns for the three; the default
37
+ // validator's deep equal takes 115).
38
+ for (i = ka.length; i-- !== 0;) {
39
+ k = ka[i];
40
+ if (!Object.prototype.hasOwnProperty.call(b, k)) return false;
41
+ x = a[k]; y = b[k];
42
+ if (x !== y && (x === null || y === null || typeof x !== 'object' || typeof y !== 'object')) return false;
43
+ }
44
+ for (i = ka.length; i-- !== 0;) {
45
+ k = ka[i]; x = a[k];
46
+ if (x !== null && typeof x === 'object' && !_deq(x, b[k])) return false;
47
+ }
48
+ return true;
49
+ }
50
+
51
+ function _uqh(x) {
52
+ if (x === null) return 1;
53
+ var t = typeof x, h, i;
54
+ if (t === 'boolean') return x ? 2 : 3;
55
+ if (t === 'number') {
56
+ if (x === 0) return 4;
57
+ h = x | 0;
58
+ if (h === x) return Math.imul(h, 0x9e3779b1) ^ 5;
59
+ // Every bit of the number goes into the hash, read through a typed
60
+ // array kept on the function (its source is emitted into modules, so
61
+ // nothing outside it is read). Nine decimal places, which this used to
62
+ // keep, hashed every number below 1e-9 alike, and an array of them was
63
+ // compared item against item.
64
+ var f = _uqh.f || (_uqh.f = new Float64Array(1)), w = _uqh.w || (_uqh.w = new Uint32Array(f.buffer));
65
+ f[0] = x;
66
+ return Math.imul(w[0] ^ Math.imul(w[1], 0x9e3779b1), 0x85ebca6b) ^ 5;
67
+ }
68
+ if (t === 'string') {
69
+ h = 0x811c9dc5 ^ x.length;
70
+ for (i = 0; i < x.length; i++) h = Math.imul(h ^ x.charCodeAt(i), 16777619);
71
+ return h;
72
+ }
73
+ if (t !== 'object') return 6;
74
+ if (Array.isArray(x)) {
75
+ h = 0x2545f491 ^ x.length;
76
+ for (i = 0; i < x.length; i++) h = Math.imul(h ^ _uqh(x[i]), 16777619);
77
+ return h;
78
+ }
79
+ var ks = Object.keys(x);
80
+ h = 0x6c8e9cf5 ^ ks.length;
81
+ for (i = 0; i < ks.length; i++) h = (h + Math.imul(_uqh(ks[i]) ^ Math.imul(_uqh(x[ks[i]]), 0x5bd1e995), 0x27d4eb2d)) | 0;
82
+ return h;
83
+ }
84
+
85
+ // Whether every item is distinct.
86
+ function _uq(a) {
87
+ var n = a.length, i, k;
88
+ if (n < 2) return true;
89
+ if (n <= 12) {
90
+ for (i = 1; i < n; i++) for (k = 0; k < i; k++) if (_deq(a[i], a[k])) return false;
91
+ return true;
92
+ }
93
+ var prim = true;
94
+ for (i = 0; i < n; i++) { var x = a[i]; if (x !== null && typeof x === 'object') { prim = false; break; } }
95
+ if (prim) {
96
+ var s = new Set();
97
+ for (i = 0; i < n; i++) { if (s.has(a[i])) return false; s.add(a[i]); }
98
+ return true;
99
+ }
100
+ var m = new Map();
101
+ for (i = 0; i < n; i++) {
102
+ var hh = _uqh(a[i]), b = m.get(hh);
103
+ if (b === undefined) m.set(hh, i);
104
+ else if (typeof b === 'number') { if (_deq(a[b], a[i])) return false; m.set(hh, [b, i]); }
105
+ else { for (k = 0; k < b.length; k++) if (_deq(a[b[k]], a[i])) return false; b.push(i); }
106
+ }
107
+ return true;
108
+ }
109
+
110
+ // The duplicate pair an error reports: the first item that has a later
111
+ // equal, and its first later equal. Returns that later index and leaves the
112
+ // first in _uqpI, or returns -1 when every item is distinct.
113
+ var _uqpI = 0;
114
+ function _uqp(a) {
115
+ var n = a.length, i, k;
116
+ if (n < 2) return -1;
117
+ if (n <= 12) {
118
+ for (i = 0; i < n - 1; i++) for (k = i + 1; k < n; k++) if (_deq(a[i], a[k])) { _uqpI = i; return k; }
119
+ return -1;
120
+ }
121
+ var prim = true;
122
+ for (i = 0; i < n; i++) { var x = a[i]; if (x !== null && typeof x === 'object') { prim = false; break; } }
123
+ var bi = -1, bj = -1, m = new Map();
124
+ if (prim) {
125
+ for (i = 0; i < n; i++) {
126
+ var p = m.get(a[i]);
127
+ if (p === undefined) m.set(a[i], i);
128
+ else if (p >= 0) { if (bi < 0 || p < bi) { bi = p; bj = i; } m.set(a[i], -1); }
129
+ }
130
+ } else {
131
+ // Each bucket holds the first occurrence of every distinct value that
132
+ // hashed there, in index order; a value is paired with its first later
133
+ // equal only once.
134
+ var paired = null;
135
+ for (i = 0; i < n; i++) {
136
+ var hh = _uqh(a[i]), b = m.get(hh), r = -1;
137
+ if (b === undefined) { m.set(hh, i); continue; }
138
+ if (typeof b === 'number') { if (_deq(a[b], a[i])) r = b; else m.set(hh, [b, i]); }
139
+ else { for (k = 0; k < b.length; k++) if (_deq(a[b[k]], a[i])) { r = b[k]; break; } if (r < 0) b.push(i); }
140
+ if (r >= 0) {
141
+ if (paired === null) paired = new Set();
142
+ if (!paired.has(r)) { paired.add(r); if (bi < 0 || r < bi) { bi = r; bj = i; } }
143
+ }
144
+ }
145
+ }
146
+ if (bi < 0) return -1;
147
+ _uqpI = bi;
148
+ return bj;
149
+ }
150
+
151
+ // For the interpreted engine.
152
+ function allDistinct(a) { return _uq(a); }
153
+ function duplicatePair(a) {
154
+ var j = _uqp(a);
155
+ return j < 0 ? null : [_uqpI, j];
156
+ }
157
+ export { _deq, duplicatePair };
@@ -12,7 +12,7 @@ let _native;
12
12
  function nativeTargeted (text, wanted) {
13
13
  if (typeof text !== 'string' || text.length < NATIVE_MIN) return null;
14
14
  if (_native === undefined) {
15
- try { _native = require('./native-load')(); } catch { _native = null; }
15
+ try { const l = require('./native-load'); _native = (typeof l === 'function' ? l : l.default)(); } catch { _native = null; }
16
16
  if (_native && typeof _native.locatePointers !== 'function') _native = null;
17
17
  }
18
18
  if (_native === null || typeof Buffer === 'undefined' || Buffer.byteLength(text, 'utf8') !== text.length) return null;
@@ -0,0 +1,2 @@
1
+ /** The JSON Schema of each operation's JSON request body, by operationId (or method and path). */
2
+ export declare function operationSchemas(doc: object): Record<string, object>
package/lib/openapi.js ADDED
@@ -0,0 +1,128 @@
1
+ 'use strict';
2
+ // ata-validator/openapi: the JSON Schema of each operation's request body in
3
+ // an OpenAPI document, ready for new Validator(schema), so a server and a page
4
+ // built from the same document check the same schema. @ata-project/unplugin
5
+ // writes a page's validators from it; a server reads it directly.
6
+ //
7
+ // const { operationSchemas } = require('ata-validator/openapi')
8
+ // const bodies = operationSchemas(require('./openapi.json'))
9
+ // const createUser = new Validator(bodies.createUser)
10
+ //
11
+ // Each schema carries the document's components.schemas as $defs, its
12
+ // references rewritten to them. OpenAPI 3.1 schemas are JSON Schema 2020-12
13
+ // and are taken as they are (a nullable left in one is read as ata reads it
14
+ // in every dialect); a document's jsonSchemaDialect becomes each schema's
15
+ // $schema. OpenAPI 3.0 schemas are read as 3.0 defines
16
+ // them: nullable: true adds null to the type (and to an enum), and a boolean
17
+ // exclusiveMinimum or exclusiveMaximum makes minimum or maximum exclusive.
18
+ // Only application/json bodies (and +json media types) are read. A reference
19
+ // outside components.schemas, or to a schema that is not there, throws.
20
+
21
+ const METHODS = ['get', 'put', 'post', 'delete', 'options', 'head', 'patch', 'trace'];
22
+ const isObj = (v) => typeof v === 'object' && v !== null && !Array.isArray(v);
23
+ const own = (o, k) => Object.prototype.hasOwnProperty.call(o, k);
24
+
25
+ // An operation's name, a JavaScript identifier: its operationId in camel
26
+ // case (create-user, users.update and create_user give createUser,
27
+ // usersUpdate and createUser; a name that is one already is kept), or, with
28
+ // none, its method and path (post /users/{id}/roles -> postUsersIdRoles).
29
+ function identifier(words) {
30
+ const w = words.filter(Boolean);
31
+ if (!w.length) return null;
32
+ const out = w[0][0].toLowerCase() + w[0].slice(1) + w.slice(1).map((x) => x[0].toUpperCase() + x.slice(1)).join('');
33
+ return /^[0-9]/.test(out) ? '_' + out : out;
34
+ }
35
+ function nameOf(method, p, op) {
36
+ if (typeof op.operationId === 'string' && op.operationId.trim() !== '') {
37
+ if (/^[A-Za-z_$][\w$]*$/.test(op.operationId) && !op.operationId.includes('_')) return op.operationId;
38
+ const id = identifier(op.operationId.split(/[^A-Za-z0-9$]+/));
39
+ if (id !== null) return id;
40
+ }
41
+ return identifier([method, ...(p.match(/[A-Za-z0-9]+/g) || [])]);
42
+ }
43
+
44
+ function jsonBody(content) {
45
+ if (!isObj(content)) return null;
46
+ for (const type of Object.keys(content)) {
47
+ const base = type.split(';')[0].trim().toLowerCase();
48
+ if ((base === 'application/json' || /^application\/[\w.-]+\+json$/.test(base)) && isObj(content[type]) && own(content[type], 'schema')) return content[type].schema;
49
+ }
50
+ return null;
51
+ }
52
+
53
+ // Where a schema keeps its subschemas: one, a list, or a map of names to
54
+ // subschemas (a name there is the data's, not a keyword). Every other keyword
55
+ // is copied as it is.
56
+ const ONE = new Set(['items', 'additionalItems', 'additionalProperties', 'not', 'if', 'then', 'else', 'contains', 'propertyNames', 'unevaluatedItems', 'unevaluatedProperties']);
57
+ const LIST = new Set(['allOf', 'anyOf', 'oneOf', 'prefixItems']);
58
+ const MAP = new Set(['properties', 'patternProperties', '$defs', 'definitions', 'dependentSchemas']);
59
+
60
+ // A copy of a schema with references into components.schemas pointed at
61
+ // $defs, and, for 3.0, its nullable and boolean exclusive bounds rewritten.
62
+ function convert(s, v30, where) {
63
+ if (!isObj(s)) return s;
64
+ const out = {};
65
+ for (const k of Object.keys(s)) {
66
+ const v = s[k];
67
+ if (k === '$ref' && typeof v === 'string') {
68
+ const m = /^#\/components\/schemas\/(.+)$/.exec(v);
69
+ if (!m) throw new Error(`ata-validator/openapi: ${where} refers to ${v}; only #/components/schemas/... is read`);
70
+ out.$ref = '#/$defs/' + m[1];
71
+ } else if (ONE.has(k)) out[k] = Array.isArray(v) ? v.map((x, i) => convert(x, v30, `${where}/${k}/${i}`)) : convert(v, v30, `${where}/${k}`);
72
+ else if (LIST.has(k) && Array.isArray(v)) out[k] = v.map((x, i) => convert(x, v30, `${where}/${k}/${i}`));
73
+ else if (MAP.has(k) && isObj(v)) { const m = {}; for (const n of Object.keys(v)) m[n] = convert(v[n], v30, `${where}/${k}/${n}`); out[k] = m; }
74
+ else out[k] = v;
75
+ }
76
+ if (v30) {
77
+ if (out.nullable === true) {
78
+ if (typeof out.type === 'string') out.type = [out.type, 'null'];
79
+ else if (Array.isArray(out.type) && !out.type.includes('null')) out.type = [...out.type, 'null'];
80
+ if (Array.isArray(out.enum) && !out.enum.includes(null)) out.enum = [...out.enum, null];
81
+ }
82
+ delete out.nullable;
83
+ if (out.exclusiveMinimum === true && typeof out.minimum === 'number') { out.exclusiveMinimum = out.minimum; delete out.minimum; } else if (typeof out.exclusiveMinimum === 'boolean') delete out.exclusiveMinimum;
84
+ if (out.exclusiveMaximum === true && typeof out.maximum === 'number') { out.exclusiveMaximum = out.maximum; delete out.maximum; } else if (typeof out.exclusiveMaximum === 'boolean') delete out.exclusiveMaximum;
85
+ }
86
+ return out;
87
+ }
88
+
89
+ // { [operation name]: schema } for every operation with a JSON request body.
90
+ function operationSchemas(doc) {
91
+ if (!isObj(doc) || !isObj(doc.paths)) throw new Error('ata-validator/openapi: not an OpenAPI document (no paths)');
92
+ const version = typeof doc.openapi === 'string' ? doc.openapi : '';
93
+ const v30 = /^3\.0\./.test(version);
94
+ if (!v30 && !/^3\.1\./.test(version)) throw new Error(`ata-validator/openapi: OpenAPI 3.0 or 3.1 is read, not ${JSON.stringify(doc.openapi || doc.swagger)}`);
95
+ const dialect = !v30 && typeof doc.jsonSchemaDialect === 'string' ? doc.jsonSchemaDialect : null;
96
+ const components = isObj(doc.components) ? doc.components : {};
97
+ const schemas = isObj(components.schemas) ? components.schemas : {};
98
+ const defs = {};
99
+ for (const k of Object.keys(schemas)) defs[k] = convert(schemas[k], v30, '#/components/schemas/' + k);
100
+ const out = {}; const seen = {};
101
+ for (const p of Object.keys(doc.paths)) {
102
+ const item = doc.paths[p];
103
+ if (!isObj(item)) continue;
104
+ for (const method of METHODS) {
105
+ const op = item[method];
106
+ if (!isObj(op)) continue;
107
+ let body = op.requestBody;
108
+ if (isObj(body) && typeof body.$ref === 'string') {
109
+ const m = /^#\/components\/requestBodies\/(.+)$/.exec(body.$ref);
110
+ body = m && isObj(components.requestBodies) ? components.requestBodies[m[1]] : undefined;
111
+ if (!isObj(body)) throw new Error(`ata-validator/openapi: ${method} ${p} refers to a request body that is not there`);
112
+ }
113
+ if (!isObj(body)) continue;
114
+ const schema = jsonBody(body.content);
115
+ if (schema === null) continue;
116
+ const name = nameOf(method, p, op);
117
+ if (own(out, name)) throw new Error(`ata-validator/openapi: ${seen[name]} and ${method} ${p} are both named ${name}; give one a different operationId`);
118
+ seen[name] = `${method} ${p}`;
119
+ const root = convert(schema, v30, `${method} ${p} request body`);
120
+ const withDefs = Object.keys(defs).length ? { ...root, $defs: { ...defs, ...(isObj(root.$defs) ? root.$defs : {}) } } : root;
121
+ // A 3.1 document's jsonSchemaDialect is the dialect of its schemas.
122
+ out[name] = dialect && withDefs.$schema === undefined ? { $schema: dialect, ...withDefs } : withDefs;
123
+ }
124
+ }
125
+ return out;
126
+ }
127
+
128
+ module.exports = { operationSchemas };
@@ -56,6 +56,21 @@ function install(deps) {
56
56
  T_STRING, T_NUMBER, T_INTEGER, T_BOOLEAN, T_NULL, T_ARRAY, T_OBJECT, resolveRef, splitFragment, resolveUri } = deps;
57
57
 
58
58
  const TRUE_FN = () => true;
59
+ // Leaf codes (leafCode in compileLeafV): a string with lengths and a
60
+ // pattern, an integer or a number with minimum and maximum, a boolean, an
61
+ // enum of primitives, a primitive const. Each answers as the closure it
62
+ // stands for.
63
+ const LC_STR = 1, LC_INT = 2, LC_NUM = 3, LC_BOOL = 4, LC_ENUM = 5, LC_CONST = 6;
64
+ function leafOk(c, d) {
65
+ switch (c[0]) {
66
+ case LC_STR: return typeof d === 'string' && (c[1] === -1 || cpAtLeast(d, c[1])) && (c[2] === -1 || cpAtMost(d, c[2])) && (c[3] === null || c[3].test(d));
67
+ case LC_INT: case LC_NUM: return (c[0] === LC_INT ? Number.isInteger(d) : typeof d === 'number' && isFinite(d)) && d >= c[1] && d <= c[2];
68
+ case LC_BOOL: return typeof d === 'boolean';
69
+ case LC_ENUM: return c[1].has(d);
70
+ default: return d === c[1];
71
+ }
72
+ }
73
+
59
74
  // `leaf`: the node compiled no subschema of its own (see compileNode). A
60
75
  // collecting caller asks a leaf for its verdict first and builds the paths
61
76
  // for its errors only when it fails.
@@ -183,11 +198,17 @@ function install(deps) {
183
198
  // chain of guarded checks: the interpreted engine spent a fifth of a
184
199
  // document in those calls.
185
200
  const reqFolded = P.required !== null && P.requiredUndef === null && P.properties !== null;
201
+ // The operands of the commonest leaves, for an object walk that checks
202
+ // them in place (leafCode below): bounds that are only minimum and
203
+ // maximum, and string checks that are only lengths and a pattern.
204
+ let nLo = -Infinity, nHi = Infinity, nPlain = true;
205
+ let sLo = -1, sHi = -1, sRe = null, sPlain = true;
186
206
  let numsRun = null;
187
207
  if (P.hasNumber) {
188
208
  const nums = [];
189
- if (P.minimum !== undefined) { const m = P.minimum; nums.push((d) => d >= m); }
190
- if (P.maximum !== undefined) { const m = P.maximum; nums.push((d) => d <= m); }
209
+ if (P.minimum !== undefined) { const m = P.minimum; nLo = m; nums.push((d) => d >= m); }
210
+ if (P.maximum !== undefined) { const m = P.maximum; nHi = m; nums.push((d) => d <= m); }
211
+ if (P.exclusiveMinimum !== undefined || P.exclusiveMaximum !== undefined || P.multipleOf !== undefined) nPlain = false;
191
212
  if (P.exclusiveMinimum !== undefined) { const m = P.exclusiveMinimum; nums.push((d) => d > m); }
192
213
  if (P.exclusiveMaximum !== undefined) { const m = P.exclusiveMaximum; nums.push((d) => d < m); }
193
214
  if (P.multipleOf !== undefined) { const m = P.multipleOf; nums.push((d) => multipleOfOk(d, m)); }
@@ -196,6 +217,10 @@ function install(deps) {
196
217
  let strsRun = null;
197
218
  if (P.hasString) {
198
219
  const strs = [];
220
+ if (P.minLength !== undefined) sLo = P.minLength;
221
+ if (P.maxLength !== undefined) sHi = P.maxLength;
222
+ if (P.pattern !== null) sRe = P.pattern;
223
+ if (P.formatFn !== null) sPlain = false;
199
224
  if (P.minLength !== undefined && P.maxLength !== undefined) {
200
225
  const lo = P.minLength, hi = P.maxLength;
201
226
  strs.push((d) => { const n = d.length; return (n >= 2 * lo && n <= hi) || (cpAtLeast(d, lo) && cpAtMost(d, hi)); });
@@ -254,10 +279,10 @@ function install(deps) {
254
279
  if (!foreign) {
255
280
  const r = own;
256
281
  switch (m) {
257
- case T_STRING: return r === null ? (d) => typeof d === 'string' : (d) => typeof d === 'string' && r(d);
258
- case T_INTEGER: return r === null ? (d) => Number.isInteger(d) : (d) => Number.isInteger(d) && r(d);
259
- case T_NUMBER: return r === null ? (d) => typeof d === 'number' && isFinite(d) : (d) => typeof d === 'number' && isFinite(d) && r(d);
260
- case T_BOOLEAN: return (d) => typeof d === 'boolean';
282
+ case T_STRING: return leafCode(r === null ? (d) => typeof d === 'string' : (d) => typeof d === 'string' && r(d), sPlain ? [LC_STR, sLo, sHi, sRe] : null);
283
+ case T_INTEGER: return leafCode(r === null ? (d) => Number.isInteger(d) : (d) => Number.isInteger(d) && r(d), nPlain ? [LC_INT, nLo, nHi] : null);
284
+ case T_NUMBER: return leafCode(r === null ? (d) => typeof d === 'number' && isFinite(d) : (d) => typeof d === 'number' && isFinite(d) && r(d), nPlain ? [LC_NUM, nLo, nHi] : null);
285
+ case T_BOOLEAN: return leafCode((d) => typeof d === 'boolean', [LC_BOOL]);
261
286
  case T_NULL: return (d) => d === null;
262
287
  case T_OBJECT: return r === null ? (d) => typeof d === 'object' && d !== null && !Array.isArray(d) : (d) => typeof d === 'object' && d !== null && !Array.isArray(d) && r(d);
263
288
  case T_ARRAY: return r === null ? (d) => Array.isArray(d) : (d) => Array.isArray(d) && r(d);
@@ -279,7 +304,16 @@ function install(deps) {
279
304
  }
280
305
  if (checks.length === 0) return TRUE_FN;
281
306
  if (!needsBits) {
282
- if (checks.length === 1) { const a = checks[0]; return (data) => a(data, 0); }
307
+ if (checks.length === 1) {
308
+ const a = checks[0];
309
+ const fn = (data) => a(data, 0);
310
+ // An enum of primitives alone, or a primitive const alone.
311
+ if (!P.hasType && !P.hasCustom && numsRun === null && strsRun === null && arrsRun === null && objsRun === null) {
312
+ if (P.enum !== null && P.enum.length > 0 && P.enumObjs.length === 0) return leafCode(fn, [LC_ENUM, P.enumPrims]);
313
+ if (P.hasConst && P.enum === null && (P.const === null || typeof P.const !== 'object')) return leafCode(fn, [LC_CONST, P.const]);
314
+ }
315
+ return fn;
316
+ }
283
317
  return (data) => {
284
318
  for (let i = 0; i < checks.length; i++) if (!checks[i](data, 0)) return false;
285
319
  return true;
@@ -297,6 +331,16 @@ function install(deps) {
297
331
  };
298
332
  }
299
333
 
334
+ // A leaf check that an object walk may run in place, by its code and
335
+ // operands, instead of calling it: one call site calling every property's
336
+ // check cannot be inlined, and on a form the calls were most of the
337
+ // verdict. leafOk must answer as the function does; tests/test_plan_leaf_codes.js
338
+ // holds the two to each other.
339
+ function leafCode(fn, code) {
340
+ if (code !== null) fn.lc = code;
341
+ return fn;
342
+ }
343
+
300
344
  // Folds a group's checks into one call, or null when the group is empty.
301
345
  function combineValue(fns) {
302
346
  if (fns.length === 0) return null;
@@ -704,6 +748,39 @@ function install(deps) {
704
748
  }
705
749
  return seenReq === reqN;
706
750
  });
751
+ // From three names to 32: below three the key array costs less than
752
+ // the for-in (1.7 ns on one name), past 32 the scan costs more.
753
+ if (apFalse && patterns === null && pnFn === null && !collect && propKeys !== null && propKeys.length >= 3 && propKeys.length <= 32) {
754
+ // A closed object with a short list of names, on an ordinary
755
+ // object: walk its keys with for-in and find each in the list, no
756
+ // key array and no Map. A key the list lacks is extra when it is
757
+ // the object's own (an enumerable name on Object.prototype is not).
758
+ // A listed key is read directly: for-in meets Object.prototype's
759
+ // own names (`constructor`) only on the object itself, so this is
760
+ // the short walk's rule. Other objects take the walk above.
761
+ const keyed = vsteps.pop();
762
+ const n = propKeys.length;
763
+ const fns = propList.map((e) => e.fn);
764
+ const reqs = propList.map((e) => e.req);
765
+ // A property whose check is a coded leaf is checked in place.
766
+ const codes = propList.map((e) => (e.fn.leaf && e.fn.v.lc !== undefined ? e.fn.v.lc : null));
767
+ vsteps.push((data, stack, rec) => {
768
+ if (dataBits(data) !== T_OBJECT || Object.getPrototypeOf(data) !== Object.prototype) return keyed(data, stack, rec);
769
+ if (fusedLeaf !== null && !fusedLeaf(data)) return false;
770
+ let seen = 0;
771
+ for (const key in data) {
772
+ let i = 0;
773
+ while (i < n && propKeys[i] !== key) i++;
774
+ if (i === n) { if (Object.hasOwn(data, key)) return false; continue; }
775
+ const value = data[key];
776
+ if (value === undefined) { if (reqs[i]) return false; continue; }
777
+ const lc = codes[i];
778
+ if (lc !== null ? !leafOk(lc, value) : !fns[i].v(value, stack)) return false;
779
+ if (reqs[i]) seen++;
780
+ }
781
+ return seen === reqN;
782
+ });
783
+ }
707
784
  }
708
785
  }
709
786
  if (P.dependentSchemas !== null) {
package/lib/public-api.js CHANGED
@@ -58,6 +58,11 @@ function attachSuggestions (errors, data) {
58
58
  return errors;
59
59
  }
60
60
 
61
+ // Loaded on first call, like the tools: a process that never calls them does
62
+ // not read the file.
63
+ function defineSchema (schema) { return require('./schema-helpers.js').defineSchema(schema); }
64
+ function schemaOf (...a) { return require('./schema-helpers.js').schemaOf(...a); }
65
+
61
66
  module.exports = {
62
67
  Validator: core.Validator,
63
68
  compile: core.compile,
@@ -69,8 +74,8 @@ module.exports = {
69
74
  SIMDJSON_PADDING: core.SIMDJSON_PADDING,
70
75
  parseJSON: core.parseJSON,
71
76
  toTypeScript,
72
- defineSchema: core.defineSchema,
73
- schemaOf: core.schemaOf,
77
+ defineSchema,
78
+ schemaOf,
74
79
  renderPretty,
75
80
  renderCompact,
76
81
  toOutput,
package/lib/rejections.js CHANGED
@@ -159,7 +159,9 @@ function stripOrdinal(e) {
159
159
  // lib/branch-collapse.js builds, the only place these codes come from. Its
160
160
  // fields are copied by name, in that order, without the ordinal: walking
161
161
  // its keys with for-in was most of the cost of reading such an error.
162
- if (e.path !== undefined && e.docUrl === undefined && COLLAPSE_CODES.has(e.code)) {
162
+ // One that carries verbose's fields takes the copy of every key below,
163
+ // which keeps them; this one dropped them.
164
+ if (e.path !== undefined && e.docUrl === undefined && COLLAPSE_CODES.has(e.code) && !('parentSchema' in e)) {
163
165
  const out = { code: e.code, keyword: e.keyword, instancePath: e.instancePath, path: e.path, schemaPath: e.schemaPath, message: e.message, params: e.params };
164
166
  if ('branchErrors' in e) out.branchErrors = Array.isArray(e.branchErrors) ? e.branchErrors.map(stripOrdinal) : e.branchErrors;
165
167
  return out;