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.
- package/README.md +63 -8
- package/build.mjs +1 -0
- package/index.browser.mjs +23 -4
- package/index.bundle.mjs +14 -4
- package/index.edge.mjs +14 -4
- package/index.js +7 -2
- package/lib/aot-build.js +18 -0
- package/lib/codegen-engine.js +72 -3
- package/lib/compiled-common.js +2 -1
- package/lib/email-literal.js +4 -1
- package/lib/formats-source.js +39 -40
- package/lib/interpreter-core.js +17 -9
- package/lib/ir/emit-deps.mjs +131 -0
- package/lib/ir/emit.mjs +560 -0
- package/lib/ir/front.mjs +834 -0
- package/lib/ir/index.mjs +10 -0
- package/lib/ir/refs.mjs +108 -0
- package/lib/ir/run.mjs +520 -0
- package/lib/ir/table.mjs +233 -0
- package/lib/js-compiler-errors.js +9 -4
- package/lib/kernels/collapse.mjs +114 -0
- package/lib/kernels/custom.mjs +70 -0
- package/lib/kernels/fill.mjs +6 -0
- package/lib/kernels/formats-small.mjs +40 -0
- package/lib/kernels/formats.mjs +29 -0
- package/lib/kernels/index.mjs +11 -0
- package/lib/kernels/kernels.mjs +285 -0
- package/lib/kernels/leaves.mjs +28 -0
- package/lib/kernels/order.mjs +25 -0
- package/lib/kernels/plan-rich.mjs +17 -0
- package/lib/kernels/plan.d.ts +9 -0
- package/lib/kernels/plan.mjs +128 -0
- package/lib/kernels/preprocess.mjs +223 -0
- package/lib/kernels/rank.mjs +97 -0
- package/lib/kernels/rt.mjs +249 -0
- package/lib/kernels/table.mjs +182 -0
- package/lib/kernels/unique.mjs +157 -0
- package/lib/native-positions.js +1 -1
- package/lib/openapi.d.ts +2 -0
- package/lib/openapi.js +128 -0
- package/lib/plan-compiler.js +84 -7
- package/lib/public-api.js +7 -2
- package/lib/rejections.js +3 -1
- package/lib/schema-helpers.js +12 -0
- package/lib/schema-helpers.mjs +20 -0
- package/lib/schema-order.js +4 -2
- package/lib/standard-schema.js +1 -1
- package/lib/validator-core.js +35 -30
- package/lib/version.js +1 -1
- package/lite.js +7 -2
- 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 };
|
package/lib/native-positions.js
CHANGED
|
@@ -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 {
|
|
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;
|
package/lib/openapi.d.ts
ADDED
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 };
|
package/lib/plan-compiler.js
CHANGED
|
@@ -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) {
|
|
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
|
|
73
|
-
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
|
-
|
|
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;
|