ata-validator 1.61.0 → 1.61.2
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 +34 -7
- package/compiled-bare-rich.d.ts +6 -0
- package/compiled-bare.d.ts +7 -0
- package/compiled-bare.js +5 -0
- package/compiled-bare.mjs +35 -0
- package/compiled-object.d.ts +11 -0
- package/compiled-object.js +6 -0
- package/compiled-object.mjs +3 -0
- package/compiled-plain.d.ts +16 -0
- package/compiled-plain.js +5 -0
- package/compiled-plain.mjs +3 -0
- package/index.aot.js +8 -0
- package/index.aot.mjs +5 -0
- package/lib/aot-build.js +9 -1
- package/lib/aot-impl.js +10 -2
- package/lib/aot-mark.js +7 -0
- package/lib/compiled-bare-rich.js +54 -0
- package/lib/compiled-bare.js +44 -0
- package/lib/compiled-common.js +17 -2
- package/lib/compiled-defaults.browser.js +9 -0
- package/lib/compiled-defaults.js +8 -0
- package/lib/compiled-object.js +123 -0
- package/lib/compiled-plain.js +57 -0
- package/lib/compiled.js +2 -1
- package/lib/email-literal.js +29 -0
- package/lib/enrich-error.js +5 -1
- package/lib/error-codes.js +6 -76
- package/lib/formats-source.js +20 -3
- package/lib/formats.js +28 -1
- package/lib/interpreter-core.js +4 -28
- package/lib/js-compiler-errors.js +4 -0
- package/lib/json-rejections.js +159 -0
- package/lib/native-codes.js +67 -0
- package/lib/native-message.js +8 -0
- package/lib/native.browser.js +11 -0
- package/lib/rejections.js +13 -152
- package/lib/shape-classifier.js +6 -1
- package/lib/standard-schema.js +1 -1
- package/lib/tier0.js +17 -4
- package/lib/validator-core.js +50 -19
- package/lib/verify.js +68 -3
- package/lib/version.js +1 -1
- package/package.json +54 -10
package/lib/rejections.js
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
// The rejection objects validate()
|
|
3
|
+
// The rejection objects validate() returns, and the helpers
|
|
4
4
|
// that build their errors on first read: declaration-order sorting,
|
|
5
5
|
// enrichment, typo correlation and source frames. Shared by the validator core
|
|
6
6
|
// and by the wrapper around a compiled module, so both report errors through
|
|
7
|
-
// the same code.
|
|
7
|
+
// the same code. The one validateJSON() returns is in lib/json-rejections.js.
|
|
8
8
|
|
|
9
9
|
const { ordinalFor: schemaOrdinal, rankFor: schemaRank } = require('./schema-order');
|
|
10
|
-
const { setDiagnosticSource: attachDiagnosticSource } = require('./diagnostic-source');
|
|
11
10
|
|
|
12
11
|
// Rejection result with errors materialized on first read. The accessor
|
|
13
12
|
// lives on the prototype so constructing one is a plain allocation; an
|
|
@@ -80,6 +79,15 @@ class RichRejection {
|
|
|
80
79
|
return raw;
|
|
81
80
|
}
|
|
82
81
|
}
|
|
82
|
+
// The position map builder of lib/json-rejections.js, which sets it when it
|
|
83
|
+
// loads. Only validateJSON passes JSON text here, and the code that calls it
|
|
84
|
+
// loads that module first (were it not, the call below throws, it does not
|
|
85
|
+
// skip the frames); validate() and a compiled wrapper without the JSON
|
|
86
|
+
// entry points never load it, so their bundles carry neither it nor the
|
|
87
|
+
// rejection validateJSON returns (532 bytes gzipped of the wrapper).
|
|
88
|
+
let _positionsFor = null;
|
|
89
|
+
function _setPositionsFor (fn) { _positionsFor = fn; }
|
|
90
|
+
|
|
83
91
|
// Orders a raw error list and gives it the shape the caller asked for: the
|
|
84
92
|
// legacy key set, or enriched. Shared by both rejection classes below.
|
|
85
93
|
function presentErrors(raw, data, rawInput, self, root, enrich) {
|
|
@@ -87,7 +95,7 @@ function presentErrors(raw, data, rawInput, self, root, enrich) {
|
|
|
87
95
|
// The position map, resolved on the first read of an error's frame (see
|
|
88
96
|
// defineLazyFrames in lib/enrich-error.js): a walk of the document that a
|
|
89
97
|
// caller reading messages and paths never needs.
|
|
90
|
-
const lazyPositions = enrich && raw.length && rawInput != null ?
|
|
98
|
+
const lazyPositions = enrich && raw.length && rawInput != null ? _positionsFor(self, rawInput, raw) : null;
|
|
91
99
|
// One options object for the whole list, not one per error.
|
|
92
100
|
const opts = enrich && raw.length
|
|
93
101
|
? {
|
|
@@ -126,153 +134,6 @@ Object.defineProperty(RichRejection.prototype, 'errors', {
|
|
|
126
134
|
});
|
|
127
135
|
|
|
128
136
|
|
|
129
|
-
// The rejection validateJSON returns. Everything the text path adds over
|
|
130
|
-
// validate(data), the value tree enrichment needs, the position map, the
|
|
131
|
-
// diagnostic payload, happens on first access to `.errors`. Reading `.valid`
|
|
132
|
-
// touches none of it. The JSON text is held here rather than read back off the
|
|
133
|
-
// validator, so the result does not depend on what the instance does next.
|
|
134
|
-
class LazyJsonRejection {
|
|
135
|
-
// `parsed`, when given, is the document the inner path parsed from
|
|
136
|
-
// `jsonStr` and validated, after defaults, coercion and removal: reading the
|
|
137
|
-
// errors uses it instead of parsing the text again, which on a 5 MB
|
|
138
|
-
// document was a third of what reading them cost.
|
|
139
|
-
constructor(result, jsonStr, self, enrich, parsed) {
|
|
140
|
-
this.valid = false;
|
|
141
|
-
this._result = result;
|
|
142
|
-
this._jsonStr = jsonStr;
|
|
143
|
-
this._self = self;
|
|
144
|
-
this._enrich = enrich;
|
|
145
|
-
this._cached = null;
|
|
146
|
-
this._parsed = parsed;
|
|
147
|
-
}
|
|
148
|
-
toJSON() {
|
|
149
|
-
return { valid: false, errors: this.errors };
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
Object.defineProperty(LazyJsonRejection.prototype, 'errors', {
|
|
153
|
-
enumerable: true,
|
|
154
|
-
configurable: true,
|
|
155
|
-
get() {
|
|
156
|
-
if (this._cached !== null) return this._cached;
|
|
157
|
-
const self = this._self;
|
|
158
|
-
const enrich = this._enrich;
|
|
159
|
-
const jsonStr = this._jsonStr;
|
|
160
|
-
// Reading the inner errors realizes the inner lazy layer, if there was one.
|
|
161
|
-
const raw = this._result.errors || [];
|
|
162
|
-
if (!raw.length) { this._cached = raw; return raw; }
|
|
163
|
-
|
|
164
|
-
// The enrich pass plucks `received` from the value tree and the suggestion
|
|
165
|
-
// engine (required-typo, format hints, coercion nudges) walks it too.
|
|
166
|
-
let parsedData = this._parsed;
|
|
167
|
-
if (parsedData === undefined) {
|
|
168
|
-
try { parsedData = JSON.parse(jsonStr); } catch { parsedData = undefined; }
|
|
169
|
-
// What was checked is the document after defaults, coercion and removal,
|
|
170
|
-
// so `received` has to come from that, as it does for validate(). A
|
|
171
|
-
// default that failed its own schema has no value in the text at all.
|
|
172
|
-
if (parsedData !== undefined && self._preprocess) self._preprocess(parsedData);
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
// Errors the inner path already enriched carry a `received` key: enrich()
|
|
176
|
-
// always sets one, even when there is no value to show. `code` is not a
|
|
177
|
-
// safe signal, because branch-collapse attaches codes to raw errors, and
|
|
178
|
-
// `docUrl` is not either, because the generated error functions stamp it
|
|
179
|
-
// on raw errors; taking it as the signal left those errors without
|
|
180
|
-
// `received` or a suggestion on the text path while validate() had both.
|
|
181
|
-
if (!raw[0] || !('received' in raw[0])) {
|
|
182
|
-
// Declaration order, as validate() applies it; the text path used to
|
|
183
|
-
// enrich in emission order.
|
|
184
|
-
const ordered = needsOrdering(raw) ? sortErrorsBySchemaOrder(self._schemaObj, raw) : raw;
|
|
185
|
-
// The position map waits for the first read of a frame (lib/enrich-error
|
|
186
|
-
// defineLazyFrames); until then reading the list costs the enrichment alone.
|
|
187
|
-
const lazyPositions = lazyPositionsFor(self, jsonStr, ordered);
|
|
188
|
-
const enrichOpts = {
|
|
189
|
-
data: parsedData,
|
|
190
|
-
positions: null,
|
|
191
|
-
lazyPositions,
|
|
192
|
-
schemaPositions: self._schemaPositions,
|
|
193
|
-
schemaFile: self._source ? self._source.path : undefined,
|
|
194
|
-
};
|
|
195
|
-
const enriched = ordered.map((e) => enrich(e, enrichOpts));
|
|
196
|
-
if (enriched.length > 1) attachRelated(enriched);
|
|
197
|
-
attachDiagnosticSource(enriched, {
|
|
198
|
-
data: parsedData,
|
|
199
|
-
text: jsonStr,
|
|
200
|
-
// Read by the diagnostic renderer only, which resolves the same map.
|
|
201
|
-
get positions () { return lazyPositions(); },
|
|
202
|
-
schema: self._schemaObj,
|
|
203
|
-
mutatesInput: self._mutatesInput === true,
|
|
204
|
-
});
|
|
205
|
-
this._cached = enriched;
|
|
206
|
-
return enriched;
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
// Already enriched, so the frames are usually attached too. Only a gap in
|
|
210
|
-
// them is worth another walk of the document: resolving the map to discover
|
|
211
|
-
// there was nothing to fill cost a second full walk on every rejection.
|
|
212
|
-
if (raw.some((e) => e && !e.dataFrame)) {
|
|
213
|
-
const positions = self._pos().targeted(jsonStr, wantedPointersFor(raw.filter((e) => e && !e.dataFrame)));
|
|
214
|
-
if (positions) {
|
|
215
|
-
for (const e of raw) {
|
|
216
|
-
if (e && !e.dataFrame) {
|
|
217
|
-
const path = e.path != null ? e.path : (e.instancePath || '');
|
|
218
|
-
const p = positions[path];
|
|
219
|
-
if (p) e.dataFrame = { byteOffset: p.byteOffset, length: p.length, line: p.line, col: p.col, text: p.text };
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
self._posCache.reset();
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
if (raw.length > 1) attachRelated(raw);
|
|
226
|
-
attachDiagnosticSource(raw, {
|
|
227
|
-
data: parsedData,
|
|
228
|
-
text: jsonStr,
|
|
229
|
-
schema: self._schemaObj,
|
|
230
|
-
mutatesInput: self._mutatesInput === true,
|
|
231
|
-
});
|
|
232
|
-
this._cached = raw;
|
|
233
|
-
return raw;
|
|
234
|
-
},
|
|
235
|
-
});
|
|
236
|
-
|
|
237
|
-
// The targeted position map for a list, built on the first call and shared
|
|
238
|
-
// by every error's frame accessor (lib/enrich-error defineLazyFrames).
|
|
239
|
-
function lazyPositionsFor (self, text, errors) {
|
|
240
|
-
let done = false;
|
|
241
|
-
let positions = null;
|
|
242
|
-
return () => {
|
|
243
|
-
if (done) return positions;
|
|
244
|
-
done = true;
|
|
245
|
-
positions = self._pos().targeted(text, wantedPointersFor(errors));
|
|
246
|
-
if (positions) self._posCache.reset();
|
|
247
|
-
return positions;
|
|
248
|
-
};
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
// The pointers a set of errors will ask the position map about. lib/enrich-error
|
|
252
|
-
// looks up the error's own path, and for an additional or unevaluated property
|
|
253
|
-
// the child pointer named in `params`, unescaped, which is the form it asks for.
|
|
254
|
-
// The escaped form goes in too: including a pointer that is never read costs
|
|
255
|
-
// nothing, and the cache answers anything outside this set from the full map
|
|
256
|
-
// rather than reporting no position.
|
|
257
|
-
function wantedPointersFor (errors) {
|
|
258
|
-
const wanted = new Set();
|
|
259
|
-
for (const e of errors) {
|
|
260
|
-
if (!e) continue;
|
|
261
|
-
const path = e.path != null ? e.path : (e.instancePath || '');
|
|
262
|
-
wanted.add(path);
|
|
263
|
-
if (e.instancePath != null && e.instancePath !== path) wanted.add(e.instancePath);
|
|
264
|
-
const params = e.params;
|
|
265
|
-
const named = params && (params.additionalProperty || params.unevaluatedProperty);
|
|
266
|
-
if (typeof named === 'string') {
|
|
267
|
-
wanted.add(path + '/' + named);
|
|
268
|
-
if (named.indexOf('~') !== -1 || named.indexOf('/') !== -1) {
|
|
269
|
-
wanted.add(path + '/' + named.replace(/~/g, '~0').replace(/\//g, '~1'));
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
return wanted;
|
|
274
|
-
}
|
|
275
|
-
|
|
276
137
|
// A raw error in the legacy shape. The error generator writes `code` and
|
|
277
138
|
// `docUrl` into its literals because a standalone module returns them as they
|
|
278
139
|
// are; the runtime's legacy shape never had them, and the interpreter leaves
|
|
@@ -411,4 +272,4 @@ function attachRelated (errors) {
|
|
|
411
272
|
return errors;
|
|
412
273
|
}
|
|
413
274
|
|
|
414
|
-
module.exports = { LazyRejection, RichRejection, presentErrors, needsOrdering,
|
|
275
|
+
module.exports = { LazyRejection, RichRejection, presentErrors, needsOrdering, _enrichLazy, attachRelated, sortErrorsBySchemaOrder, stripOrdinal, _setPositionsFor };
|
package/lib/shape-classifier.js
CHANGED
|
@@ -24,7 +24,7 @@ const TIER0_PRIMITIVE_ALLOWED = new Set([
|
|
|
24
24
|
'type', 'enum', 'const',
|
|
25
25
|
'minLength', 'maxLength',
|
|
26
26
|
'minimum', 'maximum', 'exclusiveMinimum', 'exclusiveMaximum',
|
|
27
|
-
'multipleOf',
|
|
27
|
+
'multipleOf', 'format',
|
|
28
28
|
...META_KEYS,
|
|
29
29
|
]);
|
|
30
30
|
|
|
@@ -54,6 +54,11 @@ function isTier0Primitive(schema) {
|
|
|
54
54
|
}
|
|
55
55
|
}
|
|
56
56
|
if (schema.const !== undefined && !isPrimitiveEnumValue(schema.const)) return false;
|
|
57
|
+
// `format` on a string, by name: tier 0 checks it with the table the
|
|
58
|
+
// interpreted engine reads (formats.formatChecks) or the caller's own
|
|
59
|
+
// function. A format on another type is a compat check for numbers, which
|
|
60
|
+
// the full engines handle.
|
|
61
|
+
if (schema.format !== undefined && (schema.type !== 'string' || typeof schema.format !== 'string')) return false;
|
|
57
62
|
return true;
|
|
58
63
|
}
|
|
59
64
|
|
package/lib/standard-schema.js
CHANGED
|
@@ -117,7 +117,7 @@ function standardSchemaFor(self, getSchema, validate) {
|
|
|
117
117
|
if (!message) {
|
|
118
118
|
// The native engine reports numeric codes without a message; the
|
|
119
119
|
// enrich pass knows how to word those. Rare, so required lazily.
|
|
120
|
-
message = require('./
|
|
120
|
+
message = require('./native-message')(err);
|
|
121
121
|
}
|
|
122
122
|
issues[i] = { message, path: parsePointerPath(path) };
|
|
123
123
|
}
|
package/lib/tier0.js
CHANGED
|
@@ -49,7 +49,17 @@ const _hop = Object.prototype.hasOwnProperty;
|
|
|
49
49
|
// Deno removes the __proto__ accessor; read the prototype another way there.
|
|
50
50
|
const _PN = typeof Bun === 'undefined' && ({}).__proto__ === Object.prototype; // JavaScriptCore goes through Object.getPrototypeOf, see js-compiler's _PN
|
|
51
51
|
|
|
52
|
-
function
|
|
52
|
+
// The check for a string's `format`: the caller's own function by that name,
|
|
53
|
+
// else the built-in one, else none (an unknown format is an annotation), as
|
|
54
|
+
// the interpreted engine picks it.
|
|
55
|
+
function formatFnOf(propSchema, userFormats) {
|
|
56
|
+
const f = propSchema.format;
|
|
57
|
+
if (typeof f !== 'string') return null;
|
|
58
|
+
if (userFormats && typeof userFormats[f] === 'function') return userFormats[f];
|
|
59
|
+
return require('./formats').formatChecks()[f] || null;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function primConstraint(key, propSchema, userFormats) {
|
|
53
63
|
const t = propSchema.type;
|
|
54
64
|
const hasEnum = Array.isArray(propSchema.enum);
|
|
55
65
|
const hasConst = propSchema.const !== undefined;
|
|
@@ -75,14 +85,15 @@ function primConstraint(key, propSchema) {
|
|
|
75
85
|
exclMin: typeof propSchema.exclusiveMinimum === 'number' ? propSchema.exclusiveMinimum : 0,
|
|
76
86
|
exclMax: typeof propSchema.exclusiveMaximum === 'number' ? propSchema.exclusiveMaximum : 0,
|
|
77
87
|
multipleOf: typeof propSchema.multipleOf === 'number' ? propSchema.multipleOf : 0,
|
|
88
|
+
formatFn: propSchema.type === 'string' ? formatFnOf(propSchema, userFormats) : null,
|
|
78
89
|
};
|
|
79
90
|
}
|
|
80
91
|
|
|
81
|
-
function buildTier0Plan(schema) {
|
|
92
|
+
function buildTier0Plan(schema, userFormats) {
|
|
82
93
|
if (schema.type !== 'object') {
|
|
83
94
|
return {
|
|
84
95
|
isPrimitive: true,
|
|
85
|
-
constraints: [primConstraint('__root__', schema)],
|
|
96
|
+
constraints: [primConstraint('__root__', schema, userFormats)],
|
|
86
97
|
requiredMask: 0,
|
|
87
98
|
additionalAllowed: true,
|
|
88
99
|
knownKeys: null,
|
|
@@ -96,7 +107,7 @@ function buildTier0Plan(schema) {
|
|
|
96
107
|
let requiredMask = 0;
|
|
97
108
|
for (let i = 0; i < keys.length; i++) {
|
|
98
109
|
const k = keys[i];
|
|
99
|
-
constraints[i] = primConstraint(k, props[k]);
|
|
110
|
+
constraints[i] = primConstraint(k, props[k], userFormats);
|
|
100
111
|
knownKeys.add(k);
|
|
101
112
|
if (required && required.has(k)) requiredMask |= (1 << i);
|
|
102
113
|
}
|
|
@@ -128,6 +139,7 @@ function checkPrimitive(c, v) {
|
|
|
128
139
|
const maxLen = c.maxLen;
|
|
129
140
|
if (minLen >= 0) { const l = v.length; if (l < minLen) return false; if (l < minLen * 2 && codePointLength(v) < minLen) return false; }
|
|
130
141
|
if (maxLen >= 0) { const l = v.length; if (l > maxLen && codePointLength(v) > maxLen) return false; }
|
|
142
|
+
if (c.formatFn !== null && !c.formatFn(v)) return false;
|
|
131
143
|
} else if (m === T_INTEGER) {
|
|
132
144
|
if (typeof v !== 'number' || !Number.isInteger(v)) return false;
|
|
133
145
|
const f = c.numFlags;
|
|
@@ -186,6 +198,7 @@ function tier0ValidateObject(plan, data) {
|
|
|
186
198
|
const maxLen = c.maxLen;
|
|
187
199
|
if (minLen >= 0) { const l = v.length; if (l < minLen) return false; if (l < minLen * 2 && codePointLength(v) < minLen) return false; }
|
|
188
200
|
if (maxLen >= 0) { const l = v.length; if (l > maxLen && codePointLength(v) > maxLen) return false; }
|
|
201
|
+
if (c.formatFn !== null && !c.formatFn(v)) return false;
|
|
189
202
|
} else if (m === T_INTEGER) {
|
|
190
203
|
if (typeof v !== 'number' || !Number.isInteger(v)) return false;
|
|
191
204
|
const f = c.numFlags;
|
package/lib/validator-core.js
CHANGED
|
@@ -19,10 +19,17 @@ const { normalizeKeywords, schemaUsesKeywords } = require('./keywords');
|
|
|
19
19
|
// bundle built from lite.js never contains it. Until something registers, the
|
|
20
20
|
// core behaves exactly as it does where `new Function` is blocked: every
|
|
21
21
|
// schema runs on the interpreted engine, which passes the same test suite.
|
|
22
|
-
// Whether this process runs code compiled ahead of time, where
|
|
23
|
-
//
|
|
24
|
-
//
|
|
22
|
+
// Whether this process runs code compiled ahead of time, where code made at
|
|
23
|
+
// run time with `new Function` falls to an interpreter and ata's interpreted
|
|
24
|
+
// engine, compiled with the program, is six times faster (481 against 78 ns
|
|
25
|
+
// a document under bun build --compile --aot). Bun resolves the package's
|
|
26
|
+
// `"aot"` export condition there, which loads index.aot.js and marks the
|
|
27
|
+
// process (_markAotHost); `Bun.isAOT` was the earlier signal, and
|
|
28
|
+
// ATA_ENGINE=interpreter says so from outside.
|
|
29
|
+
let _aotEntry = false;
|
|
30
|
+
function _markAotHost() { _aotEntry = true; }
|
|
25
31
|
function aotHost() {
|
|
32
|
+
if (_aotEntry) return true;
|
|
26
33
|
if (env('ATA_ENGINE') === 'interpreter') return true;
|
|
27
34
|
const b = typeof Bun !== 'undefined' ? Bun : undefined;
|
|
28
35
|
return !!(b && b.isAOT === true);
|
|
@@ -353,7 +360,14 @@ function resolveSchemaByPath(rootSchema, schemaPath) {
|
|
|
353
360
|
// by every page that loads the package, validating or not.
|
|
354
361
|
let LazyRejection, RichRejection, LazyJsonRejection, _enrichLazy, presentErrors, needsOrdering, sortErrorsBySchemaOrder, stripOrdinal;
|
|
355
362
|
function _loadRejections() {
|
|
356
|
-
({ LazyRejection, RichRejection,
|
|
363
|
+
({ LazyRejection, RichRejection, _enrichLazy, presentErrors, needsOrdering, sortErrorsBySchemaOrder, stripOrdinal } = require('./rejections'));
|
|
364
|
+
}
|
|
365
|
+
// The rejection validateJSON returns, and the position map an error's frame
|
|
366
|
+
// reads, loaded with the first rejection that carries JSON text: a process
|
|
367
|
+
// that only ever calls validate() never parses them (lib/json-rejections.js,
|
|
368
|
+
// lib/diagnostic-source.js).
|
|
369
|
+
function _loadJsonRejections() {
|
|
370
|
+
if (LazyJsonRejection === undefined) ({ LazyJsonRejection } = require('./json-rejections'));
|
|
357
371
|
}
|
|
358
372
|
|
|
359
373
|
// Text that does not parse, reported the same way on every path and platform:
|
|
@@ -1206,7 +1220,13 @@ class Validator {
|
|
|
1206
1220
|
// once per call: deciding first and walking again for the errors would
|
|
1207
1221
|
// run the keyword twice, the second time on what the first left.
|
|
1208
1222
|
const once = interp.dataContext === true;
|
|
1209
|
-
|
|
1223
|
+
// A schema tier 0 takes answers its verdict there, with or without a
|
|
1224
|
+
// call to validate() first: the shared walk over a flat plan beats the
|
|
1225
|
+
// closure tree, 76 to 60 ns on Node and 85 to 49 under Bun's compiler,
|
|
1226
|
+
// where the closures are not typed. Not for custom keywords, which
|
|
1227
|
+
// tier 0 does not know.
|
|
1228
|
+
const t0 = !preprocess && !once && !this._usesKeywords ? _tier0Verdict(this) : null;
|
|
1229
|
+
if (!preprocess && !once) this._fastVerdict = t0 !== null ? t0 : (d) => interp.isValid(d);
|
|
1210
1230
|
if (preprocess && !once) _preVerdict = (d) => { preprocess(d); return interp.isValid(d); };
|
|
1211
1231
|
// abortEarly is a documented contract, not a property of whichever engine
|
|
1212
1232
|
// answered: it promises the frozen ATA9000 stub instead of a detailed
|
|
@@ -1284,6 +1304,7 @@ class Validator {
|
|
|
1284
1304
|
// literal with a getter here cost a closure plus an accessor
|
|
1285
1305
|
// definition on every rejection, several hundred nanoseconds
|
|
1286
1306
|
// before any error was read.
|
|
1307
|
+
if (rawInput != null) _loadJsonRejections();
|
|
1287
1308
|
return new RichRejection(result, data, rawInput, self, root, enrich);
|
|
1288
1309
|
}
|
|
1289
1310
|
return result;
|
|
@@ -1320,6 +1341,7 @@ class Validator {
|
|
|
1320
1341
|
// and on the codegen path that realizes the inner lazy layer, which is
|
|
1321
1342
|
// the document walk this exists to avoid.
|
|
1322
1343
|
if (result && result.valid === false) {
|
|
1344
|
+
_loadJsonRejections();
|
|
1323
1345
|
return new LazyJsonRejection(result, jsonStr, this, enrich, parsed);
|
|
1324
1346
|
}
|
|
1325
1347
|
return result;
|
|
@@ -2323,6 +2345,22 @@ for (const [name, pick] of [
|
|
|
2323
2345
|
|
|
2324
2346
|
// Cold start: an interpreted twin answers a large schema's first calls where
|
|
2325
2347
|
// the code generator is registered (see coldTwin in index.js). Lite has none.
|
|
2348
|
+
// Whether this validator, in the browser build before the generator has
|
|
2349
|
+
// come, counts its calls so a busy one moves to generated code (lib/tiering.js).
|
|
2350
|
+
function _ticks(self) {
|
|
2351
|
+
return _codegen === null && _tiering !== null && !self._interpretOnly && !env('ATA_FORCE_NAPI');
|
|
2352
|
+
}
|
|
2353
|
+
// The tier-0 verdict for a schema it takes, or null: counting calls where
|
|
2354
|
+
// _ticks says so.
|
|
2355
|
+
function _tier0Verdict(self) {
|
|
2356
|
+
if (require('./shape-classifier').classify(self._schemaObj).tier !== 0) return null;
|
|
2357
|
+
const { buildTier0Plan, tier0Validate } = require('./tier0');
|
|
2358
|
+
const plan = buildTier0Plan(self._schemaObj, self._userFormats);
|
|
2359
|
+
if (!_ticks(self)) return (d) => tier0Validate(plan, d);
|
|
2360
|
+
const tick = _tiering.counter(self);
|
|
2361
|
+
return (d) => { tick(); return tier0Validate(plan, d); };
|
|
2362
|
+
}
|
|
2363
|
+
|
|
2326
2364
|
Validator.prototype._coldTwin = function () {
|
|
2327
2365
|
const cg = _loadCodegen();
|
|
2328
2366
|
return cg === null || cg.coldTwin === undefined ? null : cg.coldTwin(this, Validator);
|
|
@@ -2354,21 +2392,13 @@ _defineLazyMethod('isValidObject', (self) => {
|
|
|
2354
2392
|
return self.isValidObject(data);
|
|
2355
2393
|
}
|
|
2356
2394
|
// Lazy: classify + build tier 0 plan on first call, not in constructor.
|
|
2357
|
-
const
|
|
2358
|
-
if (
|
|
2359
|
-
//
|
|
2360
|
-
|
|
2361
|
-
const _plan = buildTier0Plan(self._schemaObj);
|
|
2395
|
+
const _t0 = _tier0Verdict(self);
|
|
2396
|
+
if (_t0 !== null) {
|
|
2397
|
+
// In the browser build the verdict counts its calls (see _tier0Verdict);
|
|
2398
|
+
// elsewhere the second call builds the generated code behind it.
|
|
2362
2399
|
let _n = 0;
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
// this method stays on the plan walk, four times slower, for good.
|
|
2366
|
-
const _tick = _codegen === null && _tiering !== null && !self._interpretOnly && !env('ATA_FORCE_NAPI') ? _tiering.counter(self) : null;
|
|
2367
|
-
_bindVerdict(self, _tick !== null ? (d) => {
|
|
2368
|
-
_tick();
|
|
2369
|
-
return tier0Validate(_plan, d);
|
|
2370
|
-
} : (d) => {
|
|
2371
|
-
const r = tier0Validate(_plan, d);
|
|
2400
|
+
_bindVerdict(self, _ticks(self) ? _t0 : (d) => {
|
|
2401
|
+
const r = _t0(d);
|
|
2372
2402
|
if (++_n === 2) {
|
|
2373
2403
|
try { self._ensureCodegen(); } catch {}
|
|
2374
2404
|
}
|
|
@@ -2441,6 +2471,7 @@ Object.defineProperty(module.exports, '_registerCodegen', { value: _registerCode
|
|
|
2441
2471
|
Object.defineProperty(module.exports, '_setCodegenLoader', { value: (load) => { _loadCodegen = load; }, enumerable: false });
|
|
2442
2472
|
Object.defineProperty(module.exports, '_registerTiering', { value: _registerTiering, enumerable: false });
|
|
2443
2473
|
Object.defineProperty(module.exports, '_registerBuiltHook', { value: _registerBuiltHook, enumerable: false });
|
|
2474
|
+
Object.defineProperty(module.exports, '_markAotHost', { value: _markAotHost, enumerable: false });
|
|
2444
2475
|
Object.defineProperty(module.exports, '_codegenRegistered', { value: () => _codegen !== null, enumerable: false });
|
|
2445
2476
|
Object.defineProperty(module.exports, '_tiering', { get: () => _tiering, enumerable: false });
|
|
2446
2477
|
Object.defineProperty(module.exports, '_registerAot', { value: _registerAot, enumerable: false });
|
package/lib/verify.js
CHANGED
|
@@ -195,8 +195,72 @@ function mutate (doc, r) {
|
|
|
195
195
|
}
|
|
196
196
|
}
|
|
197
197
|
|
|
198
|
-
//
|
|
199
|
-
|
|
198
|
+
// The values on either side of each bound the schema states, for the value
|
|
199
|
+
// at one place: a module that wrote a bound off by one (`< 12` for a minimum
|
|
200
|
+
// of 13) answers every random document as the runtime does, and differs only
|
|
201
|
+
// here. A bound is where a hand edit, a stale module or a generator bug shows.
|
|
202
|
+
function boundaryValues (schema, type) {
|
|
203
|
+
const out = []
|
|
204
|
+
const int = type === 'integer'
|
|
205
|
+
const step = int ? 1 : 0.5
|
|
206
|
+
const num = (b) => typeof b === 'number' && Number.isFinite(b)
|
|
207
|
+
if (type === 'integer' || type === 'number') {
|
|
208
|
+
if (num(schema.minimum)) out.push(schema.minimum, schema.minimum - step)
|
|
209
|
+
if (num(schema.maximum)) out.push(schema.maximum, schema.maximum + step)
|
|
210
|
+
if (num(schema.exclusiveMinimum)) out.push(schema.exclusiveMinimum, schema.exclusiveMinimum + step)
|
|
211
|
+
if (num(schema.exclusiveMaximum)) out.push(schema.exclusiveMaximum, schema.exclusiveMaximum - step)
|
|
212
|
+
if (num(schema.multipleOf) && schema.multipleOf > 0) out.push(schema.multipleOf, schema.multipleOf * 1.5)
|
|
213
|
+
}
|
|
214
|
+
if (type === 'string') {
|
|
215
|
+
const str = (n) => 'abcdefghij'.repeat(Math.ceil(n / 10) + 1).slice(0, n)
|
|
216
|
+
if (num(schema.minLength)) { out.push(str(schema.minLength)); if (schema.minLength > 0) out.push(str(schema.minLength - 1)) }
|
|
217
|
+
if (num(schema.maxLength) && schema.maxLength < 4096) out.push(str(schema.maxLength), str(schema.maxLength + 1))
|
|
218
|
+
}
|
|
219
|
+
return out
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Documents that differ from a valid one at a single place, by a value on
|
|
223
|
+
// either side of a bound stated there: properties, nested objects, the first
|
|
224
|
+
// element of an array, through local references. At most `limit` of them.
|
|
225
|
+
function boundaryDocs (schema, r, defs, limit, accepts) {
|
|
226
|
+
// A base the runtime accepts, where the caller can say, so that a bound
|
|
227
|
+
// read wrong changes the verdict and not only the error list.
|
|
228
|
+
let base = makeValid(schema, r, defs, 0)
|
|
229
|
+
if (accepts) for (let i = 0; i < 50 && base !== undefined && !accepts(base); i++) base = makeValid(schema, r, defs, 0)
|
|
230
|
+
if (base === undefined) return []
|
|
231
|
+
const out = []
|
|
232
|
+
const visit = (sub, path, depth) => {
|
|
233
|
+
if (out.length >= limit || depth > 8 || !sub || typeof sub !== 'object') return
|
|
234
|
+
if (typeof sub.$ref === 'string') { const t = resolveLocal(sub.$ref, defs); if (t !== undefined) visit(t, path, depth + 1); return }
|
|
235
|
+
if (Array.isArray(sub.allOf)) { visit(mergeAllOf(sub), path, depth + 1); return }
|
|
236
|
+
const types = Array.isArray(sub.type) ? sub.type : sub.type !== undefined ? [sub.type] : []
|
|
237
|
+
for (const t of types.length ? types : ['integer', 'number', 'string']) {
|
|
238
|
+
for (const val of boundaryValues(sub, t)) {
|
|
239
|
+
if (out.length >= limit) return
|
|
240
|
+
const d = clone(base)
|
|
241
|
+
if (path.length === 0) { out.push(val); continue }
|
|
242
|
+
let o = d
|
|
243
|
+
for (let i = 0; i < path.length - 1; i++) {
|
|
244
|
+
if (o[path[i]] === undefined || o[path[i]] === null || typeof o[path[i]] !== 'object') o[path[i]] = typeof path[i + 1] === 'number' ? [] : {}
|
|
245
|
+
o = o[path[i]]
|
|
246
|
+
}
|
|
247
|
+
o[path[path.length - 1]] = val
|
|
248
|
+
out.push(d)
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
if (sub.properties && typeof sub.properties === 'object') {
|
|
252
|
+
for (const k of Object.keys(sub.properties)) if (k !== '__proto__') visit(sub.properties[k], [...path, k], depth + 1)
|
|
253
|
+
}
|
|
254
|
+
const item = Array.isArray(sub.prefixItems) ? sub.prefixItems[0] : sub.items && typeof sub.items === 'object' && !Array.isArray(sub.items) ? sub.items : null
|
|
255
|
+
if (item) visit(item, [...path, 0], depth + 1)
|
|
256
|
+
}
|
|
257
|
+
visit(schema, [], 0)
|
|
258
|
+
return out
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// Documents for `schema`: `count` made, half of them with one rule broken,
|
|
262
|
+
// then the documents on either side of each bound (up to `count` more).
|
|
263
|
+
function generate (schema, count, seed, accepts) {
|
|
200
264
|
const r = rng(seed)
|
|
201
265
|
const defs = { $defs: schema && schema.$defs, definitions: schema && schema.definitions }
|
|
202
266
|
const docs = []
|
|
@@ -205,6 +269,7 @@ function generate (schema, count, seed) {
|
|
|
205
269
|
if (i % 2 === 1 && d !== undefined) d = mutate(clone(d), r)
|
|
206
270
|
docs.push(d)
|
|
207
271
|
}
|
|
272
|
+
for (const d of boundaryDocs(schema, r, defs, count, accepts)) docs.push(d)
|
|
208
273
|
return docs
|
|
209
274
|
}
|
|
210
275
|
|
|
@@ -256,7 +321,7 @@ async function verifyOne ({ schemaFile, moduleFile, schema, generate: count = 20
|
|
|
256
321
|
if (mod.schemaHash !== undefined && mod.schemaHash !== hash) report.stale = true
|
|
257
322
|
let runtime
|
|
258
323
|
try { runtime = new Validator(schema, {}) } catch (e) { report.error = `schema does not compile: ${e.message}`; return report }
|
|
259
|
-
const docs = generate(schema, count, seed).map((d, i) => ({ source: `generated #${i + 1}`, doc: d }))
|
|
324
|
+
const docs = generate(schema, count, seed, (d) => { try { return runtime.isValidObject(clone(d)) } catch { return false } }).map((d, i) => ({ source: `generated #${i + 1}`, doc: d }))
|
|
260
325
|
report.generated = docs.length
|
|
261
326
|
const sampleDocs = readSamples(samples)
|
|
262
327
|
report.samples = sampleDocs.length
|
package/lib/version.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ata-validator",
|
|
3
|
-
"version": "1.61.
|
|
3
|
+
"version": "1.61.2",
|
|
4
4
|
"description": "JSON Schema validator that compiles to plain JavaScript, still runs where code generation is blocked, and can be compiled away at build time. Passes the full official test suite.",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"main": "index.js",
|
|
@@ -8,6 +8,11 @@
|
|
|
8
8
|
"types": "index.d.ts",
|
|
9
9
|
"exports": {
|
|
10
10
|
".": {
|
|
11
|
+
"aot": {
|
|
12
|
+
"types": "./index.d.ts",
|
|
13
|
+
"import": "./index.aot.mjs",
|
|
14
|
+
"require": "./index.aot.js"
|
|
15
|
+
},
|
|
11
16
|
"workerd": {
|
|
12
17
|
"types": "./index.d.ts",
|
|
13
18
|
"import": "./index.edge.mjs",
|
|
@@ -60,6 +65,25 @@
|
|
|
60
65
|
"import": "./compiled-verdict.mjs",
|
|
61
66
|
"require": "./compiled-verdict.js"
|
|
62
67
|
},
|
|
68
|
+
"./compiled-object": {
|
|
69
|
+
"types": "./compiled-object.d.ts",
|
|
70
|
+
"import": "./compiled-object.mjs",
|
|
71
|
+
"require": "./compiled-object.js"
|
|
72
|
+
},
|
|
73
|
+
"./compiled-plain": {
|
|
74
|
+
"types": "./compiled-plain.d.ts",
|
|
75
|
+
"import": "./compiled-plain.mjs",
|
|
76
|
+
"require": "./compiled-plain.js"
|
|
77
|
+
},
|
|
78
|
+
"./compiled-bare": {
|
|
79
|
+
"types": "./compiled-bare.d.ts",
|
|
80
|
+
"import": "./compiled-bare.mjs",
|
|
81
|
+
"require": "./compiled-bare.js"
|
|
82
|
+
},
|
|
83
|
+
"./compiled-bare-rich": {
|
|
84
|
+
"types": "./compiled-bare-rich.d.ts",
|
|
85
|
+
"default": "./lib/compiled-bare-rich.js"
|
|
86
|
+
},
|
|
63
87
|
"./lite": {
|
|
64
88
|
"types": "./lite.d.ts",
|
|
65
89
|
"import": "./lite.mjs",
|
|
@@ -79,7 +103,10 @@
|
|
|
79
103
|
},
|
|
80
104
|
"sideEffects": [
|
|
81
105
|
"./codegen.js",
|
|
82
|
-
"./codegen.mjs"
|
|
106
|
+
"./codegen.mjs",
|
|
107
|
+
"./lib/aot-mark.js",
|
|
108
|
+
"./index.aot.js",
|
|
109
|
+
"./index.aot.mjs"
|
|
83
110
|
],
|
|
84
111
|
"bin": {
|
|
85
112
|
"ata": "bin/ata.js"
|
|
@@ -89,7 +116,12 @@
|
|
|
89
116
|
"./lib/aot.js": "./lib/aot.browser.js",
|
|
90
117
|
"./lib/buffer-gate.js": "./lib/buffer-gate.browser.js",
|
|
91
118
|
"./lib/interpreter.js": "./lib/interpreter.browser.js",
|
|
92
|
-
"./lib/aot-order.js": false
|
|
119
|
+
"./lib/aot-order.js": false,
|
|
120
|
+
"./lib/compiled-defaults.js": "./lib/compiled-defaults.browser.js",
|
|
121
|
+
"./lib/native-message.js": "./lib/native.browser.js",
|
|
122
|
+
"./lib/native-codes.js": "./lib/native.browser.js",
|
|
123
|
+
"./lib/email-literal.js": false,
|
|
124
|
+
"./lib/native-positions.js": "./lib/native.browser.js"
|
|
93
125
|
},
|
|
94
126
|
"scripts": {
|
|
95
127
|
"prepublishOnly": "node tests/test_pack_purity.js && ATA_ALLOW_DOC_PLACEHOLDERS=1 node scripts/check-doc-coverage.js && node tests/test_error_codes_lock.js && node tests/test_safe_regex_embed.js && node tests/test_version_sync.js",
|
|
@@ -146,6 +178,8 @@
|
|
|
146
178
|
},
|
|
147
179
|
"files": [
|
|
148
180
|
"index.js",
|
|
181
|
+
"index.aot.js",
|
|
182
|
+
"index.aot.mjs",
|
|
149
183
|
"index.mjs",
|
|
150
184
|
"index.node.mjs",
|
|
151
185
|
"index.bundle.mjs",
|
|
@@ -173,6 +207,16 @@
|
|
|
173
207
|
"compiled-verdict.js",
|
|
174
208
|
"compiled-verdict.mjs",
|
|
175
209
|
"compiled-verdict.d.ts",
|
|
210
|
+
"compiled-object.js",
|
|
211
|
+
"compiled-object.mjs",
|
|
212
|
+
"compiled-object.d.ts",
|
|
213
|
+
"compiled-plain.js",
|
|
214
|
+
"compiled-plain.mjs",
|
|
215
|
+
"compiled-plain.d.ts",
|
|
216
|
+
"compiled-bare.js",
|
|
217
|
+
"compiled-bare.mjs",
|
|
218
|
+
"compiled-bare.d.ts",
|
|
219
|
+
"compiled-bare-rich.d.ts",
|
|
176
220
|
"lite.js",
|
|
177
221
|
"lite.mjs",
|
|
178
222
|
"lite.d.ts",
|
|
@@ -187,13 +231,13 @@
|
|
|
187
231
|
"codegen.d.ts"
|
|
188
232
|
],
|
|
189
233
|
"optionalDependencies": {
|
|
190
|
-
"@ata-validator/native-darwin-arm64": "1.61.
|
|
191
|
-
"@ata-validator/native-darwin-x64": "1.61.
|
|
192
|
-
"@ata-validator/native-linux-arm64-gnu": "1.61.
|
|
193
|
-
"@ata-validator/native-linux-arm64-musl": "1.61.
|
|
194
|
-
"@ata-validator/native-linux-x64-gnu": "1.61.
|
|
195
|
-
"@ata-validator/native-linux-x64-musl": "1.61.
|
|
196
|
-
"@ata-validator/native-win32-x64": "1.61.
|
|
234
|
+
"@ata-validator/native-darwin-arm64": "1.61.2",
|
|
235
|
+
"@ata-validator/native-darwin-x64": "1.61.2",
|
|
236
|
+
"@ata-validator/native-linux-arm64-gnu": "1.61.2",
|
|
237
|
+
"@ata-validator/native-linux-arm64-musl": "1.61.2",
|
|
238
|
+
"@ata-validator/native-linux-x64-gnu": "1.61.2",
|
|
239
|
+
"@ata-validator/native-linux-x64-musl": "1.61.2",
|
|
240
|
+
"@ata-validator/native-win32-x64": "1.61.2"
|
|
197
241
|
},
|
|
198
242
|
"peerDependencies": {
|
|
199
243
|
"yaml": "^2.0.0"
|