ata-validator 1.61.0 → 1.61.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +34 -7
- package/compiled-bare-rich.d.ts +6 -0
- package/compiled-bare.d.ts +7 -0
- package/compiled-bare.js +5 -0
- package/compiled-bare.mjs +35 -0
- package/compiled-object.d.ts +11 -0
- package/compiled-object.js +6 -0
- package/compiled-object.mjs +3 -0
- package/compiled-plain.d.ts +16 -0
- package/compiled-plain.js +5 -0
- package/compiled-plain.mjs +3 -0
- package/index.aot.js +8 -0
- package/index.aot.mjs +5 -0
- package/lib/aot-build.js +9 -1
- package/lib/aot-impl.js +10 -2
- package/lib/aot-mark.js +7 -0
- package/lib/compiled-bare-rich.js +54 -0
- package/lib/compiled-bare.js +44 -0
- package/lib/compiled-common.js +17 -2
- package/lib/compiled-defaults.browser.js +9 -0
- package/lib/compiled-defaults.js +8 -0
- package/lib/compiled-object.js +123 -0
- package/lib/compiled-plain.js +57 -0
- package/lib/compiled.js +2 -1
- package/lib/email-literal.js +29 -0
- package/lib/enrich-error.js +5 -1
- package/lib/error-codes.js +6 -76
- package/lib/formats-source.js +20 -3
- package/lib/formats.js +28 -1
- package/lib/interpreter-core.js +4 -28
- package/lib/js-compiler-errors.js +4 -0
- package/lib/json-rejections.js +159 -0
- package/lib/native-codes.js +67 -0
- package/lib/native-message.js +8 -0
- package/lib/native.browser.js +11 -0
- package/lib/rejections.js +13 -152
- package/lib/shape-classifier.js +6 -1
- package/lib/standard-schema.js +1 -1
- package/lib/tier0.js +17 -4
- package/lib/validator-core.js +50 -19
- package/lib/verify.js +68 -3
- package/lib/version.js +1 -1
- package/package.json +54 -10
package/README.md
CHANGED
|
@@ -54,7 +54,14 @@ resolves to an entry without the code generator through the `workerd` and `edge-
|
|
|
54
54
|
conditions: the same names and answers, and a Worker bundled by wrangler goes from 179.1 to
|
|
55
55
|
70.4 KiB gzipped.
|
|
56
56
|
|
|
57
|
-
In a browser
|
|
57
|
+
In a browser, compile with the bundler plugin. [`@ata-project/unplugin`](https://github.com/ata-core/unplugin-ata)
|
|
58
|
+
turns each `new Validator(schema)` whose schema it can read at build time into a compiled
|
|
59
|
+
validator that gives the same answers, and the runtime stays out of the page. A form with two
|
|
60
|
+
validators, one reading errors and one asking only for a verdict, bundles to 10.7 KB gzipped,
|
|
61
|
+
or 3.1 KB with `richErrors: false` on the one that reads errors, against 53.2 KB for the same
|
|
62
|
+
code on the runtime (esbuild, minified, the runtime with code splitting).
|
|
63
|
+
|
|
64
|
+
Without the plugin, the code generator is still not part of the page. Every schema starts on the
|
|
58
65
|
interpreted engine, which gives the same answers, and a validator called a thousand times within
|
|
59
66
|
a second loads the generator in the background, through a dynamic import that Vite, webpack and
|
|
60
67
|
Rollup put in a chunk of their own, and is rebuilt on generated code when the page is idle. A
|
|
@@ -649,7 +656,24 @@ checks takes 1.12 ms where the runtime takes 6.63 ms (median of 15 fresh process
|
|
|
649
656
|
SchemaStore's 977 schemas, 725 can be compiled away; the rest stay on the runtime. Where the code only calls `isValidObject()` or `isValidJSON()`, the plugin uses
|
|
650
657
|
`fromCompiledVerdict()` from `ata-validator/compiled-verdict` instead, which carries no error pipeline: a
|
|
651
658
|
small app that only asks for a boolean bundles to 2.6 KB gzipped, against 12.1 KB with the full wrapper, on
|
|
652
|
-
ata 1.40.0 with `@ata-project/unplugin` 0.5.0.
|
|
659
|
+
ata 1.40.0 with `@ata-project/unplugin` 0.5.0. Where it reads errors but never validates JSON text, the plugin
|
|
660
|
+
uses `fromCompiledObject()` from `ata-validator/compiled-object`, the full wrapper without
|
|
661
|
+
`validateJSON()` and `isValidJSON()` and the position map behind them; and the wrappers fill
|
|
662
|
+
defaults with the compiled module's own `applyDefaults`. A form app with one validator that reads
|
|
663
|
+
errors and one that asks for a boolean went from 16.7 to 13.9 KB gzipped (esbuild, minified),
|
|
664
|
+
ata 1.61.0 against master with `@ata-project/unplugin` 0.11.0 and the change. The wrappers on
|
|
665
|
+
their own: full 13.8 to 12.8 KB, without JSON entry points 10.9, verdict only 2.7 to 1.6.
|
|
666
|
+
A validator built with `{ richErrors: false }` is compiled away as well: the module writes its
|
|
667
|
+
errors in that plain shape (`compiledModuleFor(schema, { plainErrors: true })`) and
|
|
668
|
+
`fromCompiledPlain()` from `ata-validator/compiled-plain` hands them out as they are, with no
|
|
669
|
+
error pipeline in the bundle, 2.6 KB gzipped for the wrapper. Where the plugin sees a
|
|
670
|
+
validator only ever called (`v.validate(x)`, `v.isValidObject(x)`), never handed to other code,
|
|
671
|
+
it uses the bare wrappers of `ata-validator/compiled-bare` instead, which leave out Standard
|
|
672
|
+
Schema and the extension hook nothing can reach there: 0.3 KB gzipped for both, and
|
|
673
|
+
`ata-validator/compiled-bare-rich` for one that reads ata's own errors. The same form app
|
|
674
|
+
bundles to 10.7 KB gzipped, or 3.1 KB with `richErrors: false` on the validator that reads errors. The errors
|
|
675
|
+
then carry `keyword`, `instancePath`, `schemaPath`, `params` and `message`, as that option has
|
|
676
|
+
always given them, without `received`, `suggestion` or `docUrl`.
|
|
653
677
|
|
|
654
678
|
Programmatic API if you prefer to script it:
|
|
655
679
|
|
|
@@ -693,7 +717,8 @@ npx ata verify 'schemas/*.json' --samples test/fixtures
|
|
|
693
717
|
```
|
|
694
718
|
|
|
695
719
|
For each schema it loads the module `ata build` wrote beside it, makes
|
|
696
|
-
documents from the schema (200 by default, half of them with one rule broken
|
|
720
|
+
documents from the schema (200 by default, half of them with one rule broken,
|
|
721
|
+
then a document on either side of each bound the schema states),
|
|
697
722
|
reads the project's sample documents (JSON files, JSON Lines, or a directory
|
|
698
723
|
of them), and has both the module and a `Validator` built from the schema now
|
|
699
724
|
answer every one. Verdicts and error lists have to agree; a module whose
|
|
@@ -723,10 +748,12 @@ So, in order of preference:
|
|
|
723
748
|
module by hand. The module is plain JavaScript and compiles with the
|
|
724
749
|
program: 24 ns, the same errors as the runtime.
|
|
725
750
|
2. Where the runtime `Validator` stays, let it take the interpreted engine.
|
|
726
|
-
It does so by itself
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
751
|
+
It does so by itself under `bun build --compile --aot` and `bun run --aot`,
|
|
752
|
+
where Bun resolves the package's `"aot"` export condition to an entry that
|
|
753
|
+
says so; also when the executable is built with
|
|
754
|
+
`--disallow-code-generation-from-strings` (the program then counts as
|
|
755
|
+
closed for the compiler, too), or when `ATA_ENGINE=interpreter` is set:
|
|
756
|
+
78 ns against 487 for the generated code there.
|
|
730
757
|
3. Write the module for the compiler. `ata build schema.json --target bun`
|
|
731
758
|
writes it with a closed object's keys read once and compared with the
|
|
732
759
|
declared list in order (on a ten-item order with 1024 distinct documents,
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// ata-validator/compiled-bare-rich: the compiled-bare wrapper for a validator
|
|
2
|
+
// a bundler plugin sees only called, that reads ata's own errors.
|
|
3
|
+
import type { ValidationResult } from './index.js';
|
|
4
|
+
import type { CompiledModule } from './compiled.js';
|
|
5
|
+
|
|
6
|
+
export function bareRich<T = unknown>(mod: CompiledModule, schema: object | boolean, useDefaults?: boolean): { validate(data: unknown): ValidationResult<T>; isValidObject(data: unknown): data is T };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// ata-validator/compiled-bare: the smallest compiled-module wrappers, for a
|
|
2
|
+
// validator a bundler plugin sees used only through direct method calls.
|
|
3
|
+
import type { ValidationResult } from './index.js';
|
|
4
|
+
import type { CompiledModule } from './compiled.js';
|
|
5
|
+
|
|
6
|
+
export function bareVerdict(mod: CompiledModule, useDefaults?: boolean): { isValidObject(data: unknown): boolean };
|
|
7
|
+
export function barePlain<T = unknown>(mod: CompiledModule, useDefaults?: boolean): { validate(data: unknown): ValidationResult<T>; isValidObject(data: unknown): data is T };
|
package/compiled-bare.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// ata-validator/compiled-bare as an ES module of its own rather than a
|
|
2
|
+
// re-export of the CommonJS file: a bundler then has no CommonJS interop to
|
|
3
|
+
// add, which was more than half of the bundled size (662 to 315 bytes
|
|
4
|
+
// gzipped). The same functions as lib/compiled-bare.js;
|
|
5
|
+
// tests/test_compiled_bare.js holds the two to the same answers.
|
|
6
|
+
|
|
7
|
+
const EMPTY_ERRORS = Object.freeze([]);
|
|
8
|
+
const VERDICT_DISAGREES = Object.freeze({
|
|
9
|
+
valid: false,
|
|
10
|
+
errors: Object.freeze([Object.freeze({ keyword: 'validation', instancePath: '', schemaPath: '#', params: Object.freeze({}), message: 'schema validation failed' })]),
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
function verdictOf(mod, useDefaults) {
|
|
14
|
+
const fill = useDefaults !== false && typeof mod.applyDefaults === 'function' ? mod.applyDefaults : null;
|
|
15
|
+
return fill ? (d) => { fill(d); return mod.isValid(d); } : mod.isValid;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function bareVerdict(mod, useDefaults) {
|
|
19
|
+
return { isValidObject: verdictOf(mod, useDefaults) };
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function barePlain(mod, useDefaults) {
|
|
23
|
+
const isValid = verdictOf(mod, useDefaults);
|
|
24
|
+
const errFn = mod.validate;
|
|
25
|
+
return {
|
|
26
|
+
validate(data) {
|
|
27
|
+
if (isValid(data)) return { valid: true, data, errors: EMPTY_ERRORS };
|
|
28
|
+
const r = errFn(data);
|
|
29
|
+
return r && r.valid === false ? { valid: false, errors: r.errors } : VERDICT_DISAGREES;
|
|
30
|
+
},
|
|
31
|
+
isValidObject: isValid,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export default { bareVerdict, barePlain };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// ata-validator/compiled-object: ata-validator/compiled without the JSON text
|
|
2
|
+
// entry points, for code that reads errors but never calls validateJSON().
|
|
3
|
+
import type { ValidationResult } from './index.js';
|
|
4
|
+
import type { CompiledModule, CompiledOptions } from './compiled.js';
|
|
5
|
+
|
|
6
|
+
export interface CompiledObjectValidator<T = unknown> {
|
|
7
|
+
validate(data: unknown): ValidationResult<T>;
|
|
8
|
+
isValidObject(data: unknown): data is T;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function fromCompiledObject<T = unknown>(mod: CompiledModule, schema: object, options?: CompiledOptions): CompiledObjectValidator<T>;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// ata-validator/compiled-plain: the compiled-module wrapper for a Validator
|
|
2
|
+
// built with `richErrors: false`, for a module written with `plainErrors: true`.
|
|
3
|
+
import type { ValidationResult } from './index.js';
|
|
4
|
+
import type { CompiledModule } from './compiled.js';
|
|
5
|
+
|
|
6
|
+
export interface CompiledPlainValidator<T = unknown> {
|
|
7
|
+
validate(data: unknown): ValidationResult<T>;
|
|
8
|
+
isValidObject(data: unknown): data is T;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface CompiledPlainOptions {
|
|
12
|
+
richErrors?: false;
|
|
13
|
+
useDefaults?: boolean;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function fromCompiledPlain<T = unknown>(mod: CompiledModule, schema: object, options?: CompiledPlainOptions): CompiledPlainValidator<T>;
|
package/index.aot.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// ata-validator under Bun's ahead-of-time compiler, where Bun resolves the
|
|
4
|
+
// `"aot"` export condition: the same package, with validators on the
|
|
5
|
+
// interpreted engine by default, which is compiled with the program, rather
|
|
6
|
+
// than on code made at run time, which is not (lib/aot-mark.js).
|
|
7
|
+
require('./lib/aot-mark.js');
|
|
8
|
+
module.exports = require('./index.js');
|
package/index.aot.mjs
ADDED
package/lib/aot-build.js
CHANGED
|
@@ -692,7 +692,15 @@ function compiledModuleFor(schema, opts) {
|
|
|
692
692
|
// The one-pass error function where the caller asks (a plugin writing
|
|
693
693
|
// for a Bun executable): 3.6x cheaper on a rejection, 20% larger gzipped.
|
|
694
694
|
onePass: opts && opts.onePass === true ? true : undefined,
|
|
695
|
-
source: opts && opts.onePass === true ? false : undefined,
|
|
695
|
+
source: opts && (opts.onePass === true || opts.plainErrors === true) ? false : undefined,
|
|
696
|
+
// Errors in the runtime's `richErrors: false` shape, for
|
|
697
|
+
// ata-validator/compiled-plain, which hands them out as they are.
|
|
698
|
+
plainErrors: !!(opts && opts.plainErrors === true),
|
|
699
|
+
// `verdictOnly`: the module for a reader that only ever calls isValid
|
|
700
|
+
// (a bundler plugin's verdict wrappers). Its validate() hands out the
|
|
701
|
+
// abort-early stub, so the error collector, never called there, is not
|
|
702
|
+
// written: 954 to 539 bytes gzipped on a one-field object.
|
|
703
|
+
abortEarly: !!(opts && opts.verdictOnly === true),
|
|
696
704
|
// Errors in the runtime's order, for a reader that does not sort them
|
|
697
705
|
// itself (a form resolver); fromCompiled sorts, so a plugin putting
|
|
698
706
|
// that wrapper around the module leaves it off.
|
package/lib/aot-impl.js
CHANGED
|
@@ -430,7 +430,12 @@ function toStandaloneModule(validator, opts) {
|
|
|
430
430
|
// documents against large schemas asks for it.
|
|
431
431
|
let onePass = null;
|
|
432
432
|
let ordered = false;
|
|
433
|
-
|
|
433
|
+
// `plainErrors`: the errors in the runtime's `richErrors: false` shape, as
|
|
434
|
+
// the one-pass program writes them before codes and links are stamped on.
|
|
435
|
+
// A wrapper that hands them out as they are (ata-validator/compiled-plain)
|
|
436
|
+
// then carries no error pipeline of its own.
|
|
437
|
+
const plainErrors = !!(opts && opts.plainErrors === true);
|
|
438
|
+
const wantOnePass = plainErrors || (opts && opts.onePass === true) ? 'always' : (opts && (opts.onePass === false || opts.noOnePass)) ? 'never' : 'auto';
|
|
434
439
|
// Asked for explicitly, the one-pass function wins over schema-source
|
|
435
440
|
// frames, which `ata compile` writes by default outside production; the
|
|
436
441
|
// caller is told, since it asked for both.
|
|
@@ -445,7 +450,10 @@ function toStandaloneModule(validator, opts) {
|
|
|
445
450
|
}
|
|
446
451
|
if (wantOnePass === 'always' || errLen === 0) onePass = onePassCore(validator, opts);
|
|
447
452
|
}
|
|
448
|
-
if (onePass !== null) {
|
|
453
|
+
if (onePass !== null && plainErrors) {
|
|
454
|
+
errCore = `const _ER = function(e) { this.valid = false; this.errors = e; };\n` +
|
|
455
|
+
`const _vC = (function() {\n${onePass.core}\n})();\n`;
|
|
456
|
+
} else if (onePass !== null) {
|
|
449
457
|
errCore = `function _ataStamp(e) { for (let i = 0; i < e.length; i++) { const x = e[i]; if (x.code === undefined) { const c = x.keyword === 'required' && typeof x.schemaPath === 'string' && x.schemaPath.endsWith('/dependentRequired') ? 'ATA7005' : _ataCode(x.keyword, x.params && x.params.format); if (c !== null) { x.code = c; x.docUrl = 'https://ata-validator.com/e/' + c; } } else if (x.docUrl === undefined) x.docUrl = 'https://ata-validator.com/e/' + x.code; if (x.branchErrors !== undefined) _ataStamp(x.branchErrors); } }\n` +
|
|
450
458
|
`const _ER = function(e) { _ataStamp(e); this.valid = false; this.errors = e; };\n` +
|
|
451
459
|
`${codeTableSource(onePass.keywords, onePass.formats)}const _vC = (function() {\n${onePass.core}\n})();\n`;
|
package/lib/aot-mark.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Loaded first by index.aot.js and index.aot.mjs, the entries Bun resolves
|
|
4
|
+
// through the `"aot"` export condition when it compiles a program ahead of
|
|
5
|
+
// time: every Validator made after this takes the interpreted engine by
|
|
6
|
+
// default (see aotHost in lib/validator-core.js).
|
|
7
|
+
require('./validator-core')._markAotHost();
|
|
@@ -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 };
|
package/lib/compiled-common.js
CHANGED
|
@@ -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
|
-
|
|
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 :
|
|
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,
|
|
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;
|