ata-validator 1.60.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.
Files changed (56) hide show
  1. package/README.md +134 -6
  2. package/bin/ata.js +84 -0
  3. package/build.d.ts +29 -0
  4. package/build.mjs +2 -0
  5. package/compiled-bare-rich.d.ts +6 -0
  6. package/compiled-bare.d.ts +7 -0
  7. package/compiled-bare.js +5 -0
  8. package/compiled-bare.mjs +35 -0
  9. package/compiled-object.d.ts +11 -0
  10. package/compiled-object.js +6 -0
  11. package/compiled-object.mjs +3 -0
  12. package/compiled-plain.d.ts +16 -0
  13. package/compiled-plain.js +5 -0
  14. package/compiled-plain.mjs +3 -0
  15. package/compiled.d.ts +3 -1
  16. package/index.browser.mjs +2 -2
  17. package/index.bundle.mjs +2 -2
  18. package/index.d.ts +9 -0
  19. package/index.edge.mjs +2 -2
  20. package/index.js +48 -0
  21. package/index.mjs +1 -1
  22. package/index.node.mjs +1 -1
  23. package/lib/aot-build.js +81 -6
  24. package/lib/aot-impl.js +24 -21
  25. package/lib/bun-target.js +612 -0
  26. package/lib/codegen-engine.js +9 -3
  27. package/lib/combined-runtime.js +1 -1
  28. package/lib/compiled-bare-rich.js +54 -0
  29. package/lib/compiled-bare.js +44 -0
  30. package/lib/compiled-common.js +17 -2
  31. package/lib/compiled-defaults.browser.js +9 -0
  32. package/lib/compiled-defaults.js +8 -0
  33. package/lib/compiled-object.js +123 -0
  34. package/lib/compiled-plain.js +57 -0
  35. package/lib/compiled.js +2 -1
  36. package/lib/email-literal.js +29 -0
  37. package/lib/enrich-error.js +5 -1
  38. package/lib/error-codes.js +6 -76
  39. package/lib/formats-source.js +20 -3
  40. package/lib/formats.js +1 -1
  41. package/lib/js-compiler-errors.js +49 -10
  42. package/lib/js-compiler.js +92 -13
  43. package/lib/json-rejections.js +159 -0
  44. package/lib/native-codes.js +67 -0
  45. package/lib/native-message.js +8 -0
  46. package/lib/native.browser.js +11 -0
  47. package/lib/public-api.js +1 -0
  48. package/lib/rejections.js +13 -152
  49. package/lib/standard-schema.js +1 -1
  50. package/lib/tier0.js +1 -1
  51. package/lib/validator-core.js +46 -4
  52. package/lib/verify.js +386 -0
  53. package/lib/version.js +1 -1
  54. package/lite.js +1 -0
  55. package/lite.mjs +1 -1
  56. package/package.json +44 -9
@@ -0,0 +1,54 @@
1
+ 'use strict';
2
+
3
+ // bareRich: the compiled-bare wrapper for a validator that reads ata's own
4
+ // errors (`richErrors` left on) and is only ever called, never handed to other
5
+ // code: validate() and isValidObject() as lib/compiled-object.js answers them,
6
+ // down to the error objects, without the Standard Schema member and the
7
+ // extension hook nothing can reach there, and without the option checks the
8
+ // bundler plugin already made. A separate entry from lib/compiled-bare.js so
9
+ // a bundle with only plain or verdict validators carries no error pipeline.
10
+ // tests/test_compiled_bare.js holds it to the runtime.
11
+
12
+ const { LazyRejection, RichRejection, _enrichLazy } = require('./rejections');
13
+
14
+ const EMPTY_ERRORS = Object.freeze([]);
15
+ const VERDICT_DISAGREES = Object.freeze({
16
+ valid: false,
17
+ errors: Object.freeze([Object.freeze({ keyword: 'validation', instancePath: '', schemaPath: '#', params: Object.freeze({}), message: 'schema validation failed' })]),
18
+ });
19
+
20
+ // The fields of a Validator the rejection classes read.
21
+ class CompiledState {
22
+ constructor(schema, fill) {
23
+ this._schemaObj = schema;
24
+ this._schemaPositions = undefined;
25
+ this._source = undefined;
26
+ this._mutatesInput = fill !== null;
27
+ this._preprocess = fill;
28
+ }
29
+ }
30
+
31
+ function bareRich(mod, schema, useDefaults) {
32
+ const fill = useDefaults !== false && typeof mod.applyDefaults === 'function' ? mod.applyDefaults : null;
33
+ const verdict = mod.isValid;
34
+ const isValid = fill ? (d) => { fill(d); return verdict(d); } : verdict;
35
+ const self = new CompiledState(schema, fill);
36
+ const errFn = mod.validate;
37
+ const rich = (data) => {
38
+ const r = errFn(data);
39
+ return new RichRejection(r && r.valid === false ? r : VERDICT_DISAGREES, data, null, self, schema, _enrichLazy);
40
+ };
41
+ const buildErrors = (data) => rich(data).errors;
42
+ const buildRawErrors = (data) => rich(data)._ataRaw();
43
+ return {
44
+ validate(data) {
45
+ if (isValid(data)) return { valid: true, data, errors: EMPTY_ERRORS };
46
+ // The runtime returns the rich rejection directly when it rewrites input.
47
+ if (fill) return rich(data);
48
+ return new LazyRejection(buildErrors, data, buildRawErrors);
49
+ },
50
+ isValidObject: isValid,
51
+ };
52
+ }
53
+
54
+ module.exports = { bareRich };
@@ -0,0 +1,44 @@
1
+ 'use strict';
2
+
3
+ // The smallest wrappers around a compiled module, for a validator the bundler
4
+ // plugin sees used only through direct calls of its methods: never handed to
5
+ // other code, so nothing reads `~standard` or registers a check on it with
6
+ // `_extendChecks`, and its options were read at build time. What is left is
7
+ // what those calls answer: defaults filled with the module's own pass, the
8
+ // verdict, and for `richErrors: false` the module's plain errors as they are.
9
+ // The same answers as fromCompiledVerdict and fromCompiledPlain
10
+ // (tests/test_compiled_bare.js), without their API surface: 0.3 KB gzipped
11
+ // against 1.6 and 2.6.
12
+
13
+ const EMPTY_ERRORS = Object.freeze([]);
14
+ const VERDICT_DISAGREES = Object.freeze({
15
+ valid: false,
16
+ errors: Object.freeze([Object.freeze({ keyword: 'validation', instancePath: '', schemaPath: '#', params: Object.freeze({}), message: 'schema validation failed' })]),
17
+ });
18
+
19
+ function verdictOf(mod, useDefaults) {
20
+ const fill = useDefaults !== false && typeof mod.applyDefaults === 'function' ? mod.applyDefaults : null;
21
+ return fill ? (d) => { fill(d); return mod.isValid(d); } : mod.isValid;
22
+ }
23
+
24
+ // isValidObject() only.
25
+ function bareVerdict(mod, useDefaults) {
26
+ return { isValidObject: verdictOf(mod, useDefaults) };
27
+ }
28
+
29
+ // validate() and isValidObject() for `richErrors: false`, around a module
30
+ // written with `plainErrors: true`.
31
+ function barePlain(mod, useDefaults) {
32
+ const isValid = verdictOf(mod, useDefaults);
33
+ const errFn = mod.validate;
34
+ return {
35
+ validate(data) {
36
+ if (isValid(data)) return { valid: true, data, errors: EMPTY_ERRORS };
37
+ const r = errFn(data);
38
+ return r && r.valid === false ? { valid: false, errors: r.errors } : VERDICT_DISAGREES;
39
+ },
40
+ isValidObject: isValid,
41
+ };
42
+ }
43
+
44
+ module.exports = { bareVerdict, barePlain };
@@ -5,7 +5,22 @@
5
5
  // points; lib/compiled-verdict.js answers only the two that return a boolean,
6
6
  // so a bundle that never reads errors does not carry the error pipeline.
7
7
 
8
- const { buildDefaultsApplier } = require('./defaults');
8
+ // A module from 1.58.0 on fills its own defaults (`applyDefaults`, exported
9
+ // when the schema has one, the same pass as the runtime's: tests hold the two
10
+ // equal), so the wrapper takes that and the defaults engine stays out of a
11
+ // bundle that wraps current modules. An older module, or one without the
12
+ // version stamp, takes the runtime's pass as before (lib/compiled-defaults.js).
13
+ const MODULE_DEFAULTS_SINCE = [1, 58, 0];
14
+ function carriesDefaults (mod) {
15
+ const v = typeof mod.ataVersion === 'string' ? mod.ataVersion.split(/[.-]/).slice(0, 3).map(Number) : null;
16
+ if (!v || v.some((n) => !Number.isInteger(n))) return false;
17
+ for (let i = 0; i < 3; i++) if (v[i] !== MODULE_DEFAULTS_SINCE[i]) return v[i] > MODULE_DEFAULTS_SINCE[i];
18
+ return true;
19
+ }
20
+ function defaultsFor (mod, schema) {
21
+ if (carriesDefaults(mod)) return typeof mod.applyDefaults === 'function' ? mod.applyDefaults : null;
22
+ return require('./compiled-defaults')(schema);
23
+ }
9
24
 
10
25
  // The Validator options the wrapper answers the same way as the runtime.
11
26
  // Anything else changes what the runtime does in a way the wrapper does not
@@ -23,7 +38,7 @@ function prepare(mod, schema, options, name) {
23
38
  throw new TypeError(`${name} does not support the ${key} option; a Validator with it has to stay on the runtime`);
24
39
  }
25
40
  }
26
- const fill = options && options.useDefaults === false ? null : buildDefaultsApplier(schema);
41
+ const fill = options && options.useDefaults === false ? null : defaultsFor(mod, schema);
27
42
  const isValid = fill ? (d) => { fill(d); return mod.isValid(d); } : mod.isValid;
28
43
  return { fill, isValid };
29
44
  }
@@ -0,0 +1,9 @@
1
+ 'use strict';
2
+
3
+ // Browser build of lib/compiled-defaults.js. A module from ata-validator 1.58.0
4
+ // on carries `applyDefaults` when its schema has a default, and the wrapper
5
+ // uses it; only an older module needs the runtime's pass, which this build
6
+ // leaves out. Such a module is refused rather than run without its defaults.
7
+ module.exports = function () {
8
+ throw new TypeError('This compiled module was built before ata-validator 1.58.0 and its schema has defaults; rebuild it to use it in a browser bundle.');
9
+ };
@@ -0,0 +1,8 @@
1
+ 'use strict';
2
+
3
+ // The runtime's defaults pass, for a module built before modules carried their
4
+ // own (ata-validator 1.58.0, when the pass reached behind `$ref`, `allOf` and
5
+ // array elements). The browser build maps this file to
6
+ // lib/compiled-defaults.browser.js, so a page that wraps a current module does
7
+ // not carry the defaults engine twice.
8
+ module.exports = require('./defaults').buildDefaultsApplier;
@@ -0,0 +1,123 @@
1
+ 'use strict';
2
+
3
+ // lib/compiled.js without the JSON entry points: validate(), isValidObject()
4
+ // and Standard Schema, answered as a Validator with default options answers
5
+ // them, down to the error objects. A bundler plugin puts this in place of
6
+ // `new Validator(schema)` when the code reads errors but never calls
7
+ // validateJSON() or isValidJSON(), so the position map those use to frame an
8
+ // error in the JSON text (lib/data-position-cache.js and the walk behind it)
9
+ // stays out of the bundle: 14.1 to 12.3 KB gzipped for the wrapper.
10
+ // tests/test_compiled_parity.js holds it to the full wrapper.
11
+ //
12
+ // Only for a schema compiledEligible() accepts, and with the schema
13
+ // compiledSchemaFor() returns: the one the runtime reads, after normalization.
14
+ // Custom error messages stay on the runtime, and so does every option except
15
+ // the ones COMPILED_OPTIONS lists. Defaults are applied as a default Validator
16
+ // applies them, before any check, with the same code; `useDefaults: false`
17
+ // leaves the input as it is, as the runtime then does.
18
+
19
+ const { LazyRejection, RichRejection, _enrichLazy } = require('./rejections');
20
+ const { prepare, installExtension, EXT_FALLBACK } = require('./compiled-common');
21
+ const { installStandard } = require('./standard-schema');
22
+
23
+ const EMPTY_ERRORS = Object.freeze([]);
24
+ const VERDICT_DISAGREES = Object.freeze({
25
+ valid: false,
26
+ errors: Object.freeze([Object.freeze({ keyword: 'validation', instancePath: '', schemaPath: '#', params: Object.freeze({}), message: 'schema validation failed' })]),
27
+ });
28
+
29
+ // The fields of a Validator the rejection classes read.
30
+ class CompiledState {
31
+ constructor(schema) {
32
+ this._schemaObj = schema;
33
+ this._schemaPositions = undefined;
34
+ this._source = undefined;
35
+ this._mutatesInput = false;
36
+ this._preprocess = null;
37
+ }
38
+ }
39
+
40
+ function fromCompiledObject(mod, schema, options) {
41
+ const { fill, isValid } = prepare(mod, schema, options, 'fromCompiledObject');
42
+ const self = new CompiledState(schema);
43
+ if (fill) {
44
+ self._mutatesInput = true;
45
+ self._preprocess = fill;
46
+ }
47
+ const errFn = mod.validate;
48
+ // The generated function's own result, rejected: if it says valid where the
49
+ // verdict said no, the disagreement is reported rather than turned into an
50
+ // acceptance, as the core does.
51
+ const raw = (data) => {
52
+ const r = errFn(data);
53
+ return r && r.valid === false ? r : VERDICT_DISAGREES;
54
+ };
55
+ const rich = (data) => new RichRejection(raw(data), data, null, self, schema, _enrichLazy);
56
+ const buildErrors = (data) => rich(data).errors;
57
+ const buildRawErrors = (data) => rich(data)._ataRaw();
58
+ const api = {
59
+ validate(data) {
60
+ if (isValid(data)) return { valid: true, data, errors: EMPTY_ERRORS };
61
+ // The runtime returns the rich rejection directly when it rewrites input.
62
+ if (fill) return rich(data);
63
+ return new LazyRejection(buildErrors, data, buildRawErrors);
64
+ },
65
+ isValidObject(data) {
66
+ return isValid(data);
67
+ },
68
+ };
69
+ installExtension(api, schema, (base, ext) => {
70
+ return {
71
+ // The verdict is the schema's and the check's. A failure lists the
72
+ // schema's errors, then the check's, read when somebody asks.
73
+ validate(data) {
74
+ const r = base.validate(data);
75
+ if (r.valid && ext.check(data)) return r;
76
+ return new ExtendedRejection(r, data, ext.errors);
77
+ },
78
+ isValidObject: (d) => base.isValidObject(d) && ext.check(d),
79
+ };
80
+ });
81
+ // Standard Schema V1, as the runtime Validator hands it out, so a validator
82
+ // the build compiled away still works where the runtime one did (a form
83
+ // library, a router, an RPC layer). The schema it publishes is the one the
84
+ // module was built from, as the runtime reads it.
85
+ installStandard(api, () => schema, (self, value) => self.validate(value));
86
+ return api;
87
+ }
88
+
89
+ // validate() under a check, as the runtime reports it (ExtendedRejection in
90
+ // validator-core.js): the inner result's errors, then the check's.
91
+ class ExtendedRejection {
92
+ constructor(inner, data, collect) {
93
+ this.valid = false;
94
+ this._inner = inner;
95
+ this._data = data;
96
+ this._collect = collect;
97
+ this._errors = null;
98
+ }
99
+ toJSON() {
100
+ return { valid: false, errors: this.errors };
101
+ }
102
+ _ataRaw() {
103
+ const inner = this._inner;
104
+ const more = this._collect(this._data) || [];
105
+ const raw = inner.valid ? more : (typeof inner._ataRaw === 'function' ? inner._ataRaw() : inner.errors).concat(more);
106
+ return raw.length ? raw : [EXT_FALLBACK];
107
+ }
108
+ }
109
+ Object.defineProperty(ExtendedRejection.prototype, 'errors', {
110
+ enumerable: true,
111
+ configurable: true,
112
+ get() {
113
+ if (this._errors === null) {
114
+ const inner = this._inner;
115
+ const more = this._collect(this._data) || [];
116
+ const all = inner.valid ? more : inner.errors.concat(more);
117
+ this._errors = all.length ? all : [EXT_FALLBACK];
118
+ }
119
+ return this._errors;
120
+ },
121
+ });
122
+
123
+ module.exports = { fromCompiledObject };
@@ -0,0 +1,57 @@
1
+ 'use strict';
2
+
3
+ // The compiled-module wrapper for `new Validator(schema, { richErrors: false })`:
4
+ // validate(), isValidObject() and Standard Schema, answered as such a
5
+ // Validator answers them, errors in its plain shape (keyword, instancePath,
6
+ // schemaPath, params, message). The module is written with `plainErrors: true`
7
+ // (compiledModuleFor in ata-validator/build), so its errors are already that
8
+ // shape and in that order, and this wrapper hands them out as they are: no
9
+ // rejection classes, no enrichment, no suggestions. 2.6 KB gzipped against
10
+ // 10.9 for the rich wrapper without JSON entry points.
11
+ // tests/test_compiled_plain.js holds it to the runtime over the suite.
12
+
13
+ const { prepare, installExtension, EXT_FALLBACK } = require('./compiled-common');
14
+ const { installStandard } = require('./standard-schema');
15
+
16
+ const EMPTY_ERRORS = Object.freeze([]);
17
+ // The module's own verdict and its error function disagree (the data changed
18
+ // between them): reported, never turned into an acceptance, as the core does.
19
+ const VERDICT_DISAGREES = Object.freeze({
20
+ valid: false,
21
+ errors: Object.freeze([Object.freeze({ keyword: 'validation', instancePath: '', schemaPath: '#', params: Object.freeze({}), message: 'schema validation failed' })]),
22
+ });
23
+
24
+ function fromCompiledPlain(mod, schema, options) {
25
+ const opts = options ? { ...options } : undefined;
26
+ // `richErrors: false` is what this wrapper is; anything else it is not.
27
+ if (opts) {
28
+ if (opts.richErrors !== false) throw new TypeError('fromCompiledPlain answers `richErrors: false`; a Validator with rich errors takes fromCompiled');
29
+ delete opts.richErrors;
30
+ }
31
+ const { isValid } = prepare(mod, schema, opts, 'fromCompiledPlain');
32
+ const errFn = mod.validate;
33
+ const api = {
34
+ validate(data) {
35
+ if (isValid(data)) return { valid: true, data, errors: EMPTY_ERRORS };
36
+ const r = errFn(data);
37
+ return r && r.valid === false ? { valid: false, errors: r.errors } : VERDICT_DISAGREES;
38
+ },
39
+ isValidObject(data) {
40
+ return isValid(data);
41
+ },
42
+ };
43
+ installExtension(api, schema, (base, ext) => ({
44
+ validate(data) {
45
+ const r = base.validate(data);
46
+ if (r.valid && ext.check(data)) return r;
47
+ const more = ext.errors(data) || [];
48
+ const all = r.valid ? more : r.errors.concat(more);
49
+ return { valid: false, errors: all.length ? all : [EXT_FALLBACK] };
50
+ },
51
+ isValidObject: (d) => base.isValidObject(d) && ext.check(d),
52
+ }));
53
+ installStandard(api, () => schema, (self, value) => self.validate(value));
54
+ return api;
55
+ }
56
+
57
+ module.exports = { fromCompiledPlain };
package/lib/compiled.js CHANGED
@@ -15,7 +15,8 @@
15
15
  // applies them, before any check, with the same code; `useDefaults: false`
16
16
  // leaves the input as it is, as the runtime then does.
17
17
 
18
- const { LazyRejection, RichRejection, LazyJsonRejection, _enrichLazy } = require('./rejections');
18
+ const { LazyRejection, RichRejection, _enrichLazy } = require('./rejections');
19
+ const { LazyJsonRejection } = require('./json-rejections');
19
20
  const { COMPILED_OPTIONS, prepare, parse, installExtension, extendJSON, EXT_FALLBACK } = require('./compiled-common');
20
21
  const { installStandard } = require('./standard-schema');
21
22
 
@@ -0,0 +1,29 @@
1
+ 'use strict';
2
+
3
+ // Every RFC 4291 text form of an IPv6 address as one expression: eight groups,
4
+ // or a "::" standing for one or more groups of zeros with up to seven groups
5
+ // around it, the last 32 bits optionally a dotted quad. ipv6() in lib/formats.js
6
+ // is the fast check; this is the short one, which an email address literal in a
7
+ // compiled module carries instead of the parser (2.2 KB of every module that
8
+ // checks an email). tests/test_ipv6_expression.js holds the two to the same
9
+ // answer.
10
+ const IPV6_RE = (() => {
11
+ // A dotted quad, as lib/formats.js reads one.
12
+ const v4 = require('./formats').IPV4.source.slice(1, -1);
13
+ const h = '[0-9A-Fa-f]{1,4}';
14
+ const alts = [`(?:${h}:){7}${h}`, `(?:${h}:){6}${v4}`];
15
+ for (let k = 0; k <= 7; k++) {
16
+ const left = k === 0 ? '' : `(?:${h}:){${k - 1}}${h}`;
17
+ alts.push(k === 7 ? `${left}::` : `${left}::(?:(?:${h}:){0,${6 - k}}${h})?`);
18
+ if (k <= 5) alts.push(`${left}::(?:(?:${h}:){0,${5 - k}})?${v4}`);
19
+ }
20
+ return new RegExp(`^(?:${alts.join('|')})$`);
21
+ })();
22
+
23
+ // `_in` is the text between the brackets; the check returns false from the
24
+ // function it is written into when the literal is not an address.
25
+ let _src = null;
26
+ module.exports = () => _src || (_src =
27
+ `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);if(_i6.length>45||!${IPV6_RE}.test(_i6))return false}` +
28
+ `else if(_in.length>15||!${require('./formats').IPV4}.test(_in))return false`);
29
+ module.exports.IPV6_RE = IPV6_RE;
@@ -1,6 +1,10 @@
1
1
  'use strict';
2
2
 
3
- const { CODES, codeFor, fromNative } = require('./error-codes');
3
+ const { CODES, codeFor } = require('./error-codes');
4
+ // Only the native engine writes a numeric code, so its table loads with the
5
+ // first such error (lib/native-codes.js), not with every process.
6
+ let _fromNative = null;
7
+ const fromNative = (ordinal, format) => (_fromNative || (_fromNative = require('./native-codes').fromNative))(ordinal, format);
4
8
  const { suggestFor } = require('./suggestions');
5
9
  const { resolvePointer } = require('./pointer');
6
10
 
@@ -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
- ATA3001: { keyword: 'format', format: 'email', category: 'format', headline: 'value does not match format "email"' },
30
- ATA3002: { keyword: 'format', format: 'date', category: 'format', headline: 'value does not match format "date"' },
31
- ATA3003: { keyword: 'format', format: 'date-time', category: 'format', headline: 'value does not match format "date-time"' },
32
- ATA3004: { keyword: 'format', format: 'time', category: 'format', headline: 'value does not match format "time"' },
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 reports its own `error_code` enum from include/ata.h as an
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, get, all, codeFor, fromNative, NATIVE_KEYWORDS };
92
+ module.exports = { CODES, codeFor };
@@ -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
- `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)}}` +
298
- `else{${ipv4Source('_in', true)}}` +
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 };
package/lib/formats.js CHANGED
@@ -538,4 +538,4 @@ function email(s) {
538
538
  const DURATION_RE = /^P(?:\d+W|(?:\d+Y(?:\d+M(?:\d+D)?)?|\d+M(?:\d+D)?|\d+D)(?:T(?:\d+H(?:\d+M(?:\d+S)?)?|\d+M(?:\d+S)?|\d+S))?|T(?:\d+H(?:\d+M(?:\d+S)?)?|\d+M(?:\d+S)?|\d+S))$/;
539
539
  function duration (s) { return DURATION_RE.test(s); }
540
540
 
541
- module.exports = { IPV4, IPV6_FULL, ipv4Range, URI_FAST, date, ipv4, dateTime, ipv6, hostname, uri, uriReference, uriWalk: _uriWalk, uuid, time, noReserved, jsonPointer, relativeJsonPointer, uriTemplate, email, duration, iri, iriReference, idnEmail, DURATION_RE, URI_TEMPLATE_EXPR };
541
+ module.exports = { IPV4, DURATION_RE, IPV6_FULL, ipv4Range, URI_FAST, date, ipv4, dateTime, ipv6, hostname, uri, uriReference, uriWalk: _uriWalk, uuid, time, noReserved, jsonPointer, relativeJsonPointer, uriTemplate, email, duration, iri, iriReference, idnEmail, DURATION_RE, URI_TEMPLATE_EXPR };