ata-validator 1.40.1 → 1.42.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -5,6 +5,8 @@ JSON Schema validation that compiles for speed and still runs where code generat
5
5
  [![npm](https://img.shields.io/npm/v/ata-validator)](https://www.npmjs.com/package/ata-validator)
6
6
  [![License](https://img.shields.io/npm/l/ata-validator)](LICENSE)
7
7
  [![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/ata-core/ata-validator/badge)](https://scorecard.dev/viewer/?uri=github.com/ata-core/ata-validator)
8
+ [![OpenSSF Best Practices](https://www.bestpractices.dev/projects/15196/badge)](https://www.bestpractices.dev/projects/15196)
9
+ [![Socket Badge](https://badge.socket.dev/npm/package/ata-validator)](https://socket.dev/npm/package/ata-validator)
8
10
 
9
11
  1.0 is a stability commitment: see [docs/STABILITY.md](docs/STABILITY.md) for the semver, deprecation, and error-code guarantees.
10
12
 
package/index.js CHANGED
@@ -35,6 +35,65 @@ class TextRejection {
35
35
  }
36
36
  }
37
37
 
38
+ // Errors built enriched where they fail by the rich combined function (see
39
+ // makeRich in lib/enrich-site.js), for a validator whose errors are being read
40
+ // with richErrors on. Presenting them is the ordering and the `related` links
41
+ // presentErrors applies after enriching. Where the rich function does not
42
+ // reject (the data changed under the read), the plain fallback is presented
43
+ // the usual way.
44
+ const { needsOrdering: _needsOrdering, sortErrorsBySchemaOrder: _sortErrors, attachRelated: _attachRelated } = require('./lib/rejections');
45
+ let _enrichFnIdx = null;
46
+ function _enrichOne(e, data) {
47
+ if (_enrichFnIdx === null) _enrichFnIdx = require('./lib/enrich-error').enrich;
48
+ return _enrichFnIdx(e, data !== undefined ? { data } : null);
49
+ }
50
+ function richBuilder(combRich, root, presentFallback, fallback, plain, cleanPlain) {
51
+ if (plain) {
52
+ // The runtime-shape combined function writes each error fresh and in the
53
+ // plain shape: no shared literal, no ordinal, no code or docUrl, no
54
+ // collapsed branches (lib/js-compiler.js, compileToJSCombined with
55
+ // runtimeShape). So without enrichment the list is the caller's as it is,
56
+ // once in schema order. Checking each error for those fields instead cost
57
+ // a megamorphic load per field per error across many schemas.
58
+ // tests/test_error_read_paths.js holds every error handed out to being
59
+ // the caller's own.
60
+ return (data) => {
61
+ const r = combRich(data);
62
+ if (r.valid || !r.errors || !r.errors.length) return presentFallback(fallback, data);
63
+ if (cleanPlain) return _needsOrdering(r.errors) ? _sortErrors(root, r.errors) : r.errors;
64
+ return presentFallback(r.errors, data);
65
+ };
66
+ }
67
+ return (data) => {
68
+ const r = combRich(data);
69
+ if (r.valid || !r.errors || !r.errors.length) return presentFallback(fallback, data);
70
+ const out = _needsOrdering(r.errors) ? _sortErrors(root, r.errors) : r.errors;
71
+ // A collapsed oneOf or anyOf comes out plain (no docUrl) and is enriched
72
+ // here, its branch errors with it, as presentErrors would.
73
+ for (let i = 0; i < out.length; i++) {
74
+ if (out[i].docUrl === undefined) out[i] = _enrichOne(out[i], data);
75
+ }
76
+ if (out.length > 1) _attachRelated(out);
77
+ return out;
78
+ };
79
+ }
80
+
81
+ // The rejection validate() returns once it is the one-pass combined function
82
+ // (resultShape in compileToJSCombined): the errors are already built, plain,
83
+ // fresh and in schema order. Serialises as the lazy rejection does.
84
+ class EagerRejection {
85
+ constructor(errors) {
86
+ this.valid = false;
87
+ this.errors = errors;
88
+ }
89
+ toJSON() {
90
+ return { valid: false, errors: this.errors };
91
+ }
92
+ _ataRaw() {
93
+ return this.errors;
94
+ }
95
+ }
96
+
38
97
  function installCodegenPaths (ctx) {
39
98
  const { ABORT_EARLY_RESULT, HYBRID_TIER_CALLS, SIMDJSON_THRESHOLD, VALID_RESULT, _bindVerdict, _jsonSyntaxRejection, _mustReject, _verboseWrap, getNative, isV1Dialect, resolveSchemaByPath } = core._internals;
40
99
  const { jsFn, _isCodegen, preprocess, fusedRemove, options, schemaObj, useSimdjsonForLarge, _buildCombined, _buildErr } = ctx;
@@ -131,6 +190,36 @@ function installCodegenPaths (ctx) {
131
190
  return _safeCombined;
132
191
  };
133
192
 
193
+ // The error function on its own, probed the way errOnly probes it, or null.
194
+ const errFnIfSafe = () => {
195
+ _buildErr();
196
+ const efn = ctx.err();
197
+ if (!efn) return null;
198
+ try { efn({}, true); } catch { return null; }
199
+ return (x) => efn(x, true);
200
+ };
201
+
202
+ // The rich combined function, built and probed the way the plain one is,
203
+ // and only where the plain one passed its probe.
204
+ let _richProbed = false;
205
+ let _safeRich = null;
206
+ const richIfSafe = () => {
207
+ if (_richProbed) return _safeRich;
208
+ _richProbed = true;
209
+ if (!combinedIfSafe() || !ctx.buildRich) return null;
210
+ const fn = ctx.buildRich();
211
+ if (fn) {
212
+ try {
213
+ const probe = {};
214
+ if (schemaObj && schemaObj.properties) for (const k of Object.keys(schemaObj.properties)) probe[k] = "";
215
+ if (schemaObj && schemaObj.if && schemaObj.if.properties) for (const k of Object.keys(schemaObj.if.properties)) probe[k] = "";
216
+ fn(probe); fn({}); fn(null); fn(0);
217
+ _safeRich = fn;
218
+ } catch {}
219
+ }
220
+ return _safeRich;
221
+ };
222
+
134
223
  // What the hybrid path hands to its error slot: the combined function
135
224
  // when it is usable, since it validates and collects in one pass, and
136
225
  // the error generator otherwise. Same order the eager code chose, just
@@ -227,6 +316,38 @@ function installCodegenPaths (ctx) {
227
316
  // and used to call validate() to get the errors, which ran the verdict a
228
317
  // second time and built a second rejection around it.
229
318
  if (!preprocess) ctx.rejectBase = onReject;
319
+ // The rich combined function, for a validator whose errors are being read
320
+ // with richErrors on. See _ensureCompiled.
321
+ if (!preprocess) {
322
+ ctx.richBuilder = richBuilder;
323
+ // The plain combined function stands in where no rich one is built
324
+ // (richErrors off, or a schema source to frame errors from): its errors
325
+ // are presented, and enriched when asked, the usual way, without the
326
+ // rejection layers between.
327
+ // Where the plain combined function declines (oneOf, anyOf, an
328
+ // additionalProperties schema), the error function stands in, called
329
+ // directly rather than through the rejection layers. Its errors can be
330
+ // collapsed branches that still need presenting; only the combined
331
+ // function's are already in their final plain shape (isCleanShape).
332
+ ctx.oneShotRich = ctx.buildRich ? richIfSafe : () => combinedIfSafe() || errFnIfSafe();
333
+ ctx.oneShotPlain = !ctx.buildRich;
334
+ ctx.isCleanShape = (fn) => fn !== null && fn === _safeCombined;
335
+ ctx.isCombined = (fn) => fn !== null && fn === _safeCombined;
336
+ // validate() as the combined function in its result shape: built and
337
+ // probed here, null when it declines or throws on the probe.
338
+ ctx.onePassOf = (empty, fallback) => {
339
+ // A collapsed branch error is built in its final plain shape here
340
+ // (no ordering key in the runtime shape); its branch errors are put
341
+ // in order with the rest.
342
+ const sort = (errs) => (_needsOrdering(errs) ? _sortErrors(schemaObj, errs) : errs);
343
+ try {
344
+ const fn = jsCompiler.compileToJSCombined(schemaObj, VALID_RESULT, this._schemaMap.size > 0 ? this._schemaMap : null, this._userFormats, { runtimeShape: true, resultShape: { Rejection: EagerRejection, empty, sort, fallback, verdict: jsFn } });
345
+ if (!fn) return null;
346
+ fn({}); fn(null); fn(0);
347
+ return fn;
348
+ } catch { return null; }
349
+ };
350
+ }
230
351
  } else {
231
352
  // No hybrid factory, so the assembly needs the function itself rather
232
353
  // than a reference it can call later: build it now.
@@ -256,6 +377,7 @@ function installCodegenPaths (ctx) {
256
377
  // The verbose fields are added here, so a path around this layer would
257
378
  // miss them.
258
379
  ctx.rejectBase = null;
380
+ ctx.oneShotRich = null;
259
381
  this.validate = _verboseWrap(this.validate, this._schemaObj);
260
382
  }
261
383
  // The verdict methods answer validate()'s question without building the
@@ -395,8 +517,9 @@ installCodegenPaths.compileVerdict = function compileVerdict () {
395
517
  return;
396
518
  }
397
519
  const uf = this._userFormats;
398
- const _cg = compileToJSCodegen(this._schemaObj, sm, uf);
399
- const jsFn = _cg || compileToJS(this._schemaObj, null, sm);
520
+ // Custom keywords: see the note on the same call in lib/validator-core.js.
521
+ const _cg = compileToJSCodegen(this._schemaObj, sm, uf, this._keywords ? { keywords: this._keywords } : undefined);
522
+ const jsFn = _cg || (this._usesKeywords ? null : compileToJS(this._schemaObj, null, sm));
400
523
  this._jsFn = jsFn;
401
524
  if (jsFn) {
402
525
  _bindVerdict(this, jsFn);
@@ -408,7 +531,7 @@ installCodegenPaths.compileVerdict = function compileVerdict () {
408
531
  // is the bug this cache had once already. `isCodegen` rides along so a
409
532
  // validator that later reuses this entry reports the same engine it
410
533
  // would have compiled to.
411
- if (!uf) {
534
+ if (!uf && !this._keywords) {
412
535
  if (!cached) _compileCache.set(mapKey, { jsFn, combined: undefined, errFn: undefined, isCodegen: !!_cg, full: false });
413
536
  else cached.jsFn = jsFn;
414
537
  }
@@ -433,6 +556,9 @@ installCodegenPaths.installScanner = function installScanner (schemaObj, options
433
556
  // builds it now, which is how the differential test reaches it.
434
557
  this._ensureScanner = (now) => {
435
558
  if (self._scanner === undefined) {
559
+ // The scanner reads the schema's own keywords from the text and knows
560
+ // nothing of custom ones, so it would accept what they reject.
561
+ if (self._usesKeywords) { self._scanner = null; return null; }
436
562
  if (!now && ++calls < SCAN_AFTER) return undefined;
437
563
  const built = require('./lib/scan-compiler').compileScanner(schemaObj, { userFormats: self._userFormats });
438
564
  self._scanner = built ? built.scan : null;
package/lib/aot-build.js CHANGED
@@ -30,9 +30,13 @@ async function expandGlobs(globs) {
30
30
  out.push(path.resolve(g));
31
31
  continue;
32
32
  }
33
- const m = g.match(/^(.*?)(?:\/\*\.(.+))?$/);
34
- const dir = m && m[1] ? m[1] : '.';
35
- const ext = m && m[2] ? '.' + m[2] : null;
33
+ // Split on the first "/*." that has something after it, by index rather
34
+ // than a regular expression, which backtracked polynomially on a long
35
+ // run of "/*.a".
36
+ const star = g.indexOf('/*.');
37
+ const split = star !== -1 && star + 3 < g.length;
38
+ const dir = (split ? g.slice(0, star) : g) || '.';
39
+ const ext = split ? '.' + g.slice(star + 3) : null;
36
40
  if (!fs.existsSync(dir)) continue;
37
41
  for (const entry of fs.readdirSync(dir)) {
38
42
  if (ext && !entry.endsWith(ext)) continue;
package/lib/aot-impl.js CHANGED
@@ -727,11 +727,28 @@ function loadBundle(Validator, mods, schemas, opts) {
727
727
  });
728
728
  }
729
729
 
730
+ // A module written here may end up inline in an HTML page, inside a <script>
731
+ // element, and the HTML parser ends that element at the first `</script` it
732
+ // sees, whatever JavaScript context it is in. A schema string carrying one, an
733
+ // enum value, a pattern, a property name, closed the element early and handed
734
+ // the rest of the module to the HTML parser. `<!--` changes how the parser
735
+ // reads the element too. Both are written with `\x3C` for the `<`, an escape
736
+ // that means the same character in a string, a template and a regular
737
+ // expression, with or without the u flag. An escaped `\<`, which the emitters
738
+ // do not write but a pattern could carry, has its backslash dropped, since
739
+ // `\x3C` already is the escape. A module with neither sequence, which is
740
+ // nearly every one, comes back as the same string.
741
+ function htmlSafe(src) {
742
+ if (typeof src !== 'string' || src.indexOf('<') === -1) return src;
743
+ return src.replace(/(\\*)<(\/script|!--)/gi, (m, bs, rest) => (bs.length % 2 ? bs.slice(0, -1) : bs) + '\\x3C' + rest);
744
+ }
745
+ const htmlSafeResult = (fn) => function () { return htmlSafe(fn.apply(this, arguments)); };
746
+
730
747
  module.exports = {
731
- toStandalone,
732
- toStandaloneModule,
733
- bundle,
734
- bundleStandalone,
735
- bundleCompact,
748
+ toStandalone: htmlSafeResult(toStandalone),
749
+ toStandaloneModule: htmlSafeResult(toStandaloneModule),
750
+ bundle: htmlSafeResult(bundle),
751
+ bundleStandalone: htmlSafeResult(bundleStandalone),
752
+ bundleCompact: htmlSafeResult(bundleCompact),
736
753
  loadBundle,
737
754
  };
@@ -40,25 +40,30 @@ function __ataScore (errs) {
40
40
  return errs.length * 100 + s; // primary: count, secondary: severity
41
41
  }
42
42
 
43
- // `br` is one entry per branch in declaration order: {valid, errors, title}.
43
+ // `br` is one entry per branch in declaration order: {valid, errors} and a
44
+ // `title`, or the titles in `titles` when given (the generated code passes
45
+ // each branch's result as it is rather than copying it to add one).
44
46
  // Returns the single error to report, or null when the keyword is satisfied.
45
- function __ataCollapse (kw, br, pp, sp, o) {
46
- const pass = br.filter((b) => b.valid);
47
+ function __ataCollapse (kw, br, pp, sp, o, titles) {
48
+ let passN = 0;
49
+ for (let i = 0; i < br.length; i++) if (br[i].valid) passN++;
50
+ const pass = { length: passN };
47
51
  if (kw === 'oneOf') {
48
52
  if (pass.length === 1) return null;
49
53
  if (pass.length > 1) {
50
54
  const passIdx = [];
51
55
  for (let i = 0; i < br.length; i++) if (br[i].valid) passIdx.push(i);
52
- return {
56
+ const multi = {
53
57
  code: 'ATA4002',
54
58
  keyword: 'oneOf',
55
59
  instancePath: pp || '',
56
60
  path: pp || '',
57
61
  schemaPath: sp,
58
- _o: o,
59
62
  message: 'value matched ' + pass.length + ' of ' + br.length + ' oneOf variants, expected exactly one',
60
63
  params: { passingSchemas: passIdx },
61
64
  };
65
+ if (o !== null) Object.defineProperty(multi, '_o', { value: o });
66
+ return multi;
62
67
  }
63
68
  } else if (pass.length >= 1) {
64
69
  return null;
@@ -70,17 +75,21 @@ function __ataCollapse (kw, br, pp, sp, o) {
70
75
  if (s < bs) { bs = s; bi = i; }
71
76
  }
72
77
  const best = br[bi];
73
- return {
78
+ // The ordering key, when there is one, is not enumerable: the error is
79
+ // otherwise in the shape it is handed out in, and a caller that reads it
80
+ // as it comes (the combined function's one-pass result) does not copy it.
81
+ const none = {
74
82
  code: kw === 'oneOf' ? 'ATA4001' : 'ATA4003',
75
83
  keyword: kw,
76
84
  instancePath: pp || '',
77
85
  path: pp || '',
78
86
  schemaPath: sp,
79
- _o: o,
80
87
  message: 'value matched 0 of ' + br.length + ' ' + kw + ' variants',
81
- params: { variants: br.length, closest: bi, closestName: best.title || ('variant ' + (bi + 1)) },
88
+ params: { variants: br.length, closest: bi, closestName: (titles !== undefined ? titles[bi] : best.title) || ('variant ' + (bi + 1)) },
82
89
  branchErrors: best.errors,
83
90
  };
91
+ if (o !== null) Object.defineProperty(none, '_o', { value: o });
92
+ return none;
84
93
  }
85
94
 
86
95
  // The named-argument form the interpreter and the tests call.
package/lib/draft7.js CHANGED
@@ -166,6 +166,22 @@ function _normalizeNullable(schema) {
166
166
  }
167
167
  }
168
168
  if ('nullable' in schema) delete schema.nullable
169
+ // A JSON Pointer in a URI fragment is percent-encoded, so a definition named
170
+ // `Node<Row>` is reached by `#/definitions/Node%3CRow%3E` (RFC 6901, section
171
+ // 6). The interpreter decodes the fragment; the code generator compared the
172
+ // encoded text with the definition names and declined the whole document, so
173
+ // one such reference sent every validator built from it to the interpreter and
174
+ // made `ata compile` refuse it. Rewritten here to the decoded pointer, every
175
+ // engine reads the same text. A decoded form still holding a `%` is left
176
+ // alone: the engines decode once more, and `100%` must not be decoded twice.
177
+ // This walk is the one every normalization runs, so it costs no second pass;
178
+ // a reference it does not reach keeps the old behaviour, the interpreter.
179
+ if (/^#.*%/.test(schema.$ref)) {
180
+ try {
181
+ const decoded = decodeURIComponent(schema.$ref)
182
+ if (!decoded.includes('%')) schema.$ref = decoded
183
+ } catch {}
184
+ }
169
185
 
170
186
  const objSubs = ['properties', 'patternProperties', '$defs', 'definitions', 'dependentSchemas']
171
187
  for (const key of objSubs) {
@@ -185,40 +185,56 @@ function rankFor (keyword) {
185
185
  function enrich (rawErr, opts) {
186
186
  const data = opts && opts.data;
187
187
  const positions = opts && opts.positions;
188
- const format = rawErr.params && rawErr.params.format;
189
- // The native engine reports its own enum as a number. Translate it to the
190
- // keyword and public code the JavaScript engines use, so an error means the
191
- // same thing whichever engine produced it.
192
- const fromAddon = typeof rawErr.code === 'number' ? fromNative(rawErr.code, format) : null;
193
- const keyword = rawErr.keyword || (fromAddon && fromAddon.keyword);
194
- // Prefer a code the codegen already attached (e.g. branch-collapse emits
195
- // ATA4001/4002/4003 distinguishing zero/multi/anyOf failure modes). The
196
- // keyword-derived lookup only finds the first match for `keyword: 'oneOf'`.
197
- // A property that dependentRequired asks for is reported with the keyword
198
- // `required`, and its documented code is ATA7005, not required's ATA7001.
199
- // The error generator stamped 7005 and the other engines left it to the
200
- // keyword here, so the same failure carried either code depending on which
201
- // generator answered.
202
- const dependent = keyword === 'required' && typeof rawErr.schemaPath === 'string' && rawErr.schemaPath.endsWith('/dependentRequired');
203
- const code = (fromAddon && fromAddon.code) ||
204
- (typeof rawErr.code === 'string' && rawErr.code) ||
205
- (dependent && 'ATA7005') ||
206
- codeFor(keyword, format) ||
207
- 'ATA9001';
208
- const meta = CODES[code];
209
- const path = rawErr.instancePath != null ? rawErr.instancePath : (rawErr.path || '');
210
-
188
+ // A literal the generated code shares between calls carries a slot for the
189
+ // fields that depend on nothing but the literal: worked out on the first
190
+ // read, taken from the slot after that.
191
+ let st = rawErr._t;
192
+ if (st === undefined || st.code === undefined) {
193
+ const format = rawErr.params && rawErr.params.format;
194
+ // The native engine reports its own enum as a number. Translate it to the
195
+ // keyword and public code the JavaScript engines use, so an error means the
196
+ // same thing whichever engine produced it.
197
+ const fromAddon = typeof rawErr.code === 'number' ? fromNative(rawErr.code, format) : null;
198
+ const keyword = rawErr.keyword || (fromAddon && fromAddon.keyword);
199
+ // Prefer a code the codegen already attached (e.g. branch-collapse emits
200
+ // ATA4001/4002/4003 distinguishing zero/multi/anyOf failure modes). The
201
+ // keyword-derived lookup only finds the first match for `keyword: 'oneOf'`.
202
+ // A property that dependentRequired asks for is reported with the keyword
203
+ // `required`, and its documented code is ATA7005, not required's ATA7001.
204
+ // The error generator stamped 7005 and the other engines left it to the
205
+ // keyword here, so the same failure carried either code depending on which
206
+ // generator answered.
207
+ const dependent = keyword === 'required' && typeof rawErr.schemaPath === 'string' && rawErr.schemaPath.endsWith('/dependentRequired');
208
+ const code = (fromAddon && fromAddon.code) ||
209
+ (typeof rawErr.code === 'string' && rawErr.code) ||
210
+ (dependent && 'ATA7005') ||
211
+ codeFor(keyword, format) ||
212
+ 'ATA9001';
213
+ const meta = CODES[code];
214
+ const fixed = {
215
+ code,
216
+ message: rawErr.message || (meta && meta.headline) || 'validation failed',
217
+ keyword,
218
+ path: rawErr.instancePath != null ? rawErr.instancePath : (rawErr.path || ''),
219
+ expected: expectedFor(rawErr),
220
+ docUrl: DOC_BASE + code,
221
+ rank: rankFor(keyword),
222
+ };
223
+ if (st !== undefined && !fromAddon) Object.assign(st, fixed);
224
+ st = fixed;
225
+ }
226
+ const path = st.path;
211
227
  const at = data !== undefined ? resolveAt(rawErr, data) : MISSING;
212
228
 
213
229
  const out = {
214
- code,
215
- message: rawErr.message || (meta && meta.headline) || 'validation failed',
216
- keyword,
230
+ code: st.code,
231
+ message: st.message,
232
+ keyword: st.keyword,
217
233
  path,
218
- expected: expectedFor(rawErr),
234
+ expected: st.expected,
219
235
  received: at === MISSING ? undefined : reprValue(at),
220
236
  schemaPath: rawErr.schemaPath,
221
- docUrl: DOC_BASE + code,
237
+ docUrl: st.docUrl,
222
238
  // Back-compat aliases (additive, present in both rich and legacy paths)
223
239
  instancePath: path,
224
240
  dataPath: path,
@@ -264,7 +280,7 @@ function enrich (rawErr, opts) {
264
280
 
265
281
  const detail = detailFor(rawErr, out);
266
282
  if (detail !== undefined) out.detail = detail;
267
- out.rank = rankFor(keyword);
283
+ out.rank = st.rank;
268
284
 
269
285
  // Token-level anchor. `dataFrame` already carries the value span; `anchor`
270
286
  // adds the key span so a caret can sit on the property that is wrong rather
@@ -287,4 +303,4 @@ function enrich (rawErr, opts) {
287
303
  return out;
288
304
  }
289
305
 
290
- module.exports = { enrich, reprValue, expectedFor };
306
+ module.exports = { enrich, reprValue, expectedFor, detailFor, rankFor, MISSING, DOC_BASE };
@@ -0,0 +1,58 @@
1
+ 'use strict';
2
+
3
+ const { CODES, codeFor } = require('./error-codes');
4
+ const { suggestFor } = require('./suggestions');
5
+ const { reprValue, expectedFor, detailFor, rankFor, MISSING, DOC_BASE } = require('./enrich-error');
6
+
7
+ // Enrichment done where the error happens. The combined generator, once a
8
+ // validator's errors are being read, calls `makeRich` at each failure with a
9
+ // site record built at compile time by `errorSite` and the value it was
10
+ // checking, and gets the same object `enrich` would build from the raw error:
11
+ // same keys, same order, same values. What depends only on the site (code,
12
+ // docUrl, rank, and `expected` when the params are fixed) is worked out once,
13
+ // and the value comes from the generated code instead of a pointer walk. It is
14
+ // for validate(data) without positions or a schema source; those reads keep
15
+ // `enrich`. tests/test_rich_errors_generated.js holds the two to the same
16
+ // output. A module of its own because only the code generator reaches it, and
17
+ // ata-validator/lite, which enriches through `enrich`, should not carry it.
18
+ const NO_EXPECTED = {};
19
+ function errorSite (keyword, schemaPath, params, format) {
20
+ const dependent = keyword === 'required' && typeof schemaPath === 'string' && schemaPath.endsWith('/dependentRequired');
21
+ const code = (dependent && 'ATA7005') || codeFor(keyword, format) || 'ATA9001';
22
+ return {
23
+ code,
24
+ keyword,
25
+ schemaPath,
26
+ docUrl: DOC_BASE + code,
27
+ rank: rankFor(keyword),
28
+ headline: (CODES[code] && CODES[code].headline) || 'validation failed',
29
+ expected: params === undefined ? NO_EXPECTED : expectedFor({ keyword, params }),
30
+ };
31
+ }
32
+
33
+ function makeRich (site, path, params, message, value, root) {
34
+ // As resolveAt: a root that is null, undefined, '' or NaN resolves nothing.
35
+ const at = (!root && root !== 0 && root !== false) ? MISSING : value;
36
+ const out = {
37
+ code: site.code,
38
+ message: message || site.headline,
39
+ keyword: site.keyword,
40
+ path,
41
+ expected: site.expected === NO_EXPECTED ? expectedFor({ keyword: site.keyword, params }) : site.expected,
42
+ received: at === MISSING ? undefined : reprValue(at),
43
+ schemaPath: site.schemaPath,
44
+ docUrl: site.docUrl,
45
+ instancePath: path,
46
+ dataPath: path,
47
+ params,
48
+ parentSchema: undefined,
49
+ };
50
+ const sugg = suggestFor(out, root, at === MISSING ? undefined : at);
51
+ if (sugg) out.suggestion = sugg;
52
+ const detail = detailFor({ keyword: site.keyword, params }, out);
53
+ if (detail !== undefined) out.detail = detail;
54
+ out.rank = site.rank;
55
+ return out;
56
+ }
57
+
58
+ module.exports = { errorSite, makeRich };
@@ -1255,9 +1255,15 @@ function createInterpreter(schema, options) {
1255
1255
  }
1256
1256
 
1257
1257
  module.exports = { createInterpreter };
1258
+ // The resolver, for the code generator: it resolves references at compile
1259
+ // time the way this engine does at run time (normalizeRefs in js-compiler).
1260
+ Object.defineProperty(module.exports, '_refInternals', {
1261
+ value: { indexSchemas, resolveRef, FALLBACK_BASE },
1262
+ enumerable: false,
1263
+ });
1258
1264
  // For lib/plan-source.js, which emits source from the same plans and must call
1259
1265
  // the same helpers so the two cannot answer differently.
1260
1266
  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 },
1267
+ value: { Plan, deepEqual, cpAtLeast, cpAtMost, multipleOfOk, dataBits, typeBit, T_STRING, T_NUMBER, T_INTEGER, T_BOOLEAN, T_NULL, T_OBJECT, T_ARRAY, T_ANY },
1262
1268
  enumerable: false,
1263
1269
  });