ata-validator 1.49.0 → 1.50.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.
@@ -0,0 +1,22 @@
1
+ 'use strict';
2
+
3
+ // The code a module stamps on an error, from its keyword (and format): the
4
+ // first code in sorted order that names the keyword, the same rule as codeFor
5
+ // in lib/error-codes.js and the table a module carries (lib/aot-impl.js).
6
+ const { CODES } = require('./error-codes');
7
+
8
+ const byKw = {};
9
+ const byFmt = {};
10
+ for (const code of Object.keys(CODES).sort()) {
11
+ const c = CODES[code];
12
+ if (byKw[c.keyword] === undefined) byKw[c.keyword] = code;
13
+ if (c.keyword === 'format' && c.format && byFmt[c.format] === undefined) byFmt[c.format] = code;
14
+ }
15
+
16
+ function codeOf(keyword, format) {
17
+ if (keyword === 'format' && format) { const h = byFmt[format]; return h === undefined ? 'ATA3099' : h; }
18
+ const h = byKw[keyword];
19
+ return h === undefined ? null : h;
20
+ }
21
+
22
+ module.exports = { codeOf, byKw, byFmt };
@@ -0,0 +1,85 @@
1
+ 'use strict';
2
+
3
+ // The error runtime that compiled modules share. A module built in the
4
+ // compact shape carries only its verdict function and its schema with the
5
+ // annotations taken out; when a document fails, it hands the document to this
6
+ // runtime, which walks the same schema with the interpreted engine and returns
7
+ // the error list. One copy serves every module of a build, so the error detail
8
+ // a module used to carry as generated code is paid for once.
9
+ //
10
+ // What comes back is what `validate()` returns on the runtime Validator with
11
+ // `richErrors: false`, plus the `code` and `docUrl` a module stamps: the same
12
+ // errors, in schema declaration order.
13
+ //
14
+ // It loads the interpreter without its plan compiler, which adds speed and
15
+ // no answers: lib/error-runtime.js packs this file and what it requires into
16
+ // the runtime a build writes, and `ata-validator/error-runtime` exports it
17
+ // for bundler plugins.
18
+
19
+ const { createInterpreter } = require('./interpreter-core');
20
+ const { sortErrorsByRank } = require('./error-sort-rank');
21
+
22
+ const DOC = 'https://ata-validator.com/e/';
23
+
24
+ function stamp(errors, codeOf) {
25
+ const out = new Array(errors.length);
26
+ for (let i = 0; i < errors.length; i++) {
27
+ const e = errors[i];
28
+ // A collapsed oneOf/anyOf error comes with its code and no docUrl, as
29
+ // the module's own collector leaves it; only its branches are stamped.
30
+ if (e.code !== undefined) {
31
+ const x = {};
32
+ for (const k in e) if (k !== '_o' && k !== 'branchErrors') x[k] = e[k];
33
+ if (Array.isArray(e.branchErrors)) x.branchErrors = stamp(e.branchErrors, codeOf);
34
+ out[i] = x;
35
+ continue;
36
+ }
37
+ let code;
38
+ {
39
+ code = e.keyword === 'required' && typeof e.schemaPath === 'string' && e.schemaPath.endsWith('/dependentRequired')
40
+ ? 'ATA7005'
41
+ : codeOf(e.keyword, e.params && e.params.format);
42
+ }
43
+ const x = code === null || code === undefined ? {} : { code };
44
+ for (const k in e) if (k !== 'code' && k !== '_o' && k !== 'docUrl' && k !== 'branchErrors') x[k] = e[k];
45
+ if (code !== null && code !== undefined) x.docUrl = DOC + code;
46
+ if (Array.isArray(e.branchErrors)) x.branchErrors = stamp(e.branchErrors, codeOf);
47
+ out[i] = x;
48
+ }
49
+ return out;
50
+ }
51
+
52
+ // The error list for a document `interp` rejects, as a module reports it, or
53
+ // null when there is no detail to give.
54
+ function errorsOf(interp, schema, codeOf, data) {
55
+ let raw;
56
+ try {
57
+ raw = interp.validate(data).errors;
58
+ } catch (e) {
59
+ // Data nested deeper than the walk's stack allows: no detail, which the
60
+ // module reports as its ATA9000 stub. The verdict came from the module.
61
+ if (e instanceof RangeError) return null;
62
+ throw e;
63
+ }
64
+ if (!raw || raw.length === 0) return null;
65
+ const ordered = raw.length > 1 || (raw[0] && Array.isArray(raw[0].branchErrors))
66
+ ? sortErrorsByRank(schema, raw)
67
+ : raw;
68
+ return stamp(ordered, codeOf);
69
+ }
70
+
71
+ function interpreterOptions(options) {
72
+ return { v1: !!(options && options.v1), flat: !!(options && options.flat) };
73
+ }
74
+
75
+ // schema: the schema the module's verdict was compiled from, after dialect
76
+ // normalization. codeOf(keyword, format): the module's own code table.
77
+ function createErrors(schema, codeOf, options) {
78
+ let interp = null;
79
+ return function errorsFor(data) {
80
+ if (interp === null) interp = createInterpreter(schema, interpreterOptions(options));
81
+ return errorsOf(interp, schema, codeOf, data);
82
+ };
83
+ }
84
+
85
+ module.exports = { createErrors, errorsOf, interpreterOptions };
@@ -0,0 +1,152 @@
1
+ 'use strict';
2
+
3
+ // Source of the error runtime that compact modules import (see
4
+ // lib/error-runtime-entry.js for what it does). It is put together from the
5
+ // library's own files when a build asks for it, so there is no second copy of
6
+ // the interpreter to keep in step: each file becomes a function in a small
7
+ // module table, and require() inside them resolves against that table. The
8
+ // interpreter comes without its plan compiler (lib/interpreter-core.js), which
9
+ // produces the same errors.
10
+ //
11
+ // The output is not minified. It goes through the application's bundler like
12
+ // the modules that import it.
13
+
14
+ const fs = require('fs');
15
+ const path = require('path');
16
+
17
+ const FILES = [
18
+ 'error-runtime-entry',
19
+ 'interpreter-core',
20
+ 'error-sort-rank',
21
+ 'schema-order',
22
+ 'branch-collapse',
23
+ 'safe-regex',
24
+ 'unique-items',
25
+ 'formats',
26
+ ];
27
+ // The parts a build can leave out when none of its schemas needs them. The
28
+ // decision is made on the schemas the runtime walks (as embedded, see
29
+ // runtimeFeatures) and errs toward keeping a part: a key with that name
30
+ // anywhere, even inside an enum value, keeps it. A part left out is replaced
31
+ // by a stub that throws, which a schema without that keyword cannot reach.
32
+ const OPTIONAL = {
33
+ formats: { keys: ['format'], stub: 'module.exports = {};' },
34
+ 'safe-regex': { keys: ['pattern', 'patternProperties'], stub: "module.exports = { compileSafe() { throw new Error('ata error runtime: built without the pattern engine'); } };" },
35
+ 'unique-items': { keys: ['uniqueItems', 'enum', 'const'], stub: "function no() { throw new Error('ata error runtime: built without deep equality'); }\nmodule.exports = { allDistinct: no, duplicatePair: no, _deq: no };" },
36
+ };
37
+
38
+ // Which optional parts these schemas need: { formats, 'safe-regex',
39
+ // 'unique-items' } as booleans.
40
+ function runtimeFeatures(schemas) {
41
+ const keys = new Set();
42
+ const seen = new Set();
43
+ const walk = (n) => {
44
+ if (n === null || typeof n !== 'object' || seen.has(n)) return;
45
+ seen.add(n);
46
+ if (Array.isArray(n)) { for (const x of n) walk(x); return; }
47
+ for (const k of Object.keys(n)) { keys.add(k); walk(n[k]); }
48
+ };
49
+ for (const s of schemas) walk(s);
50
+ const out = {};
51
+ for (const [part, { keys: ks }] of Object.entries(OPTIONAL)) out[part] = ks.some((k) => keys.has(k));
52
+ return out;
53
+ }
54
+
55
+ function codeTable() {
56
+ const { byKw, byFmt } = require('./error-code-of');
57
+ return { byKw, byFmt };
58
+ }
59
+
60
+ // Lines that hold nothing but a // comment are about half of these files.
61
+ // They go, unless the line sits inside a template literal (tracked by
62
+ // counting backticks on the lines before it) or the file is one whose
63
+ // templates carry such lines on purpose (formats.js builds code from them).
64
+ const KEEP_COMMENTS = new Set(['formats']);
65
+ function dropCommentLines(src, name) {
66
+ if (KEEP_COMMENTS.has(name)) return src;
67
+ const out = [];
68
+ let inTemplate = false;
69
+ for (const line of src.split('\n')) {
70
+ if (!inTemplate && /^\s*\/\//.test(line)) continue;
71
+ if (!inTemplate && line.trim() === '') continue;
72
+ out.push(line);
73
+ const ticks = (line.match(/`/g) || []).length;
74
+ if (ticks % 2 === 1) inTemplate = !inTemplate;
75
+ }
76
+ return out.join('\n');
77
+ }
78
+
79
+ const _cache = new Map();
80
+
81
+ // The files a schema-mode runtime adds: the verdict comes from the
82
+ // interpreter too, with its plan compiler for schemas used often.
83
+ const SCHEMA_FILES = ['schema-runtime-entry', 'interpreter', 'plan-compiler'];
84
+
85
+ // features: from runtimeFeatures, or omitted for a runtime with every part.
86
+ // schemaMode: also carry the verdict (createValidator), for schema modules.
87
+ function errorRuntimeSource(format, version, features, schemaMode) {
88
+ const want = (part) => !features || features[part] !== false;
89
+ const key = (format === 'cjs' ? 'cjs' : 'esm') + ':' + Object.keys(OPTIONAL).map((p) => (want(p) ? '1' : '0')).join('') + (schemaMode ? ':schema' : '');
90
+ const hit = _cache.get(key);
91
+ if (hit !== undefined) return hit;
92
+ const defs = [];
93
+ for (const name of schemaMode ? FILES.concat(SCHEMA_FILES) : FILES) {
94
+ const src = OPTIONAL[name] !== undefined && !want(name)
95
+ ? OPTIONAL[name].stub
96
+ : dropCommentLines(fs.readFileSync(path.join(__dirname, name + '.js'), 'utf8'), name);
97
+ defs.push(`${JSON.stringify(name)}: function (module, exports, require) {\n${src}\n}`);
98
+ }
99
+ const { byKw, byFmt } = codeTable();
100
+ const body = `// Auto-generated by ata-validator ${version}, do not edit.
101
+ // The error runtime shared by the compact modules of one build. It imports
102
+ // nothing. Re-run the build after upgrading ata.
103
+ 'use strict';
104
+ const _ataDefs = {
105
+ ${defs.join(',\n')}
106
+ };
107
+ const _ataCache = {};
108
+ function _ataRequire(spec) {
109
+ const name = spec.replace(/^\\.\\//, '').replace(/\\.js$/, '');
110
+ const hit = _ataCache[name];
111
+ if (hit !== undefined) return hit.exports;
112
+ const def = _ataDefs[name];
113
+ if (def === undefined) throw new Error('ata error runtime: missing module ' + spec);
114
+ const module = { exports: {} };
115
+ _ataCache[name] = module;
116
+ def(module, module.exports, _ataRequire);
117
+ return module.exports;
118
+ }
119
+ const _ataKw = ${JSON.stringify(byKw)};
120
+ const _ataFmt = ${JSON.stringify(byFmt)};
121
+ function _ataCode(k, f) {
122
+ if (k === 'format' && f) { const h = _ataFmt[f]; return h === undefined ? 'ATA3099' : h; }
123
+ const h = _ataKw[k];
124
+ return h === undefined ? null : h;
125
+ }
126
+ // The modules are evaluated on the first call, not when this file loads.
127
+ function createErrors(schema, options) { return _ataRequire('error-runtime-entry').createErrors(schema, _ataCode, options); }
128
+ function compileSafe(src) { return _ataRequire('safe-regex').compileSafe(src); }
129
+ ${schemaMode ? "function createValidator(schema, options) { return _ataRequire('schema-runtime-entry').createValidator(schema, _ataCode, options); }\n" : ''}
130
+ const ataVersion = ${JSON.stringify(version)};
131
+
132
+ // Warm-up, in a browser only: the runtime's code compiles on its first use,
133
+ // which a page would otherwise pay for on its first rejected document, about
134
+ // a millisecond. When the browser is idle, one small error list is built here
135
+ // instead, once, so the first real one finds the code compiled. Nothing it
136
+ // does reaches a caller.
137
+ const WARM_SCHEMA = { type: 'object', properties: { a: { type: 'string' }, b: { type: 'integer', minimum: 0 }, c: { anyOf: [{ type: 'string' }, { type: 'null' }] }, f: { type: 'string', format: 'email' } }, required: ['d'], additionalProperties: false };
138
+ function warm() { try { createErrors(WARM_SCHEMA, { flat: true })({ a: 1, b: -1, c: 2, f: 'x', e: 0 }); } catch (_) {} }
139
+ if (typeof requestIdleCallback === 'function') { try { requestIdleCallback(warm); } catch (_) {} }
140
+ // In Node the same happens once the event loop is first free after start-up,
141
+ // on a timer that does not keep the process alive: a script that exits at
142
+ // once never runs it, and a server has it done before its first request.
143
+ else if (typeof process === 'object' && process !== null && process.versions && process.versions.node && typeof setTimeout === 'function') { try { const t = setTimeout(warm, 0); if (t && typeof t.unref === 'function') t.unref(); } catch (_) {} }
144
+ `;
145
+ const out = format === 'cjs'
146
+ ? body + `module.exports = { createErrors, compileSafe, ${schemaMode ? 'createValidator, ' : ''}ataVersion };\n`
147
+ : body + `export { createErrors, compileSafe, ${schemaMode ? 'createValidator, ' : ''}ataVersion };\n`;
148
+ _cache.set(key, out);
149
+ return out;
150
+ }
151
+
152
+ module.exports = { errorRuntimeSource, runtimeFeatures, codeTable };
@@ -0,0 +1,61 @@
1
+ 'use strict';
2
+
3
+ // Declaration-order sorting for the shared error runtime
4
+ // (lib/error-runtime-entry.js), in its own file so the runtime Validator and
5
+ // ata-validator/lite, which sort by ordinals (lib/error-sort.js), do not carry
6
+ // it.
7
+ const { rankFor: schemaRank } = require('./schema-order');
8
+
9
+ // The same order from ranks alone: a rank is the path of child indexes from
10
+ // the root to the node an error names, built by walking that one path, while
11
+ // the ordinals above number every node of the schema on the first sort. The
12
+ // shared error runtime sorts this way, so a rejection costs a walk of the
13
+ // paths it reports, not of the schema. Comparing ranks segment by segment is
14
+ // the pre-order relation the ordinals encode (see lib/schema-order.js), and a
15
+ // path behind a local `$ref` ranks inside its target as the ordinal's tie
16
+ // does; the runtime only takes schemas whose references stay inside them. An
17
+ // error with no place in the document keeps the place of the one before it.
18
+ function compareRanks(a, b) {
19
+ const m = Math.min(a.length, b.length);
20
+ for (let k = 0; k < m; k++) if (a[k] !== b[k]) return a[k] - b[k];
21
+ return a.length - b.length;
22
+ }
23
+
24
+ function sortErrorsByRank(rootSchema, errors) {
25
+ let out = null;
26
+ for (let i = 0; i < errors.length; i++) {
27
+ const e = errors[i];
28
+ const b = e !== null && typeof e === 'object' ? e.branchErrors : undefined;
29
+ if (!Array.isArray(b) || b.length === 0) continue;
30
+ const sb = sortErrorsByRank(rootSchema, b);
31
+ if (sb === b) continue;
32
+ if (out === null) out = errors.slice();
33
+ out[i] = Object.assign({}, e, { branchErrors: sb });
34
+ }
35
+ if (out !== null) errors = out;
36
+ const n = errors.length;
37
+ if (n < 2) return errors;
38
+ const keys = new Array(n);
39
+ let prev = [];
40
+ let sorted = true;
41
+ for (let i = 0; i < n; i++) {
42
+ let r = schemaRank(rootSchema, errors[i].schemaPath);
43
+ if (r === null) r = prev;
44
+ keys[i] = r;
45
+ if (i > 0 && compareRanks(prev, r) > 0) sorted = false;
46
+ prev = r;
47
+ }
48
+ if (sorted) return errors;
49
+ const res = errors.slice();
50
+ for (let i = 1; i < n; i++) {
51
+ const k = keys[i];
52
+ const e = res[i];
53
+ let j = i - 1;
54
+ while (j >= 0 && compareRanks(keys[j], k) > 0) { keys[j + 1] = keys[j]; res[j + 1] = res[j]; j--; }
55
+ keys[j + 1] = k;
56
+ res[j + 1] = e;
57
+ }
58
+ return res;
59
+ }
60
+
61
+ module.exports = { sortErrorsByRank };