ata-validator 1.61.0 → 1.61.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +28 -3
- 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/lib/aot-build.js +9 -1
- package/lib/aot-impl.js +10 -2
- 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/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/standard-schema.js +1 -1
- package/lib/validator-core.js +10 -1
- package/lib/verify.js +68 -3
- package/lib/version.js +1 -1
- package/package.json +43 -9
package/lib/error-codes.js
CHANGED
|
@@ -26,16 +26,10 @@ const CODES = Object.freeze({
|
|
|
26
26
|
ATA2013: { keyword: 'pattern', category: 'constraint', headline: 'string does not match pattern' },
|
|
27
27
|
|
|
28
28
|
// ATA3xxx — format
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
ATA3005: { keyword: 'format', format: 'uri', category: 'format', headline: 'value does not match format "uri"' },
|
|
34
|
-
ATA3006: { keyword: 'format', format: 'uri-reference', category: 'format', headline: 'value does not match format "uri-reference"' },
|
|
35
|
-
ATA3007: { keyword: 'format', format: 'ipv4', category: 'format', headline: 'value does not match format "ipv4"' },
|
|
36
|
-
ATA3008: { keyword: 'format', format: 'ipv6', category: 'format', headline: 'value does not match format "ipv6"' },
|
|
37
|
-
ATA3009: { keyword: 'format', format: 'uuid', category: 'format', headline: 'value does not match format "uuid"' },
|
|
38
|
-
ATA3010: { keyword: 'format', format: 'hostname', category: 'format', headline: 'value does not match format "hostname"' },
|
|
29
|
+
// One entry per built-in format the engines name, in this order: the same
|
|
30
|
+
// object for each, so it is written once.
|
|
31
|
+
...Object.fromEntries(['email', 'date', 'date-time', 'time', 'uri', 'uri-reference', 'ipv4', 'ipv6', 'uuid', 'hostname']
|
|
32
|
+
.map((format, i) => [`ATA30${String(i + 1).padStart(2, '0')}`, { keyword: 'format', format, category: 'format', headline: `value does not match format "${format}"` }])),
|
|
39
33
|
ATA3099: { keyword: 'format', category: 'format', headline: 'value does not match user-defined format' },
|
|
40
34
|
|
|
41
35
|
// ATA4xxx — composition
|
|
@@ -69,14 +63,6 @@ const CODES = Object.freeze({
|
|
|
69
63
|
ATA9002: { keyword: '__compile__', category: 'system', headline: 'schema failed to compile' },
|
|
70
64
|
});
|
|
71
65
|
|
|
72
|
-
function get (code) {
|
|
73
|
-
return CODES[code];
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
function all () {
|
|
77
|
-
return Object.keys(CODES).sort();
|
|
78
|
-
}
|
|
79
|
-
|
|
80
66
|
// Reverse lookups, built once. codeFor runs per error on every failing
|
|
81
67
|
// validation, so walking the table there showed up as most of the cost of
|
|
82
68
|
// enriching a rejected payload. Insertion follows sorted code order, so the
|
|
@@ -101,62 +87,6 @@ function codeFor (keyword, format) {
|
|
|
101
87
|
}
|
|
102
88
|
|
|
103
89
|
|
|
104
|
-
// The native engine
|
|
105
|
-
// ordinal. Nothing outside the addon should ever see one: an error that came
|
|
106
|
-
// from there carries the same public code and keyword as the same failure from
|
|
107
|
-
// the JavaScript engines. Ordinals are positional, so this table follows the
|
|
108
|
-
// enum's declaration order and `tests/test_native_error_codes.js` fails if the
|
|
109
|
-
// two drift apart.
|
|
110
|
-
const NATIVE_KEYWORDS = [
|
|
111
|
-
null, // 0 ok
|
|
112
|
-
'__parse__', // 1 invalid_json
|
|
113
|
-
'__compile__', // 2 invalid_schema
|
|
114
|
-
'type', // 3 type_mismatch
|
|
115
|
-
'required', // 4 required_property_missing
|
|
116
|
-
'additionalProperties', // 5 additional_property_not_allowed
|
|
117
|
-
'enum', // 6 enum_mismatch
|
|
118
|
-
'const', // 7 const_mismatch
|
|
119
|
-
'minimum', // 8 minimum_violation
|
|
120
|
-
'maximum', // 9 maximum_violation
|
|
121
|
-
'exclusiveMinimum', // 10 exclusive_minimum_violation
|
|
122
|
-
'exclusiveMaximum', // 11 exclusive_maximum_violation
|
|
123
|
-
'minLength', // 12 min_length_violation
|
|
124
|
-
'maxLength', // 13 max_length_violation
|
|
125
|
-
'pattern', // 14 pattern_mismatch
|
|
126
|
-
'format', // 15 format_mismatch
|
|
127
|
-
'minItems', // 16 min_items_violation
|
|
128
|
-
'maxItems', // 17 max_items_violation
|
|
129
|
-
'uniqueItems', // 18 unique_items_violation
|
|
130
|
-
'minProperties', // 19 min_properties_violation
|
|
131
|
-
'maxProperties', // 20 max_properties_violation
|
|
132
|
-
'multipleOf', // 21 multiple_of_violation
|
|
133
|
-
'allOf', // 22 all_of_failed
|
|
134
|
-
'anyOf', // 23 any_of_failed
|
|
135
|
-
'oneOf', // 24 one_of_failed
|
|
136
|
-
'not', // 25 not_failed
|
|
137
|
-
'$ref', // 26 ref_not_found
|
|
138
|
-
'if', // 27 if_then_else_failed
|
|
139
|
-
'__depth__', // 28 depth_exceeded
|
|
140
|
-
]
|
|
141
|
-
|
|
142
|
-
// Keyword and public code for a native ordinal, or null when the ordinal is
|
|
143
|
-
// outside the enum, which would mean the addon is newer than this table.
|
|
144
|
-
function fromNative (ordinal, format) {
|
|
145
|
-
if (typeof ordinal !== 'number' || !Number.isInteger(ordinal)) return null
|
|
146
|
-
const keyword = NATIVE_KEYWORDS[ordinal]
|
|
147
|
-
if (!keyword) return null
|
|
148
|
-
// codeFor('format') answers with the first format code, which names a
|
|
149
|
-
// specific format. Without knowing which format failed, the generic one is
|
|
150
|
-
// the honest answer.
|
|
151
|
-
// The walker stopped at its depth limit and rejected the document. No
|
|
152
|
-
// public code names that: the APIs that ask the walker route schemas deep or
|
|
153
|
-
// recursive enough to reach it to validate() first (lib/buffer-gate.js), so
|
|
154
|
-
// it is the generic failure.
|
|
155
|
-
if (keyword === '__depth__') return { keyword, code: 'ATA9000' }
|
|
156
|
-
const code = keyword === 'format'
|
|
157
|
-
? (format ? codeFor('format', format) || 'ATA3099' : 'ATA3099')
|
|
158
|
-
: codeFor(keyword) || 'ATA9001'
|
|
159
|
-
return { keyword, code }
|
|
160
|
-
}
|
|
90
|
+
// The native engine's ordinals are translated in lib/native-codes.js.
|
|
161
91
|
|
|
162
|
-
module.exports = { CODES,
|
|
92
|
+
module.exports = { CODES, codeFor };
|
package/lib/formats-source.js
CHANGED
|
@@ -263,6 +263,21 @@ const EMAIL_LOCAL_SRC = (v) => `(${v}>32&&${v}<127&&${v}!==34&&${v}!==40&&${v}!=
|
|
|
263
263
|
// functions by their source text, so anything reaching a free variable would
|
|
264
264
|
// not survive the trip. The domain half reuses hostnameSource, which keeps
|
|
265
265
|
// one hostname implementation for both halves of this file.
|
|
266
|
+
// The check of an email address literal's inside (`_in`, between the
|
|
267
|
+
// brackets). Where code is written for a module, by expression
|
|
268
|
+
// (lib/email-literal.js), which is a fraction of the bytes of the parsers. A
|
|
269
|
+
// browser build maps that module to nothing: its generator writes code for the
|
|
270
|
+
// page alone, so it uses the parsers it carries anyway and the expression's
|
|
271
|
+
// builder stays out of the page. tests/test_ipv6_expression.js holds both forms
|
|
272
|
+
// to the same answers.
|
|
273
|
+
const _literalByParsers = () =>
|
|
274
|
+
`if((_in.charCodeAt(0)|32)===105&&(_in.charCodeAt(1)|32)===112&&(_in.charCodeAt(2)|32)===118&&_in.charCodeAt(3)===54&&_in.charCodeAt(4)===58){const _i6=_in.slice(5);${ipv6Source('_i6', true)}}` +
|
|
275
|
+
`else{${ipv4Source('_in', true)}}`;
|
|
276
|
+
const _emailLiteral = () => {
|
|
277
|
+
const byExpression = require('./email-literal');
|
|
278
|
+
return typeof byExpression === 'function' ? byExpression() : _literalByParsers();
|
|
279
|
+
};
|
|
280
|
+
|
|
266
281
|
function emailSource (v, isStr) {
|
|
267
282
|
const inner =
|
|
268
283
|
// One forward pass, and neither half sliced out. '@' is not a local-part
|
|
@@ -294,8 +309,10 @@ function emailSource (v, isStr) {
|
|
|
294
309
|
`const _dm=${v}.slice(_at+1);` +
|
|
295
310
|
`if(_dm.charCodeAt(_dm.length-1)!==93||_dm.length<=2)return false;` +
|
|
296
311
|
`const _in=_dm.slice(1,-1);` +
|
|
297
|
-
|
|
298
|
-
|
|
312
|
+
// The literal is rare, and checked by expression rather than by the
|
|
313
|
+
// IPv6 and IPv4 parsers, which made it 72% of this check's source:
|
|
314
|
+
// the same answers (tests/test_ipv6_expression.js), a fraction of the bytes.
|
|
315
|
+
_emailLiteral() +
|
|
299
316
|
`}` +
|
|
300
317
|
`else{${hostnameRangeSource(v, '_at+1', `${v}.length`)}}`;
|
|
301
318
|
return isStr ? `{${inner}}` : `if(typeof ${v}==='string'){${inner}}`;
|
|
@@ -304,4 +321,4 @@ function durationSource (v, isStr) {
|
|
|
304
321
|
const inner = `if(!${_formats().DURATION_RE.toString()}.test(${v}))return false`;
|
|
305
322
|
return isStr ? inner : `if(typeof ${v}==='string'&&${inner.slice(3)}`;
|
|
306
323
|
}
|
|
307
|
-
module.exports = { uriHelperSource, uriHelperCompactSource, uriSource, uriReferenceSource, iriSource, iriReferenceSource, idnEmailSource, dateSource, ipv4Source, dateTimeSource, ipv6Source, hostnameSource, uuidSource, timeSource, noReservedSource, jsonPointerSource, relativeJsonPointerSource, uriTemplateSource, emailSource, durationSource };
|
|
324
|
+
module.exports = { _literalByParsers, uriHelperSource, uriHelperCompactSource, uriSource, uriReferenceSource, iriSource, iriReferenceSource, idnEmailSource, dateSource, ipv4Source, dateTimeSource, ipv6Source, hostnameSource, uuidSource, timeSource, noReservedSource, jsonPointerSource, relativeJsonPointerSource, uriTemplateSource, emailSource, durationSource };
|
|
@@ -2028,6 +2028,10 @@ const NOT_VALIDATING = new Set(['$schema', '$id', '$anchor', '$comment', '$defs'
|
|
|
2028
2028
|
function typeMismatchRest(schema, types) {
|
|
2029
2029
|
const drop = new Set(['type', 'enum', 'const', 'anyOf', 'oneOf'])
|
|
2030
2030
|
for (const t of types) for (const k of (TYPE_KEYWORDS[t] || [])) drop.add(k)
|
|
2031
|
+
// A built-in format is written for strings only (`typeof v==='string'&&`
|
|
2032
|
+
// off the string path), so for a value that failed `type: string` it can
|
|
2033
|
+
// never fire: carried here it was dead code in every module that checks one.
|
|
2034
|
+
if (types.includes('string') && typeof schema.format === 'string' && FORMAT_CODEGEN[schema.format]) drop.add('format')
|
|
2031
2035
|
// A non-integer number reaches the type block already (numberToo).
|
|
2032
2036
|
let rest = null
|
|
2033
2037
|
for (const k of Object.keys(schema)) {
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// The rejection validateJSON() returns, and the position map behind an error's
|
|
4
|
+
// frame in the JSON text. Apart from lib/rejections.js so that code with no
|
|
5
|
+
// JSON entry point (validate() through a compiled wrapper) does not bundle it.
|
|
6
|
+
|
|
7
|
+
const { needsOrdering, sortErrorsBySchemaOrder, attachRelated, _setPositionsFor } = require('./rejections');
|
|
8
|
+
const { setDiagnosticSource: attachDiagnosticSource } = require('./diagnostic-source');
|
|
9
|
+
|
|
10
|
+
// The rejection validateJSON returns. Everything the text path adds over
|
|
11
|
+
// validate(data), the value tree enrichment needs, the position map, the
|
|
12
|
+
// diagnostic payload, happens on first access to `.errors`. Reading `.valid`
|
|
13
|
+
// touches none of it. The JSON text is held here rather than read back off the
|
|
14
|
+
// validator, so the result does not depend on what the instance does next.
|
|
15
|
+
class LazyJsonRejection {
|
|
16
|
+
// `parsed`, when given, is the document the inner path parsed from
|
|
17
|
+
// `jsonStr` and validated, after defaults, coercion and removal: reading the
|
|
18
|
+
// errors uses it instead of parsing the text again, which on a 5 MB
|
|
19
|
+
// document was a third of what reading them cost.
|
|
20
|
+
constructor(result, jsonStr, self, enrich, parsed) {
|
|
21
|
+
this.valid = false;
|
|
22
|
+
this._result = result;
|
|
23
|
+
this._jsonStr = jsonStr;
|
|
24
|
+
this._self = self;
|
|
25
|
+
this._enrich = enrich;
|
|
26
|
+
this._cached = null;
|
|
27
|
+
this._parsed = parsed;
|
|
28
|
+
}
|
|
29
|
+
toJSON() {
|
|
30
|
+
return { valid: false, errors: this.errors };
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
Object.defineProperty(LazyJsonRejection.prototype, 'errors', {
|
|
34
|
+
enumerable: true,
|
|
35
|
+
configurable: true,
|
|
36
|
+
get() {
|
|
37
|
+
if (this._cached !== null) return this._cached;
|
|
38
|
+
const self = this._self;
|
|
39
|
+
const enrich = this._enrich;
|
|
40
|
+
const jsonStr = this._jsonStr;
|
|
41
|
+
// Reading the inner errors realizes the inner lazy layer, if there was one.
|
|
42
|
+
const raw = this._result.errors || [];
|
|
43
|
+
if (!raw.length) { this._cached = raw; return raw; }
|
|
44
|
+
|
|
45
|
+
// The enrich pass plucks `received` from the value tree and the suggestion
|
|
46
|
+
// engine (required-typo, format hints, coercion nudges) walks it too.
|
|
47
|
+
let parsedData = this._parsed;
|
|
48
|
+
if (parsedData === undefined) {
|
|
49
|
+
try { parsedData = JSON.parse(jsonStr); } catch { parsedData = undefined; }
|
|
50
|
+
// What was checked is the document after defaults, coercion and removal,
|
|
51
|
+
// so `received` has to come from that, as it does for validate(). A
|
|
52
|
+
// default that failed its own schema has no value in the text at all.
|
|
53
|
+
if (parsedData !== undefined && self._preprocess) self._preprocess(parsedData);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Errors the inner path already enriched carry a `received` key: enrich()
|
|
57
|
+
// always sets one, even when there is no value to show. `code` is not a
|
|
58
|
+
// safe signal, because branch-collapse attaches codes to raw errors, and
|
|
59
|
+
// `docUrl` is not either, because the generated error functions stamp it
|
|
60
|
+
// on raw errors; taking it as the signal left those errors without
|
|
61
|
+
// `received` or a suggestion on the text path while validate() had both.
|
|
62
|
+
if (!raw[0] || !('received' in raw[0])) {
|
|
63
|
+
// Declaration order, as validate() applies it; the text path used to
|
|
64
|
+
// enrich in emission order.
|
|
65
|
+
const ordered = needsOrdering(raw) ? sortErrorsBySchemaOrder(self._schemaObj, raw) : raw;
|
|
66
|
+
// The position map waits for the first read of a frame (lib/enrich-error
|
|
67
|
+
// defineLazyFrames); until then reading the list costs the enrichment alone.
|
|
68
|
+
const lazyPositions = lazyPositionsFor(self, jsonStr, ordered);
|
|
69
|
+
const enrichOpts = {
|
|
70
|
+
data: parsedData,
|
|
71
|
+
positions: null,
|
|
72
|
+
lazyPositions,
|
|
73
|
+
schemaPositions: self._schemaPositions,
|
|
74
|
+
schemaFile: self._source ? self._source.path : undefined,
|
|
75
|
+
};
|
|
76
|
+
const enriched = ordered.map((e) => enrich(e, enrichOpts));
|
|
77
|
+
if (enriched.length > 1) attachRelated(enriched);
|
|
78
|
+
attachDiagnosticSource(enriched, {
|
|
79
|
+
data: parsedData,
|
|
80
|
+
text: jsonStr,
|
|
81
|
+
// Read by the diagnostic renderer only, which resolves the same map.
|
|
82
|
+
get positions () { return lazyPositions(); },
|
|
83
|
+
schema: self._schemaObj,
|
|
84
|
+
mutatesInput: self._mutatesInput === true,
|
|
85
|
+
});
|
|
86
|
+
this._cached = enriched;
|
|
87
|
+
return enriched;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Already enriched, so the frames are usually attached too. Only a gap in
|
|
91
|
+
// them is worth another walk of the document: resolving the map to discover
|
|
92
|
+
// there was nothing to fill cost a second full walk on every rejection.
|
|
93
|
+
if (raw.some((e) => e && !e.dataFrame)) {
|
|
94
|
+
const positions = self._pos().targeted(jsonStr, wantedPointersFor(raw.filter((e) => e && !e.dataFrame)));
|
|
95
|
+
if (positions) {
|
|
96
|
+
for (const e of raw) {
|
|
97
|
+
if (e && !e.dataFrame) {
|
|
98
|
+
const path = e.path != null ? e.path : (e.instancePath || '');
|
|
99
|
+
const p = positions[path];
|
|
100
|
+
if (p) e.dataFrame = { byteOffset: p.byteOffset, length: p.length, line: p.line, col: p.col, text: p.text };
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
self._posCache.reset();
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
if (raw.length > 1) attachRelated(raw);
|
|
107
|
+
attachDiagnosticSource(raw, {
|
|
108
|
+
data: parsedData,
|
|
109
|
+
text: jsonStr,
|
|
110
|
+
schema: self._schemaObj,
|
|
111
|
+
mutatesInput: self._mutatesInput === true,
|
|
112
|
+
});
|
|
113
|
+
this._cached = raw;
|
|
114
|
+
return raw;
|
|
115
|
+
},
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
// The targeted position map for a list, built on the first call and shared
|
|
119
|
+
// by every error's frame accessor (lib/enrich-error defineLazyFrames).
|
|
120
|
+
function lazyPositionsFor (self, text, errors) {
|
|
121
|
+
let done = false;
|
|
122
|
+
let positions = null;
|
|
123
|
+
return () => {
|
|
124
|
+
if (done) return positions;
|
|
125
|
+
done = true;
|
|
126
|
+
positions = self._pos().targeted(text, wantedPointersFor(errors));
|
|
127
|
+
if (positions) self._posCache.reset();
|
|
128
|
+
return positions;
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// The pointers a set of errors will ask the position map about. lib/enrich-error
|
|
133
|
+
// looks up the error's own path, and for an additional or unevaluated property
|
|
134
|
+
// the child pointer named in `params`, unescaped, which is the form it asks for.
|
|
135
|
+
// The escaped form goes in too: including a pointer that is never read costs
|
|
136
|
+
// nothing, and the cache answers anything outside this set from the full map
|
|
137
|
+
// rather than reporting no position.
|
|
138
|
+
function wantedPointersFor (errors) {
|
|
139
|
+
const wanted = new Set();
|
|
140
|
+
for (const e of errors) {
|
|
141
|
+
if (!e) continue;
|
|
142
|
+
const path = e.path != null ? e.path : (e.instancePath || '');
|
|
143
|
+
wanted.add(path);
|
|
144
|
+
if (e.instancePath != null && e.instancePath !== path) wanted.add(e.instancePath);
|
|
145
|
+
const params = e.params;
|
|
146
|
+
const named = params && (params.additionalProperty || params.unevaluatedProperty);
|
|
147
|
+
if (typeof named === 'string') {
|
|
148
|
+
wanted.add(path + '/' + named);
|
|
149
|
+
if (named.indexOf('~') !== -1 || named.indexOf('/') !== -1) {
|
|
150
|
+
wanted.add(path + '/' + named.replace(/~/g, '~0').replace(/\//g, '~1'));
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return wanted;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
_setPositionsFor(lazyPositionsFor);
|
|
158
|
+
|
|
159
|
+
module.exports = { LazyJsonRejection };
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// A module of its own so the browser build, which has no native engine
|
|
4
|
+
// (lib/native-load.browser.js), maps it to lib/native-codes.browser.js and
|
|
5
|
+
// carries no table for ordinals that never reach it.
|
|
6
|
+
|
|
7
|
+
const { codeFor } = require('./error-codes');
|
|
8
|
+
|
|
9
|
+
// The native engine reports its own `error_code` enum from include/ata.h as an
|
|
10
|
+
// ordinal. Nothing outside the addon should ever see one: an error that came
|
|
11
|
+
// from there carries the same public code and keyword as the same failure from
|
|
12
|
+
// the JavaScript engines. Ordinals are positional, so this table follows the
|
|
13
|
+
// enum's declaration order and `tests/test_native_error_codes.js` fails if the
|
|
14
|
+
// two drift apart.
|
|
15
|
+
const NATIVE_KEYWORDS = [
|
|
16
|
+
null, // 0 ok
|
|
17
|
+
'__parse__', // 1 invalid_json
|
|
18
|
+
'__compile__', // 2 invalid_schema
|
|
19
|
+
'type', // 3 type_mismatch
|
|
20
|
+
'required', // 4 required_property_missing
|
|
21
|
+
'additionalProperties', // 5 additional_property_not_allowed
|
|
22
|
+
'enum', // 6 enum_mismatch
|
|
23
|
+
'const', // 7 const_mismatch
|
|
24
|
+
'minimum', // 8 minimum_violation
|
|
25
|
+
'maximum', // 9 maximum_violation
|
|
26
|
+
'exclusiveMinimum', // 10 exclusive_minimum_violation
|
|
27
|
+
'exclusiveMaximum', // 11 exclusive_maximum_violation
|
|
28
|
+
'minLength', // 12 min_length_violation
|
|
29
|
+
'maxLength', // 13 max_length_violation
|
|
30
|
+
'pattern', // 14 pattern_mismatch
|
|
31
|
+
'format', // 15 format_mismatch
|
|
32
|
+
'minItems', // 16 min_items_violation
|
|
33
|
+
'maxItems', // 17 max_items_violation
|
|
34
|
+
'uniqueItems', // 18 unique_items_violation
|
|
35
|
+
'minProperties', // 19 min_properties_violation
|
|
36
|
+
'maxProperties', // 20 max_properties_violation
|
|
37
|
+
'multipleOf', // 21 multiple_of_violation
|
|
38
|
+
'allOf', // 22 all_of_failed
|
|
39
|
+
'anyOf', // 23 any_of_failed
|
|
40
|
+
'oneOf', // 24 one_of_failed
|
|
41
|
+
'not', // 25 not_failed
|
|
42
|
+
'$ref', // 26 ref_not_found
|
|
43
|
+
'if', // 27 if_then_else_failed
|
|
44
|
+
'__depth__', // 28 depth_exceeded
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
// Keyword and public code for a native ordinal, or null when the ordinal is
|
|
48
|
+
// outside the enum, which would mean the addon is newer than this table.
|
|
49
|
+
function fromNative (ordinal, format) {
|
|
50
|
+
if (typeof ordinal !== 'number' || !Number.isInteger(ordinal)) return null
|
|
51
|
+
const keyword = NATIVE_KEYWORDS[ordinal]
|
|
52
|
+
if (!keyword) return null
|
|
53
|
+
// codeFor('format') answers with the first format code, which names a
|
|
54
|
+
// specific format. Without knowing which format failed, the generic one is
|
|
55
|
+
// the honest answer.
|
|
56
|
+
// The walker stopped at its depth limit and rejected the document. No
|
|
57
|
+
// public code names that: the APIs that ask the walker route schemas deep or
|
|
58
|
+
// recursive enough to reach it to validate() first (lib/buffer-gate.js), so
|
|
59
|
+
// it is the generic failure.
|
|
60
|
+
if (keyword === '__depth__') return { keyword, code: 'ATA9000' }
|
|
61
|
+
const code = keyword === 'format'
|
|
62
|
+
? (format ? codeFor('format', format) || 'ATA3099' : 'ATA3099')
|
|
63
|
+
: codeFor(keyword) || 'ATA9001'
|
|
64
|
+
return { keyword, code }
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
module.exports = { fromNative, NATIVE_KEYWORDS };
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// The words for an error the native engine reported without a message (it
|
|
4
|
+
// reports numeric codes): the enrich pass knows them. A module of its own so
|
|
5
|
+
// the browser build, which has no native engine (lib/native-load.browser.js),
|
|
6
|
+
// maps it to lib/native-message.browser.js and leaves the enrichment out of
|
|
7
|
+
// bundles that only reach it from here, such as ata-validator/compiled-plain.
|
|
8
|
+
module.exports = (err) => require('./enrich-error').enrich(err, {}).message;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Browser build of lib/native-message.js, lib/native-codes.js and
|
|
4
|
+
// lib/native-positions.js, one file for all three so a page carries one module
|
|
5
|
+
// for them: there is no native engine in a browser (lib/native-load.browser.js
|
|
6
|
+
// returns null), so no error arrives without a message or with a native
|
|
7
|
+
// ordinal, and no position is found by the addon. Kept total all the same.
|
|
8
|
+
const message = (err) => (err && err.message) || 'validation failed';
|
|
9
|
+
message.fromNative = message.nativeTargeted = () => null;
|
|
10
|
+
message.NATIVE_KEYWORDS = [];
|
|
11
|
+
module.exports = message;
|
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/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/validator-core.js
CHANGED
|
@@ -353,7 +353,14 @@ function resolveSchemaByPath(rootSchema, schemaPath) {
|
|
|
353
353
|
// by every page that loads the package, validating or not.
|
|
354
354
|
let LazyRejection, RichRejection, LazyJsonRejection, _enrichLazy, presentErrors, needsOrdering, sortErrorsBySchemaOrder, stripOrdinal;
|
|
355
355
|
function _loadRejections() {
|
|
356
|
-
({ LazyRejection, RichRejection,
|
|
356
|
+
({ LazyRejection, RichRejection, _enrichLazy, presentErrors, needsOrdering, sortErrorsBySchemaOrder, stripOrdinal } = require('./rejections'));
|
|
357
|
+
}
|
|
358
|
+
// The rejection validateJSON returns, and the position map an error's frame
|
|
359
|
+
// reads, loaded with the first rejection that carries JSON text: a process
|
|
360
|
+
// that only ever calls validate() never parses them (lib/json-rejections.js,
|
|
361
|
+
// lib/diagnostic-source.js).
|
|
362
|
+
function _loadJsonRejections() {
|
|
363
|
+
if (LazyJsonRejection === undefined) ({ LazyJsonRejection } = require('./json-rejections'));
|
|
357
364
|
}
|
|
358
365
|
|
|
359
366
|
// Text that does not parse, reported the same way on every path and platform:
|
|
@@ -1284,6 +1291,7 @@ class Validator {
|
|
|
1284
1291
|
// literal with a getter here cost a closure plus an accessor
|
|
1285
1292
|
// definition on every rejection, several hundred nanoseconds
|
|
1286
1293
|
// before any error was read.
|
|
1294
|
+
if (rawInput != null) _loadJsonRejections();
|
|
1287
1295
|
return new RichRejection(result, data, rawInput, self, root, enrich);
|
|
1288
1296
|
}
|
|
1289
1297
|
return result;
|
|
@@ -1320,6 +1328,7 @@ class Validator {
|
|
|
1320
1328
|
// and on the codegen path that realizes the inner lazy layer, which is
|
|
1321
1329
|
// the document walk this exists to avoid.
|
|
1322
1330
|
if (result && result.valid === false) {
|
|
1331
|
+
_loadJsonRejections();
|
|
1323
1332
|
return new LazyJsonRejection(result, jsonStr, this, enrich, parsed);
|
|
1324
1333
|
}
|
|
1325
1334
|
return result;
|