ata-validator 1.31.2 → 1.32.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/index.d.ts +10 -0
- package/index.js +113 -16
- package/lib/aot-impl.js +1 -271
- package/lib/clone-emit.js +302 -0
- package/lib/interpreter.js +10 -4
- package/lib/js-compiler.js +155 -21
- package/lib/plan-compiler.js +1 -1
- package/lib/plan-source.js +281 -0
- package/lib/tier0.js +2 -2
- package/lib/version.js +1 -1
- package/package.json +9 -9
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// The copy behind parse(): shared by the parse() export of an ahead-of-time
|
|
4
|
+
// module (lib/aot-impl.js) and the runtime's Validator#parse (index.js). It
|
|
5
|
+
// lives apart from the module emitter so the runtime can use it without
|
|
6
|
+
// pulling the emitter, and the safe-regex source it embeds, into a browser
|
|
7
|
+
// bundle.
|
|
8
|
+
|
|
9
|
+
const { compileToJSCodegen, unevalContributions } = require('./js-compiler');
|
|
10
|
+
|
|
11
|
+
// Emit an expression that rebuilds `access` from the properties the schema
|
|
12
|
+
// declares, dropping everything else. This is the sanitising counterpart to
|
|
13
|
+
// removeAdditional: instead of enumerating the input and deleting the keys
|
|
14
|
+
// that do not belong, it reads only the keys that do. That is O(schema)
|
|
15
|
+
// rather than O(input), it leaves the caller's object untouched, and it keeps
|
|
16
|
+
// one hidden class instead of walking the object through a transition per
|
|
17
|
+
// delete.
|
|
18
|
+
//
|
|
19
|
+
// Returns null when the clone cannot be proven exact, and the module then
|
|
20
|
+
// simply has no parse(). Declining is recoverable; a sanitiser that quietly
|
|
21
|
+
// drops a property the schema allows is not.
|
|
22
|
+
// Resolve a local JSON pointer ('#', '#/$defs/x', draft-7 '#/definitions/x',
|
|
23
|
+
// any '#/...' path) against the schema root. Returns null for anything else:
|
|
24
|
+
// external documents, anchors, URNs. Those stay with the runtime engines.
|
|
25
|
+
function resolveLocalPointer(root, ref) {
|
|
26
|
+
if (ref === '#') return root;
|
|
27
|
+
if (typeof ref !== 'string' || !ref.startsWith('#/')) return null;
|
|
28
|
+
let cur = root;
|
|
29
|
+
for (const raw of ref.slice(2).split('/')) {
|
|
30
|
+
let seg = raw.replace(/~1/g, '/').replace(/~0/g, '~');
|
|
31
|
+
if (cur === null || typeof cur !== 'object') return null;
|
|
32
|
+
if (!Object.prototype.hasOwnProperty.call(cur, seg)) {
|
|
33
|
+
try { seg = decodeURIComponent(seg); } catch (_) { return null; }
|
|
34
|
+
if (!Object.prototype.hasOwnProperty.call(cur, seg)) return null;
|
|
35
|
+
}
|
|
36
|
+
cur = cur[seg];
|
|
37
|
+
}
|
|
38
|
+
return cur;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Inline local, acyclic $refs before the clone proof runs, so a generated
|
|
42
|
+
// schema ($defs + $ref, the shape every schema generator emits) can still get
|
|
43
|
+
// a parse(). Only a node that is exactly a $ref plus annotations is replaced;
|
|
44
|
+
// a $ref with a constraining sibling, a cycle, an unresolvable target or an
|
|
45
|
+
// external reference is left in place, and emitClone declines it as before.
|
|
46
|
+
// The walk never mutates its input and never descends into data positions
|
|
47
|
+
// (default, const, enum, examples), where an object holding a "$ref" key is
|
|
48
|
+
// a value, not a schema.
|
|
49
|
+
const _INLINE_ANNOTATIONS = new Set(['$ref', 'title', 'description', '$comment', 'examples', 'deprecated', 'readOnly', 'writeOnly', 'default']);
|
|
50
|
+
const _INLINE_DATA_KEYS = new Set(['default', 'const', 'enum', 'examples']);
|
|
51
|
+
function inlineRefsForClone(root) {
|
|
52
|
+
const state = { budget: 512 };
|
|
53
|
+
// `inRef` marks content brought in from behind a reference. The runtime's
|
|
54
|
+
// useDefaults fills a default written next to the $ref, and does not fill
|
|
55
|
+
// defaults written inside the referenced definition, so the inlined copy
|
|
56
|
+
// keeps the first and drops the second. parse() must stay exactly as
|
|
57
|
+
// generous as validate(); a parse that fills more is a disagreement, not a
|
|
58
|
+
// feature.
|
|
59
|
+
const walk = (node, active, inRef) => {
|
|
60
|
+
if (!node || typeof node !== 'object') return node;
|
|
61
|
+
if (Array.isArray(node)) return node.map((n) => walk(n, active, inRef));
|
|
62
|
+
let cur = node;
|
|
63
|
+
let entered = false;
|
|
64
|
+
let siblingDefault;
|
|
65
|
+
while (cur && typeof cur === 'object' && !Array.isArray(cur) && typeof cur.$ref === 'string') {
|
|
66
|
+
for (const k of Object.keys(cur)) if (!_INLINE_ANNOTATIONS.has(k)) return node;
|
|
67
|
+
if (active.has(cur.$ref) || state.budget-- <= 0) return node;
|
|
68
|
+
const target = resolveLocalPointer(root, cur.$ref);
|
|
69
|
+
if (!target || typeof target !== 'object' || Array.isArray(target)) return node;
|
|
70
|
+
if (!entered && !inRef && cur.default !== undefined) siblingDefault = cur.default;
|
|
71
|
+
active = new Set(active);
|
|
72
|
+
active.add(cur.$ref);
|
|
73
|
+
cur = target;
|
|
74
|
+
entered = true;
|
|
75
|
+
}
|
|
76
|
+
const strip = inRef || entered;
|
|
77
|
+
const out = {};
|
|
78
|
+
for (const k of Object.keys(cur)) {
|
|
79
|
+
if (strip && k === 'default') continue;
|
|
80
|
+
const v = cur[k];
|
|
81
|
+
if (_INLINE_DATA_KEYS.has(k) || k === '$defs' || k === 'definitions') { out[k] = v; continue; }
|
|
82
|
+
out[k] = walk(v, active, strip);
|
|
83
|
+
}
|
|
84
|
+
if (siblingDefault !== undefined) out.default = siblingDefault;
|
|
85
|
+
return out;
|
|
86
|
+
};
|
|
87
|
+
return walk(root, new Set(), false);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const PROTO_KEY_NAMES = new Set([...Object.getOwnPropertyNames(Object.prototype), '__proto__']);
|
|
91
|
+
|
|
92
|
+
function emitClone(node, access, depth) {
|
|
93
|
+
if (!node || typeof node !== 'object') return null;
|
|
94
|
+
if (depth > 12) return null;
|
|
95
|
+
// Only plain object nodes are cloneable, and only when the schema names
|
|
96
|
+
// every property it keeps. A $ref means the set of allowed keys is not
|
|
97
|
+
// this node's to decide. In-place applicators (allOf, anyOf, oneOf,
|
|
98
|
+
// if/then/else) are admitted under the same proof the unevaluated* error
|
|
99
|
+
// generators use: every property name they contribute must already be
|
|
100
|
+
// declared in this node's own `properties`, so they constrain values but
|
|
101
|
+
// never widen the key set. `not` never widens it. `unevaluatedProperties`
|
|
102
|
+
// is admitted only as `false` under that proof, where it is exactly
|
|
103
|
+
// `additionalProperties: false`.
|
|
104
|
+
if (node.$ref || node.patternProperties || node.additionalProperties === true) {
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
// additionalProperties as a schema is the record shape (z.record and every
|
|
108
|
+
// generator's map type): the key set is open by declaration, so keeping
|
|
109
|
+
// every key is exact, and every undeclared value is rebuilt against that
|
|
110
|
+
// one schema. That is a proof, not a guess, so it is admitted. What stays
|
|
111
|
+
// out: applicators next to a record (a branch could constrain some keys'
|
|
112
|
+
// values beyond the record schema, and the rebuild would ignore it), and
|
|
113
|
+
// `additionalProperties: true`, where the value is unconstrained and this
|
|
114
|
+
// pass only copies what it can name. An open object with no
|
|
115
|
+
// `additionalProperties` at all also stays out, for the original reason:
|
|
116
|
+
// validate() accepts its unknown keys, so a parse() that strips them would
|
|
117
|
+
// silently drop allowed data, and one that keeps them raw would not be a
|
|
118
|
+
// sanitiser.
|
|
119
|
+
const apSchema = (node.additionalProperties && typeof node.additionalProperties === 'object')
|
|
120
|
+
? node.additionalProperties
|
|
121
|
+
: null;
|
|
122
|
+
if (apSchema) {
|
|
123
|
+
if (node.unevaluatedProperties !== undefined) return null;
|
|
124
|
+
if (node.allOf || node.anyOf || node.oneOf || node.if || node.then || node.else ||
|
|
125
|
+
node.dependentSchemas !== undefined || node.dependencies !== undefined ||
|
|
126
|
+
node.$dynamicRef !== undefined || node.$recursiveRef !== undefined) return null;
|
|
127
|
+
}
|
|
128
|
+
if (node.unevaluatedProperties !== undefined && node.unevaluatedProperties !== false) return null;
|
|
129
|
+
if (node.type !== 'object') return null;
|
|
130
|
+
if (!node.properties && !apSchema) return null;
|
|
131
|
+
const keys = node.properties ? Object.keys(node.properties) : [];
|
|
132
|
+
if (keys.length === 0 && !apSchema) return null;
|
|
133
|
+
if (node.allOf || node.anyOf || node.oneOf || node.if || node.then || node.else ||
|
|
134
|
+
node.unevaluatedProperties === false) {
|
|
135
|
+
if (node.$dynamicRef !== undefined || node.$recursiveRef !== undefined ||
|
|
136
|
+
node.dependentSchemas !== undefined || node.dependencies !== undefined) return null;
|
|
137
|
+
const names = new Set();
|
|
138
|
+
const state = { prefix: 0 };
|
|
139
|
+
for (const k of ['allOf', 'anyOf', 'oneOf']) {
|
|
140
|
+
if (node[k] !== undefined) {
|
|
141
|
+
if (!Array.isArray(node[k])) return null;
|
|
142
|
+
for (const b of node[k]) if (!unevalContributions(b, 'props', names, state, 0)) return null;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
for (const k of ['if', 'then', 'else']) {
|
|
146
|
+
if (node[k] !== undefined && !unevalContributions(node[k], 'props', names, state, 0)) return null;
|
|
147
|
+
}
|
|
148
|
+
for (const n of names) if (!Object.prototype.hasOwnProperty.call(node.properties, n)) return null;
|
|
149
|
+
}
|
|
150
|
+
const required = new Set(Array.isArray(node.required) ? node.required : []);
|
|
151
|
+
|
|
152
|
+
const fixed = [];
|
|
153
|
+
const conditional = [];
|
|
154
|
+
for (const key of keys) {
|
|
155
|
+
const prop = node.properties[key];
|
|
156
|
+
if (!prop || typeof prop !== 'object') return null;
|
|
157
|
+
if (prop.$ref) return null;
|
|
158
|
+
// A property that is required and carries a default is the one place the
|
|
159
|
+
// runtime and a raw-input validation disagree: the runtime fills the
|
|
160
|
+
// default before checking `required`, so `{}` validates there and would
|
|
161
|
+
// throw here. Declining keeps parse() exactly as strict as validate().
|
|
162
|
+
if (required.has(key) && prop.default !== undefined) return null;
|
|
163
|
+
// A default the property's own schema rejects would make parse() hand
|
|
164
|
+
// back a document validate() refuses: the runtime fills defaults before
|
|
165
|
+
// validating, so it catches the bad default, and a raw-input check here
|
|
166
|
+
// never sees it. Probe the default at emit time and decline when it does
|
|
167
|
+
// not hold, or when the subschema cannot be compiled to check it.
|
|
168
|
+
if (prop.default !== undefined) {
|
|
169
|
+
let probe = null;
|
|
170
|
+
try { probe = compileToJSCodegen(prop, null, null); } catch (_) { probe = null; }
|
|
171
|
+
if (!probe || !probe(prop.default)) return null;
|
|
172
|
+
}
|
|
173
|
+
const read = `${access}[${JSON.stringify(key)}]`;
|
|
174
|
+
const value = emitValueExpr(prop, read, depth);
|
|
175
|
+
if (value === null) return null;
|
|
176
|
+
// A plain `"__proto__":` in an object literal is the prototype slot, not
|
|
177
|
+
// a property; `["__proto__"]:` is a property. Only that key is written as
|
|
178
|
+
// a computed one: a literal with computed keys cannot use the engine's
|
|
179
|
+
// literal boilerplate and is built key by key, which made the copy cost
|
|
180
|
+
// about as much as the validation in front of it.
|
|
181
|
+
if (required.has(key)) fixed.push(key === '__proto__' ? `[${JSON.stringify(key)}]: ${value}` : `${JSON.stringify(key)}: ${value}`);
|
|
182
|
+
else conditional.push({ key, value, dflt: prop.default });
|
|
183
|
+
}
|
|
184
|
+
if (fixed.length === 0 && conditional.length === 0 && !apSchema) return null;
|
|
185
|
+
|
|
186
|
+
const tmp = `_c${depth}`;
|
|
187
|
+
// The record loop: every key of the input that is not a declared property
|
|
188
|
+
// is kept, its value rebuilt against the additionalProperties schema. The
|
|
189
|
+
// input is already validated, so every such value satisfies that schema;
|
|
190
|
+
// the rebuild only strips inside it. Defaults under a record value are
|
|
191
|
+
// dropped before emission because the runtime's useDefaults does not fill
|
|
192
|
+
// them there, and parse() must equal validate().data, not improve on it.
|
|
193
|
+
let recordLoop = '';
|
|
194
|
+
if (apSchema) {
|
|
195
|
+
const kVar = `_k${depth}`;
|
|
196
|
+
const vVar = `_v${depth}`;
|
|
197
|
+
const valueExpr = emitValueExpr(stripDefaultsDeep(apSchema), vVar, depth);
|
|
198
|
+
if (valueExpr === null) return null;
|
|
199
|
+
let skip = '';
|
|
200
|
+
let declSet = '';
|
|
201
|
+
if (keys.length > 8) {
|
|
202
|
+
declSet = `const _d${depth} = new Set(${JSON.stringify(keys)}); `;
|
|
203
|
+
skip = `if (_d${depth}.has(${kVar})) continue; `;
|
|
204
|
+
} else if (keys.length > 0) {
|
|
205
|
+
skip = `if (${keys.map((k) => `${kVar} === ${JSON.stringify(k)}`).join(' || ')}) continue; `;
|
|
206
|
+
}
|
|
207
|
+
// The key comes from the input, so it can be the string "__proto__" (an
|
|
208
|
+
// own key only JSON.parse writes); assignment would hit the prototype
|
|
209
|
+
// setter, so that one key goes through defineProperty.
|
|
210
|
+
recordLoop = ` ${declSet}for (const ${kVar} of Object.keys(${access})) { ${skip}const ${vVar} = ${access}[${kVar}]; if (${kVar} === "__proto__") Object.defineProperty(${tmp}, ${kVar}, { value: ${valueExpr}, writable: true, enumerable: true, configurable: true }); else ${tmp}[${kVar}] = ${valueExpr}; }`;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const literal = `{ ${fixed.join(', ')} }`;
|
|
214
|
+
if (conditional.length === 0 && !recordLoop) return literal;
|
|
215
|
+
// Optional properties are added only when present, so the result never
|
|
216
|
+
// gains a key the input did not have.
|
|
217
|
+
const adds = conditional
|
|
218
|
+
.map(({ key, value, dflt }) => {
|
|
219
|
+
// Object.hasOwn, not `in`: `in` also answers for what the instance
|
|
220
|
+
// inherits, so a property named `constructor` would copy the inherited
|
|
221
|
+
// function into the output that validate().data does not carry. And a
|
|
222
|
+
// key named `__proto__` cannot be written by assignment, which hits
|
|
223
|
+
// the prototype setter; defineProperty writes an own key.
|
|
224
|
+
const assign = key === '__proto__'
|
|
225
|
+
? `Object.defineProperty(${tmp}, ${JSON.stringify(key)}, { value: ${value}, writable: true, enumerable: true, configurable: true });`
|
|
226
|
+
: `${tmp}[${JSON.stringify(key)}] = ${value};`;
|
|
227
|
+
// The same own-property test the generators emit, since Object.hasOwn
|
|
228
|
+
// is not inlined: `in`, then the prototype, then hasOwnProperty only
|
|
229
|
+
// for an object that is not an ordinary one.
|
|
230
|
+
const k = JSON.stringify(key);
|
|
231
|
+
const own = PROTO_KEY_NAMES.has(key)
|
|
232
|
+
? `Object.prototype.hasOwnProperty.call(${access}, ${k})`
|
|
233
|
+
: `(${k} in ${access} && (${access}.__proto__ === Object.prototype || Object.prototype.hasOwnProperty.call(${access}, ${k})))`;
|
|
234
|
+
const set = `if (${own}) ${assign}`;
|
|
235
|
+
// The default is emitted as a literal inside the call, so an object or
|
|
236
|
+
// array default is a fresh value on every parse, never shared state.
|
|
237
|
+
if (dflt === undefined) return set;
|
|
238
|
+
const setDflt = key === '__proto__'
|
|
239
|
+
? `Object.defineProperty(${tmp}, ${JSON.stringify(key)}, { value: ${JSON.stringify(dflt)}, writable: true, enumerable: true, configurable: true });`
|
|
240
|
+
: `${tmp}[${JSON.stringify(key)}] = ${JSON.stringify(dflt)};`;
|
|
241
|
+
return `${set} else ${setDflt}`;
|
|
242
|
+
})
|
|
243
|
+
.join(' ');
|
|
244
|
+
return `(function(){ const ${tmp} = ${literal}; ${adds}${recordLoop} return ${tmp}; })()`;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// The value dispatch shared by declared properties and the record loop. A
|
|
248
|
+
// value is copyable only when the emitter can name everything that may live
|
|
249
|
+
// under it: a primitive holds nothing, an object with declared properties or
|
|
250
|
+
// a record schema is rebuilt the same way, an array of primitives has
|
|
251
|
+
// nowhere to hide a key. A $ref, a composition or an untyped node could all
|
|
252
|
+
// carry keys this code cannot see, and copying the reference would smuggle
|
|
253
|
+
// them into a value that claims to be sanitised, so the caller declines the
|
|
254
|
+
// whole clone instead.
|
|
255
|
+
// Drop every schema-position `default` in a subtree, leaving data positions
|
|
256
|
+
// (const, enum, examples) untouched. Used on a record's value schema, where
|
|
257
|
+
// the runtime's useDefaults fills nothing.
|
|
258
|
+
function stripDefaultsDeep(node) {
|
|
259
|
+
if (!node || typeof node !== 'object') return node;
|
|
260
|
+
if (Array.isArray(node)) return node.map(stripDefaultsDeep);
|
|
261
|
+
const out = {};
|
|
262
|
+
for (const k of Object.keys(node)) {
|
|
263
|
+
if (k === 'default') continue;
|
|
264
|
+
out[k] = _INLINE_DATA_KEYS.has(k) ? node[k] : stripDefaultsDeep(node[k]);
|
|
265
|
+
}
|
|
266
|
+
return out;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const _CLONE_PRIMITIVE = new Set(['string', 'number', 'integer', 'boolean', 'null']);
|
|
270
|
+
function emitValueExpr(prop, read, depth) {
|
|
271
|
+
if (!prop || typeof prop !== 'object') return null;
|
|
272
|
+
if (prop.$ref) return null;
|
|
273
|
+
const types = Array.isArray(prop.type) ? prop.type : [prop.type];
|
|
274
|
+
if (types.every((t) => _CLONE_PRIMITIVE.has(t))) return read;
|
|
275
|
+
const isRecordable = (s) => s.properties ||
|
|
276
|
+
(s.additionalProperties && typeof s.additionalProperties === 'object');
|
|
277
|
+
if (prop.type === 'object' && isRecordable(prop)) {
|
|
278
|
+
return emitClone(prop, read, depth + 1);
|
|
279
|
+
}
|
|
280
|
+
if (prop.type === 'array' && prop.items && typeof prop.items === 'object' &&
|
|
281
|
+
!Array.isArray(prop.items)) {
|
|
282
|
+
const it = prop.items;
|
|
283
|
+
const itemTypes = Array.isArray(it.type) ? it.type : [it.type];
|
|
284
|
+
if (itemTypes.every((t) => _CLONE_PRIMITIVE.has(t))) return read;
|
|
285
|
+
if (it.type === 'object' && isRecordable(it)) {
|
|
286
|
+
const el = '_e' + depth;
|
|
287
|
+
const inner = emitClone(it, el, depth + 1);
|
|
288
|
+
if (!inner) return null;
|
|
289
|
+
return `${read}.map((${el}) => (${inner}))`;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
return null;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// The copy as an expression over `data`, or null where the kept key set is
|
|
296
|
+
// not provable.
|
|
297
|
+
function cloneExprFor(schema) {
|
|
298
|
+
if (!schema || typeof schema !== 'object') return null;
|
|
299
|
+
return emitClone(inlineRefsForClone(schema), 'data', 0);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
module.exports = { cloneExprFor, emitClone, inlineRefsForClone, stripDefaultsDeep, resolveLocalPointer };
|
package/lib/interpreter.js
CHANGED
|
@@ -230,7 +230,7 @@ function cpAtMost(s, max) {
|
|
|
230
230
|
function typeMatches(type, d) {
|
|
231
231
|
switch (type) {
|
|
232
232
|
case 'string': return typeof d === 'string';
|
|
233
|
-
case 'number': return
|
|
233
|
+
case 'number': return Number.isFinite(d);
|
|
234
234
|
case 'integer': return typeof d === 'number' && Number.isInteger(d);
|
|
235
235
|
case 'boolean': return typeof d === 'boolean';
|
|
236
236
|
case 'null': return d === null;
|
|
@@ -529,7 +529,7 @@ function evalLeafV(P, data) {
|
|
|
529
529
|
if (!found) return false;
|
|
530
530
|
}
|
|
531
531
|
if (P.hasConst && !deepEqual(P.const, data)) return false;
|
|
532
|
-
if (P.hasNumber &&
|
|
532
|
+
if (P.hasNumber && Number.isFinite(data)) {
|
|
533
533
|
if (P.minimum !== undefined && !(data >= P.minimum)) return false;
|
|
534
534
|
if (P.maximum !== undefined && !(data <= P.maximum)) return false;
|
|
535
535
|
if (P.exclusiveMinimum !== undefined && !(data > P.exclusiveMinimum)) return false;
|
|
@@ -646,7 +646,7 @@ function evalLeaf(P, data, errors, instancePath, schemaPath) {
|
|
|
646
646
|
if (errors !== NOERRORS) errors.push(err('const', 'const', instancePath, schemaPath + '/const', { allowedValue: P.const }, 'must be equal to constant'));
|
|
647
647
|
valid = false;
|
|
648
648
|
}
|
|
649
|
-
if (P.hasNumber &&
|
|
649
|
+
if (P.hasNumber && Number.isFinite(data)) {
|
|
650
650
|
if (P.minimum !== undefined && !(data >= P.minimum)) { errors === NOERRORS || errors.push(err('minimum', 'minimum', instancePath, schemaPath + '/minimum', { comparison: '>=', limit: P.minimum }, `must be >= ${P.minimum}`)); valid = false; }
|
|
651
651
|
if (P.maximum !== undefined && !(data <= P.maximum)) { errors === NOERRORS || errors.push(err('maximum', 'maximum', instancePath, schemaPath + '/maximum', { comparison: '<=', limit: P.maximum }, `must be <= ${P.maximum}`)); valid = false; }
|
|
652
652
|
if (P.exclusiveMinimum !== undefined && !(data > P.exclusiveMinimum)) { errors === NOERRORS || errors.push(err('exclusiveMinimum', 'exclusiveMinimum', instancePath, schemaPath + '/exclusiveMinimum', { comparison: '>', limit: P.exclusiveMinimum }, `must be > ${P.exclusiveMinimum}`)); valid = false; }
|
|
@@ -937,7 +937,7 @@ class Interpreter {
|
|
|
937
937
|
}
|
|
938
938
|
|
|
939
939
|
// numbers
|
|
940
|
-
if (P.hasNumber &&
|
|
940
|
+
if (P.hasNumber && Number.isFinite(data)) {
|
|
941
941
|
if (P.minimum !== undefined && !(data >= P.minimum)) { errors === NOERRORS || errors.push(err('minimum', 'minimum', instancePath, schemaPath + '/minimum', { comparison: '>=', limit: P.minimum }, `must be >= ${P.minimum}`)); valid = false; }
|
|
942
942
|
if (P.maximum !== undefined && !(data <= P.maximum)) { errors === NOERRORS || errors.push(err('maximum', 'maximum', instancePath, schemaPath + '/maximum', { comparison: '<=', limit: P.maximum }, `must be <= ${P.maximum}`)); valid = false; }
|
|
943
943
|
if (P.exclusiveMinimum !== undefined && !(data > P.exclusiveMinimum)) { errors === NOERRORS || errors.push(err('exclusiveMinimum', 'exclusiveMinimum', instancePath, schemaPath + '/exclusiveMinimum', { comparison: '>', limit: P.exclusiveMinimum }, `must be > ${P.exclusiveMinimum}`)); valid = false; }
|
|
@@ -1255,3 +1255,9 @@ function createInterpreter(schema, options) {
|
|
|
1255
1255
|
}
|
|
1256
1256
|
|
|
1257
1257
|
module.exports = { createInterpreter };
|
|
1258
|
+
// For lib/plan-source.js, which emits source from the same plans and must call
|
|
1259
|
+
// the same helpers so the two cannot answer differently.
|
|
1260
|
+
Object.defineProperty(module.exports, '_planInternals', {
|
|
1261
|
+
value: { Plan, deepEqual, cpAtLeast, cpAtMost, multipleOfOk, dataBits, T_STRING, T_NUMBER, T_INTEGER, T_BOOLEAN, T_NULL, T_OBJECT, T_ARRAY, T_ANY },
|
|
1262
|
+
enumerable: false,
|
|
1263
|
+
});
|