ata-validator 1.34.0 → 1.35.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 +16 -15
- package/compiled.d.ts +17 -0
- package/compiled.js +7 -0
- package/compiled.mjs +3 -0
- package/index.browser.mjs +3 -2
- package/index.js +606 -2991
- package/index.mjs +1 -1
- package/lib/aot-build.js +40 -0
- package/lib/buffer-gate.js +0 -1
- package/lib/compiled.js +84 -0
- package/lib/enrich-error.js +7 -0
- package/lib/formats-source.js +323 -0
- package/lib/formats.js +1 -317
- package/lib/interpreter.js +4 -4
- package/lib/js-compiler.js +5 -5
- package/lib/rejections.js +309 -0
- package/lib/validator-core.js +2052 -0
- package/lib/version.js +1 -1
- package/lib/vocabularies.js +1 -2
- package/package.json +17 -9
package/index.mjs
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import mod from './index.js';
|
|
2
|
-
export const { Validator, validate, validateAsync, parseAsync, version, createPaddedBuffer, SIMDJSON_PADDING, defineSchema, renderPretty, renderCompact, renderJSON } = mod;
|
|
2
|
+
export const { Validator, compile, validate, validateAsync, parseAsync, version, createPaddedBuffer, SIMDJSON_PADDING, parseJSON, toTypeScript, defineSchema, renderPretty, renderCompact, toOutput, toRetryMessage, describeSchema, renderJSON } = mod;
|
|
3
3
|
export default mod;
|
package/lib/aot-build.js
CHANGED
|
@@ -288,7 +288,47 @@ function toStandaloneModule(schema, opts) {
|
|
|
288
288
|
|
|
289
289
|
const { schemaHash } = require('./schema-hash');
|
|
290
290
|
|
|
291
|
+
// Whether `new Validator(schema)` with default options can be replaced by
|
|
292
|
+
// fromCompiled() around the module toStandaloneModule(schema) writes, which a
|
|
293
|
+
// bundler plugin does so the runtime compiler stays out of the bundle. The
|
|
294
|
+
// wrapper reproduces a default Validator's results exactly for these schemas;
|
|
295
|
+
// tests/test_compiled_parity.js holds that. Declined: anything that rewrites
|
|
296
|
+
// its input before the check, since `useDefaults` is on by default and the
|
|
297
|
+
// module applies no defaults, and custom error messages, which the core
|
|
298
|
+
// applies in a layer the wrapper does not carry. A caller must also get a
|
|
299
|
+
// module back from toStandaloneModule, which returns null for a schema it
|
|
300
|
+
// cannot compile. The text checks are deliberately coarse: a property named
|
|
301
|
+
// `default` declines too, and declining costs only the saving.
|
|
302
|
+
function compiledEligible(schema) {
|
|
303
|
+
if (typeof schema !== 'object' || schema === null || Array.isArray(schema)) return false;
|
|
304
|
+
const text = JSON.stringify(schema);
|
|
305
|
+
return !text.includes('"default"') && !text.includes('"errorMessage"');
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// The module that replaces `new Validator(schema)`, or null where the
|
|
309
|
+
// replacement would not answer as the runtime does: a schema
|
|
310
|
+
// compiledEligible() declines, one the emitter cannot compile, and one whose
|
|
311
|
+
// error function the generator declines (unevaluated* among them), where the
|
|
312
|
+
// module would report every failure as the ATA9000 stub while the runtime
|
|
313
|
+
// reports the real errors.
|
|
314
|
+
function compiledModuleFor(schema, opts) {
|
|
315
|
+
if (!compiledEligible(schema)) return null;
|
|
316
|
+
let degraded = false;
|
|
317
|
+
let src;
|
|
318
|
+
try {
|
|
319
|
+
src = toStandaloneModule(schema, {
|
|
320
|
+
format: (opts && opts.format) || 'esm',
|
|
321
|
+
onWarning: (msg, info) => { if (info && info.kind === 'error-detail') degraded = true; },
|
|
322
|
+
});
|
|
323
|
+
} catch {
|
|
324
|
+
return null;
|
|
325
|
+
}
|
|
326
|
+
return src && !degraded ? src : null;
|
|
327
|
+
}
|
|
328
|
+
|
|
291
329
|
module.exports = {
|
|
330
|
+
compiledEligible,
|
|
331
|
+
compiledModuleFor,
|
|
292
332
|
build,
|
|
293
333
|
expandGlobs,
|
|
294
334
|
parseSchemaFile,
|
package/lib/buffer-gate.js
CHANGED
|
@@ -106,7 +106,6 @@ function installSlowBufferApis(validator) {
|
|
|
106
106
|
return validator.validate(value).valid;
|
|
107
107
|
};
|
|
108
108
|
validator.isValid = (input) => isValidText(toText(input, 'isValid'));
|
|
109
|
-
validator.isValidJSON = (jsonStr) => isValidText(jsonStr);
|
|
110
109
|
validator.isValidPrepadded = (paddedBuffer, jsonLength) =>
|
|
111
110
|
isValidText(Buffer.from(paddedBuffer.buffer, paddedBuffer.byteOffset, jsonLength).toString('utf8'));
|
|
112
111
|
const ndjson = (input, name) => {
|
package/lib/compiled.js
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// A Validator-shaped object around a module the code generator wrote ahead of
|
|
4
|
+
// time: what a bundler plugin puts in place of `new Validator(schema)` when the
|
|
5
|
+
// schema is known at build time, so the runtime compiler does not ship. It
|
|
6
|
+
// answers validate(), isValidObject(), validateJSON() and isValidJSON() the way
|
|
7
|
+
// a Validator built with default options answers them, down to the error
|
|
8
|
+
// objects, which come from the same rejection classes the validator core uses.
|
|
9
|
+
// tests/test_compiled_parity.js holds that over the official suite.
|
|
10
|
+
//
|
|
11
|
+
// Only for a schema compiledEligible() accepts. Anything that rewrites its input
|
|
12
|
+
// (defaults, coercion, removal), custom error messages, and every option other
|
|
13
|
+
// than the defaults stay on the runtime.
|
|
14
|
+
|
|
15
|
+
const { LazyRejection, RichRejection, LazyJsonRejection, _enrichLazy } = require('./rejections');
|
|
16
|
+
|
|
17
|
+
const VALID_RESULT = Object.freeze({ valid: true, errors: Object.freeze([]) });
|
|
18
|
+
const EMPTY_ERRORS = Object.freeze([]);
|
|
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 syntaxRejection(e) {
|
|
25
|
+
return { valid: false, errors: [{ keyword: '__parse__', instancePath: '', schemaPath: '', params: {}, message: 'invalid JSON: ' + e.message }] };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// The fields of a Validator the rejection classes read.
|
|
29
|
+
class CompiledState {
|
|
30
|
+
constructor(schema) {
|
|
31
|
+
this._schemaObj = schema;
|
|
32
|
+
this._schemaPositions = undefined;
|
|
33
|
+
this._source = undefined;
|
|
34
|
+
this._mutatesInput = false;
|
|
35
|
+
this._preprocess = null;
|
|
36
|
+
this._posCache = null;
|
|
37
|
+
}
|
|
38
|
+
_pos() {
|
|
39
|
+
return this._posCache || (this._posCache = require('./data-position-cache').createCache());
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function fromCompiled(mod, schema) {
|
|
44
|
+
const isValid = mod.isValid;
|
|
45
|
+
const errFn = mod.validate;
|
|
46
|
+
const self = new CompiledState(schema);
|
|
47
|
+
// The generated function's own result, rejected: if it says valid where the
|
|
48
|
+
// verdict said no, the disagreement is reported rather than turned into an
|
|
49
|
+
// acceptance, as the core does.
|
|
50
|
+
const raw = (data) => {
|
|
51
|
+
const r = errFn(data);
|
|
52
|
+
return r && r.valid === false ? r : VERDICT_DISAGREES;
|
|
53
|
+
};
|
|
54
|
+
const rich = (data) => new RichRejection(raw(data), data, null, self, schema, _enrichLazy);
|
|
55
|
+
const buildErrors = (data) => rich(data).errors;
|
|
56
|
+
const buildRawErrors = (data) => rich(data)._ataRaw();
|
|
57
|
+
const parse = (text) => {
|
|
58
|
+
try { return { value: JSON.parse(text) }; } catch (e) {
|
|
59
|
+
if (!(e instanceof SyntaxError)) throw e;
|
|
60
|
+
return { error: e };
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
return {
|
|
64
|
+
validate(data) {
|
|
65
|
+
if (isValid(data)) return { valid: true, data, errors: EMPTY_ERRORS };
|
|
66
|
+
return new LazyRejection(buildErrors, data, buildRawErrors);
|
|
67
|
+
},
|
|
68
|
+
isValidObject(data) {
|
|
69
|
+
return isValid(data);
|
|
70
|
+
},
|
|
71
|
+
validateJSON(text) {
|
|
72
|
+
const p = parse(text);
|
|
73
|
+
if (p.error) return new LazyJsonRejection(syntaxRejection(p.error), text, self, _enrichLazy);
|
|
74
|
+
if (isValid(p.value)) return VALID_RESULT;
|
|
75
|
+
return new LazyJsonRejection(raw(p.value), text, self, _enrichLazy);
|
|
76
|
+
},
|
|
77
|
+
isValidJSON(text) {
|
|
78
|
+
const p = parse(text);
|
|
79
|
+
return p.error ? false : isValid(p.value);
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
module.exports = { fromCompiled };
|
package/lib/enrich-error.js
CHANGED
|
@@ -194,8 +194,15 @@ function enrich (rawErr, opts) {
|
|
|
194
194
|
// Prefer a code the codegen already attached (e.g. branch-collapse emits
|
|
195
195
|
// ATA4001/4002/4003 distinguishing zero/multi/anyOf failure modes). The
|
|
196
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');
|
|
197
203
|
const code = (fromAddon && fromAddon.code) ||
|
|
198
204
|
(typeof rawErr.code === 'string' && rawErr.code) ||
|
|
205
|
+
(dependent && 'ATA7005') ||
|
|
199
206
|
codeFor(keyword, format) ||
|
|
200
207
|
'ATA9001';
|
|
201
208
|
const meta = CODES[code];
|
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// The source emitters for the formats: each returns JavaScript source that
|
|
4
|
+
// checks a string in place, which the code generator inlines into the
|
|
5
|
+
// function it builds. Split from lib/formats.js, which keeps the runtime
|
|
6
|
+
// checks the interpreted engine calls, so a bundle without the code generator
|
|
7
|
+
// (ata-validator/lite) does not carry them.
|
|
8
|
+
|
|
9
|
+
const { DURATION_RE, URI_TEMPLATE_EXPR, URI_FAST } = require('./formats');
|
|
10
|
+
|
|
11
|
+
const uriCharsSource = (v, from) =>
|
|
12
|
+
`for(let _ri=${from};_ri<${v}.length;_ri++){const _rc=${v}.charCodeAt(_ri);` +
|
|
13
|
+
'if(_rc<33||_rc>126||_rc===34||_rc===60||_rc===62||_rc===92||_rc===94||_rc===96||_rc===123||_rc===124||_rc===125)return false;' +
|
|
14
|
+
`if(_rc===37){const _h1=${v}.charCodeAt(_ri+1),_h2=${v}.charCodeAt(_ri+2);` +
|
|
15
|
+
'if(!((_h1>=48&&_h1<=57)||(_h1>=97&&_h1<=102)||(_h1>=65&&_h1<=70))||' +
|
|
16
|
+
'!((_h2>=48&&_h2<=57)||(_h2>=97&&_h2<=102)||(_h2>=65&&_h2<=70)))return false;_ri+=2}}';
|
|
17
|
+
const uriAuthoritySource = (v, start) =>
|
|
18
|
+
`if(${v}.charCodeAt(${start})===47&&${v}.charCodeAt(${start}+1)===47){` +
|
|
19
|
+
`let _ae=${v}.length;` +
|
|
20
|
+
`for(let _ai=${start}+2;_ai<${v}.length;_ai++){const _ac=${v}.charCodeAt(_ai);` +
|
|
21
|
+
'if(_ac===47||_ac===63||_ac===35){_ae=_ai;break}}' +
|
|
22
|
+
`const _au=${v}.slice(${start}+2,_ae);const _aat=_au.lastIndexOf('@');` +
|
|
23
|
+
'if(_aat!==-1&&/[[\\]]/.test(_au.slice(0,_aat)))return false;' +
|
|
24
|
+
'const _hp=_aat===-1?_au:_au.slice(_aat+1);' +
|
|
25
|
+
'if(_hp.charCodeAt(0)===91){const _cl=_hp.indexOf("]");' +
|
|
26
|
+
'if(_cl===-1)return false;const _rs=_hp.slice(_cl+1);' +
|
|
27
|
+
'if(_rs!==""){if(_rs.charCodeAt(0)!==58||!/^[0-9]*$/.test(_rs.slice(1)))return false}}' +
|
|
28
|
+
'else{const _co=_hp.lastIndexOf(":");' +
|
|
29
|
+
'if(_co!==-1){if(_hp.indexOf(":")!==_co)return false;' +
|
|
30
|
+
'if(!/^[0-9]*$/.test(_hp.slice(_co+1)))return false}}}';
|
|
31
|
+
const iriCharsSource = (v, from) =>
|
|
32
|
+
`for(let _ii=${from};_ii<${v}.length;_ii++){const _ic=${v}.charCodeAt(_ii);` +
|
|
33
|
+
'if(_ic===127||(_ic<127&&(_ic<33||_ic===34||_ic===60||_ic===62||_ic===92||_ic===94||_ic===96||_ic===123||_ic===124||_ic===125)))return false;' +
|
|
34
|
+
`if(_ic===37){const _j1=${v}.charCodeAt(_ii+1),_j2=${v}.charCodeAt(_ii+2);` +
|
|
35
|
+
'if(!((_j1>=48&&_j1<=57)||(_j1>=97&&_j1<=102)||(_j1>=65&&_j1<=70))||' +
|
|
36
|
+
'!((_j2>=48&&_j2<=57)||(_j2>=97&&_j2<=102)||(_j2>=65&&_j2<=70)))return false;_ii+=2}}';
|
|
37
|
+
function iriSource (v, isStr) {
|
|
38
|
+
return guard(v, isStr, `const _n=${v}.length;if(_n===0)return false;` +
|
|
39
|
+
`const _f=${v}.charCodeAt(0);if(!((_f>=97&&_f<=122)||(_f>=65&&_f<=90)))return false;` +
|
|
40
|
+
`let _co=-1;for(let _i=1;_i<_n;_i++){const _c=${v}.charCodeAt(_i);if(_c===58){_co=_i;break}` +
|
|
41
|
+
`if(!((_c>=48&&_c<=57)||(_c>=97&&_c<=122)||(_c>=65&&_c<=90)||_c===43||_c===45||_c===46))return false}` +
|
|
42
|
+
`if(_co===-1)return false;` +
|
|
43
|
+
iriCharsSource(v, '_co+1') +
|
|
44
|
+
uriAuthoritySource(v, '_co+1'));
|
|
45
|
+
}
|
|
46
|
+
function iriReferenceSource (v, isStr) { return guard(v, isStr, iriCharsSource(v, '0')); }
|
|
47
|
+
function idnEmailSource (v, isStr) {
|
|
48
|
+
const inner =
|
|
49
|
+
`const _at=${v}.lastIndexOf('@');` +
|
|
50
|
+
`if(_at<=0||_at===${v}.length-1)return false;` +
|
|
51
|
+
`const _lp=${v}.slice(0,_at),_dm=${v}.slice(_at+1);` +
|
|
52
|
+
`if(_lp.charCodeAt(0)===34){if(_lp.length<2||_lp.charCodeAt(_lp.length-1)!==34||_dm.length===0)return false}` +
|
|
53
|
+
`else{` +
|
|
54
|
+
`if(_lp.charCodeAt(0)===46||_lp.charCodeAt(_lp.length-1)===46)return false;` +
|
|
55
|
+
`if(_lp.indexOf('..')!==-1)return false;` +
|
|
56
|
+
`if(_dm.charCodeAt(0)===91){if(_dm.charCodeAt(_dm.length-1)!==93||_dm.length<=2)return false}` +
|
|
57
|
+
`else{` +
|
|
58
|
+
`if(_dm.charCodeAt(0)===46||_dm.charCodeAt(_dm.length-1)===46)return false;` +
|
|
59
|
+
`if(_dm.indexOf('..')!==-1)return false;` +
|
|
60
|
+
`for(let _di=0;_di<_dm.length;_di++){const _dc=_dm.charCodeAt(_di);` +
|
|
61
|
+
`if(_dc<=32||_dc===127||_dc===64)return false}` +
|
|
62
|
+
`}` +
|
|
63
|
+
`}`;
|
|
64
|
+
return isStr ? `{${inner}}` : `if(typeof ${v}==='string'){${inner}}`;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Source twin of noReserved, same two tiers. `from` is an expression for the
|
|
68
|
+
// first index the loop reads; the variable names stay clear of the ones the
|
|
69
|
+
// other format emitters use.
|
|
70
|
+
function noReservedSource (v, from) {
|
|
71
|
+
return `if(/[^\\u0021-\\u007e]/.test(${v})){` +
|
|
72
|
+
`for(let _ri=${from};_ri<${v}.length;_ri++){const _rc=${v}.charCodeAt(_ri);` +
|
|
73
|
+
'if(_rc>32&&_rc<127)continue;' +
|
|
74
|
+
'if(_rc<=32||_rc===127)return false;' +
|
|
75
|
+
'if(_rc===160||_rc===5760||(_rc>=8192&&_rc<=8202)||_rc===8232||' +
|
|
76
|
+
'_rc===8233||_rc===8239||_rc===8287||_rc===12288||_rc===65279)return false}}';
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Source twins. `v` is the expression holding the string; when `isStr` is
|
|
80
|
+
// false the check is wrapped in a typeof guard, matching FORMAT_CODEGEN's
|
|
81
|
+
// convention. Each returns a statement that `return false`s on mismatch.
|
|
82
|
+
function guard (v, isStr, body) { return isStr ? `{${body}}` : `if(typeof ${v}==='string'){${body}}`; }
|
|
83
|
+
|
|
84
|
+
function dateSource (v, isStr) {
|
|
85
|
+
return guard(v, isStr, `if(${v}.length!==10)return false;for(let _i=0;_i<10;_i++){const _c=${v}.charCodeAt(_i);if(_i===4||_i===7){if(_c!==45)return false}else if(_c<48||_c>57)return false}const _m=(${v}.charCodeAt(5)-48)*10+(${v}.charCodeAt(6)-48),_d=(${v}.charCodeAt(8)-48)*10+(${v}.charCodeAt(9)-48);if(_m<1||_m>12||_d<1)return false;const _y=(${v}.charCodeAt(0)-48)*1000+(${v}.charCodeAt(1)-48)*100+(${v}.charCodeAt(2)-48)*10+(${v}.charCodeAt(3)-48);const _dim=_m===2?(((_y%4===0&&_y%100!==0)||_y%400===0)?29:28):((_m===4||_m===6||_m===9||_m===11)?30:31);if(_d>_dim)return false`);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function dateTimeSource (v, isStr) {
|
|
89
|
+
return guard(v, isStr, `const _n=${v}.length;if(_n<20)return false;` +
|
|
90
|
+
`if(${v}.charCodeAt(4)!==45||${v}.charCodeAt(7)!==45)return false;` +
|
|
91
|
+
`const _sep=${v}.charCodeAt(10);if(_sep!==84&&_sep!==116)return false;` +
|
|
92
|
+
`if(${v}.charCodeAt(13)!==58||${v}.charCodeAt(16)!==58)return false;` +
|
|
93
|
+
`const _y0=${v}.charCodeAt(0)-48,_y1=${v}.charCodeAt(1)-48,_y2=${v}.charCodeAt(2)-48,_y3=${v}.charCodeAt(3)-48;` +
|
|
94
|
+
'if((_y0>>>0)>9||(_y1>>>0)>9||(_y2>>>0)>9||(_y3>>>0)>9)return false;' +
|
|
95
|
+
'const _y=_y0*1000+_y1*100+_y2*10+_y3;' +
|
|
96
|
+
`const _mo0=${v}.charCodeAt(5)-48,_mo1=${v}.charCodeAt(6)-48;` +
|
|
97
|
+
'if((_mo0>>>0)>9||(_mo1>>>0)>9)return false;const _mo=_mo0*10+_mo1;if(_mo<1||_mo>12)return false;' +
|
|
98
|
+
`const _dm=_mo===2?(((_y%4===0&&_y%100!==0)||_y%400===0)?29:28):(_mo===4||_mo===6||_mo===9||_mo===11?30:31);` +
|
|
99
|
+
`const _d0=${v}.charCodeAt(8)-48,_d1=${v}.charCodeAt(9)-48;` +
|
|
100
|
+
'if((_d0>>>0)>9||(_d1>>>0)>9)return false;const _d=_d0*10+_d1;if(_d<1||_d>_dm)return false;' +
|
|
101
|
+
`const _h0=${v}.charCodeAt(11)-48,_h1=${v}.charCodeAt(12)-48;` +
|
|
102
|
+
'if((_h0>>>0)>9||(_h1>>>0)>9||_h0*10+_h1>23)return false;' +
|
|
103
|
+
`const _mi0=${v}.charCodeAt(14)-48,_mi1=${v}.charCodeAt(15)-48;` +
|
|
104
|
+
'if((_mi0>>>0)>5||(_mi1>>>0)>9)return false;' +
|
|
105
|
+
`const _se0=${v}.charCodeAt(17)-48,_se1=${v}.charCodeAt(18)-48;` +
|
|
106
|
+
'if((_se0>>>0)>6||(_se1>>>0)>9||_se0*10+_se1>60)return false;' +
|
|
107
|
+
`let _i2=19;if(${v}.charCodeAt(19)===46){_i2=20;const _st=_i2;while(_i2<_n){const _c2=${v}.charCodeAt(_i2);if(_c2<48||_c2>57)break;_i2++}if(_i2===_st)return false}` +
|
|
108
|
+
`const _tz=${v}.charCodeAt(_i2);let _dtoff=0;` +
|
|
109
|
+
`if(_tz===90||_tz===122){if(_i2!==_n-1)return false}` +
|
|
110
|
+
`else{if(_tz!==43&&_tz!==45)return false;if(_n-_i2!==6)return false;` +
|
|
111
|
+
`const _oh=${v}.charCodeAt(_i2+1),_oh2=${v}.charCodeAt(_i2+2),_om=${v}.charCodeAt(_i2+4),_om2=${v}.charCodeAt(_i2+5);` +
|
|
112
|
+
`if(_oh<48||_oh>57||_oh2<48||_oh2>57||_om<48||_om>57||_om2<48||_om2>57)return false;` +
|
|
113
|
+
`if(${v}.charCodeAt(_i2+3)!==58)return false;` +
|
|
114
|
+
`const _ohv=(_oh-48)*10+(_oh2-48),_omv=(_om-48)*10+(_om2-48);` +
|
|
115
|
+
`if(_ohv>23||_omv>59)return false;` +
|
|
116
|
+
`_dtoff=(_tz===43?1:-1)*(_ohv*60+_omv)}` +
|
|
117
|
+
`if(_se0*10+_se1===60){` +
|
|
118
|
+
`const _dtu=(((_h0*10+_h1)*60+(_mi0*10+_mi1)-_dtoff)%1440+1440)%1440;` +
|
|
119
|
+
`if(_dtu!==1439)return false}`);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// The scan, over a range of the string given as source expressions. The email
|
|
123
|
+
// check reads its domain half through this, without slicing it out, which was an
|
|
124
|
+
// allocation on every call.
|
|
125
|
+
//
|
|
126
|
+
// A separate function rather than optional parameters on hostnameSource: the code
|
|
127
|
+
// generator calls every entry of its format table as `fn(v, isStr, ctx)`, so a
|
|
128
|
+
// third parameter here would silently receive a context object and emit
|
|
129
|
+
// `_st=[object Object]`. That declines the whole compile, which is how it was
|
|
130
|
+
// found.
|
|
131
|
+
function hostnameRangeSource (v, st, en) {
|
|
132
|
+
return `const _st=${st},_en=${en},_n=_en-_st;if(_n===0||_n>253)return false;let _ll=0,_pv=46;` +
|
|
133
|
+
`for(let _i=_st;_i<_en;_i++){const _c=${v}.charCodeAt(_i);` +
|
|
134
|
+
`if(_c===46){if(_ll===0||_pv===45)return false;_ll=0;_pv=_c;continue}` +
|
|
135
|
+
`const _an=(_c>=48&&_c<=57)||(_c>=97&&_c<=122)||(_c>=65&&_c<=90);` +
|
|
136
|
+
`if(!_an&&_c!==45)return false;` +
|
|
137
|
+
`if(_c===45&&_pv===46)return false;` +
|
|
138
|
+
`if(++_ll>63)return false;_pv=_c}` +
|
|
139
|
+
`if(_ll===0||_pv===45)return false`;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function hostnameSource (v, isStr) {
|
|
143
|
+
return guard(v, isStr, hostnameRangeSource(v, '0', `${v}.length`));
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function uuidSource (v, isStr) {
|
|
147
|
+
const run = (from, to) => `for(let _ui=${from};_ui<${to};_ui++){const _uc=${v}.charCodeAt(_ui);` +
|
|
148
|
+
'if(!((_uc-48>>>0)<10||((_uc|32)-97>>>0)<6))return false}';
|
|
149
|
+
return guard(v, isStr, `if(${v}.length!==36)return false;` +
|
|
150
|
+
`if(${v}.charCodeAt(8)!==45||${v}.charCodeAt(13)!==45||` +
|
|
151
|
+
`${v}.charCodeAt(18)!==45||${v}.charCodeAt(23)!==45)return false;` +
|
|
152
|
+
run(0, 8) + run(9, 13) + run(14, 18) + run(19, 23) + run(24, 36));
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function timeSource (v, isStr) {
|
|
156
|
+
return guard(v, isStr, `const _n=${v}.length;if(_n<8)return false;` +
|
|
157
|
+
`const _h1=${v}.charCodeAt(0)-48,_h2=${v}.charCodeAt(1)-48;` +
|
|
158
|
+
'if((_h1>>>0)>9||(_h2>>>0)>9||_h1*10+_h2>23)return false;' +
|
|
159
|
+
`if(${v}.charCodeAt(2)!==58||${v}.charCodeAt(5)!==58)return false;` +
|
|
160
|
+
`const _m1=${v}.charCodeAt(3)-48,_m2=${v}.charCodeAt(4)-48;` +
|
|
161
|
+
'if((_m1>>>0)>5||(_m2>>>0)>9)return false;' +
|
|
162
|
+
`const _s1=${v}.charCodeAt(6)-48,_s2=${v}.charCodeAt(7)-48;` +
|
|
163
|
+
'if((_s1>>>0)>6||(_s2>>>0)>9||_s1*10+_s2>60)return false;' +
|
|
164
|
+
`let _ti=8;if(_ti<_n&&${v}.charCodeAt(_ti)===46){_ti++;const _tf=_ti;` +
|
|
165
|
+
`while(_ti<_n){const _tc=${v}.charCodeAt(_ti)-48;if((_tc>>>0)>9)break;_ti++}` +
|
|
166
|
+
'if(_ti===_tf)return false}' +
|
|
167
|
+
'if(_ti===_n)return false;' +
|
|
168
|
+
`const _tz=${v}.charCodeAt(_ti);let _off=0;` +
|
|
169
|
+
'if(_tz===90||_tz===122){if(_ti!==_n-1)return false}' +
|
|
170
|
+
'else{if(_tz!==43&&_tz!==45)return false;' +
|
|
171
|
+
`if(_n-_ti!==6||${v}.charCodeAt(_ti+3)!==58)return false;` +
|
|
172
|
+
`if((${v}.charCodeAt(_ti+1)-48>>>0)>9||(${v}.charCodeAt(_ti+2)-48>>>0)>9||` +
|
|
173
|
+
`(${v}.charCodeAt(_ti+4)-48>>>0)>9||(${v}.charCodeAt(_ti+5)-48>>>0)>9)return false;` +
|
|
174
|
+
`const _oh=(${v}.charCodeAt(_ti+1)-48)*10+(${v}.charCodeAt(_ti+2)-48),` +
|
|
175
|
+
`_om=(${v}.charCodeAt(_ti+4)-48)*10+(${v}.charCodeAt(_ti+5)-48);` +
|
|
176
|
+
'if(_oh>23||_om>59)return false;' +
|
|
177
|
+
'_off=(_tz===43?1:-1)*(_oh*60+_om)}' +
|
|
178
|
+
'if(_s1*10+_s2===60){' +
|
|
179
|
+
'const _u=(((_h1*10+_h2)*60+(_m1*10+_m2)-_off)%1440+1440)%1440;' +
|
|
180
|
+
'if(_u!==1439)return false}');
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// The same walk as `uri` above, as source, for the code generator to hoist
|
|
184
|
+
// once per compiled function and call. Two copies of one algorithm is what
|
|
185
|
+
// this file has always done for every format, so they sit together here and
|
|
186
|
+
// tests/test_uri_helper_parity.js holds them to the same answer over a corpus
|
|
187
|
+
// built from the walk's own boundaries.
|
|
188
|
+
const URI_HELPER_TABLES = 'const _uct=new Uint8Array(128);for(let _i=33;_i<127;_i++)_uct[_i]=1;' +
|
|
189
|
+
'_uct[34]=_uct[60]=_uct[62]=_uct[92]=_uct[94]=_uct[96]=_uct[123]=_uct[124]=_uct[125]=0;' +
|
|
190
|
+
'const _uch=new Uint8Array(128);for(let _i=48;_i<58;_i++)_uch[_i]=1;' +
|
|
191
|
+
'for(let _i=97;_i<103;_i++)_uch[_i]=1;for(let _i=65;_i<71;_i++)_uch[_i]=1;' +
|
|
192
|
+
'const _ucs=new Uint8Array(128);for(let _i=48;_i<58;_i++)_ucs[_i]=1;' +
|
|
193
|
+
'for(let _i=97;_i<123;_i++)_ucs[_i]=1;for(let _i=65;_i<91;_i++)_ucs[_i]=1;' +
|
|
194
|
+
'_ucs[43]=1;_ucs[45]=1;_ucs[46]=1;const _ufa=/' + URI_FAST.source + '/';
|
|
195
|
+
const URI_HELPER_BODY = 'if(_ufa.test(_s))return true;const _n=_s.length;if(_n===0)return false;let _c=_s.charCodeAt(0);if(_c>127||_ucs[_c]===0||(_c>=48&&_c<=57)||_c===43||_c===45||_c===46)return false;let _co=-1;for(let _i=1;_i<_n;_i++){_c=_s.charCodeAt(_i);if(_c===58){_co=_i;break}if(_c>127||_ucs[_c]===0)return false}if(_co===-1)return false;let _i=_co+1;let _ae=_n;if(_s.charCodeAt(_i)===47&&_s.charCodeAt(_i+1)===47){const _as=_i+2;let _at=-1,_fc=-1,_lc=-1,_br=0,_ba=0,_hs=_as;_ae=-1;for(_i=_as;_i<_n;_i++){_c=_s.charCodeAt(_i);if(_c>127||_uct[_c]===0)return false;if(_c===47||_c===63||_c===35){_ae=_i;break}if(_c===37){const _h1=_s.charCodeAt(_i+1),_h2=_s.charCodeAt(_i+2);if(!(_h1<=127)||!(_h2<=127)||_uch[_h1]===0||_uch[_h2]===0)return false;_i+=2;continue}if(_c===64){_ba=_br;_at=_i;_fc=-1;_lc=-1;_hs=_i+1}else if(_c===58){if(_fc===-1)_fc=_i;_lc=_i}else if(_c===91||_c===93){_br=1}}if(_ae===-1)_ae=_n;if(_at!==-1&&_ba)return false;if(_s.charCodeAt(_hs)===91){let _cl=-1;for(let _j=_hs+1;_j<_ae;_j++){if(_s.charCodeAt(_j)===93){_cl=_j;break}}if(_cl===-1)return false;if(_cl+1!==_ae){if(_s.charCodeAt(_cl+1)!==58)return false;for(let _j=_cl+2;_j<_ae;_j++){const _d=_s.charCodeAt(_j);if(_d<48||_d>57)return false}}}else if(_lc!==-1){if(_fc!==_lc)return false;for(let _j=_lc+1;_j<_ae;_j++){const _d=_s.charCodeAt(_j);if(_d<48||_d>57)return false}}_i=_ae}for(;_i<_n;_i++){_c=_s.charCodeAt(_i);if(_c>127||_uct[_c]===0)return false;if(_c===37){const _h1=_s.charCodeAt(_i+1),_h2=_s.charCodeAt(_i+2);if(!(_h1<=127)||!(_h2<=127)||_uch[_h1]===0||_uch[_h2]===0)return false;_i+=2}}return true;';
|
|
196
|
+
const uriHelperSource = (name) =>
|
|
197
|
+
URI_HELPER_TABLES + ';function ' + name + '(_s){' + URI_HELPER_BODY + '}';
|
|
198
|
+
|
|
199
|
+
function uriSource (v, isStr) {
|
|
200
|
+
return guard(v, isStr, `const _n=${v}.length;if(_n===0)return false;` +
|
|
201
|
+
`const _f=${v}.charCodeAt(0);if(!((_f>=97&&_f<=122)||(_f>=65&&_f<=90)))return false;` +
|
|
202
|
+
`let _co=-1;for(let _i=1;_i<_n;_i++){const _c=${v}.charCodeAt(_i);if(_c===58){_co=_i;break}` +
|
|
203
|
+
`if(!((_c>=48&&_c<=57)||(_c>=97&&_c<=122)||(_c>=65&&_c<=90)||_c===43||_c===45||_c===46))return false}` +
|
|
204
|
+
`if(_co===-1)return false;` +
|
|
205
|
+
uriCharsSource(v, '_co+1') +
|
|
206
|
+
uriAuthoritySource(v, '_co+1'));
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function ipv6Source (v, isStr) {
|
|
210
|
+
return guard(v, isStr, `const _n=${v}.length;if(_n<2||_n>45)return false;let _end=_n,_g=0;` +
|
|
211
|
+
`const _dot=${v}.indexOf('.');` +
|
|
212
|
+
`if(_dot!==-1){const _lc=${v}.lastIndexOf(':',_dot);if(_lc===-1)return false;` +
|
|
213
|
+
`{const _f=_lc+1,_t=_n,_ln=_t-_f;if(_ln<7||_ln>15)return false;let _o=0,_val=0,_dg=0;` +
|
|
214
|
+
`for(let _i=_f;_i<=_t;_i++){const _c=_i<_t?${v}.charCodeAt(_i):46;` +
|
|
215
|
+
`if(_c===46){if(_dg===0||_val>255)return false;_o++;_val=0;_dg=0;if(_o>4)return false}` +
|
|
216
|
+
`else if(_c>=48&&_c<=57){if(_dg===1&&_val===0)return false;_val=_val*10+(_c-48);_dg++;if(_dg>3)return false}` +
|
|
217
|
+
`else return false}if(_o!==4)return false}` +
|
|
218
|
+
`_end=_lc+1;_g=2}` +
|
|
219
|
+
`let _cp=false,_dg2=0,_i2=0;` +
|
|
220
|
+
`if(${v}.charCodeAt(0)===58&&${v}.charCodeAt(1)!==58)return false;` +
|
|
221
|
+
`while(_i2<_end){const _c2=${v}.charCodeAt(_i2);` +
|
|
222
|
+
`if(_c2===58){if(_dg2>0){_g++;_dg2=0}` +
|
|
223
|
+
`if(_i2+1<_n&&${v}.charCodeAt(_i2+1)===58){if(_cp)return false;_cp=true;_i2+=2;if(_i2<_n&&${v}.charCodeAt(_i2)===58)return false;continue}` +
|
|
224
|
+
`_i2++;if(_i2===_end&&_end===_n)return false;continue}` +
|
|
225
|
+
`if((_c2>=48&&_c2<=57)||(_c2>=97&&_c2<=102)||(_c2>=65&&_c2<=70)){if(++_dg2>4)return false;_i2++;continue}` +
|
|
226
|
+
`return false}` +
|
|
227
|
+
`if(_dg2>0)_g++;if(_g>8)return false;if(_cp){if(_g>=8)return false}else if(_g!==8)return false`);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function ipv4Source (v, isStr) {
|
|
231
|
+
return guard(v, isStr, `const _n=${v}.length;if(_n<7||_n>15)return false;let _o=0,_val=0,_dg=0;for(let _i=0;_i<=_n;_i++){const _c=_i<_n?${v}.charCodeAt(_i):46;if(_c===46){if(_dg===0||_val>255)return false;_o++;_val=0;_dg=0;if(_o>4)return false}else if(_c>=48&&_c<=57){if(_dg===1&&_val===0)return false;_val=_val*10+(_c-48);_dg++;if(_dg>3)return false}else return false}if(_o!==4)return false`);
|
|
232
|
+
}
|
|
233
|
+
function jsonPointerSource(v, isStr) {
|
|
234
|
+
const body = `{let _ok=${v}==='';if(!_ok&&${v}.charCodeAt(0)===47){_ok=true;for(let _i=0;_i<${v}.length;_i++){if(${v}.charCodeAt(_i)!==126)continue;const _n=${v}.charCodeAt(_i+1);if(_n!==48&&_n!==49){_ok=false;break}}}if(!_ok)return false}`;
|
|
235
|
+
return isStr ? body : `if(typeof ${v}==='string')${body}`;
|
|
236
|
+
}
|
|
237
|
+
function relativeJsonPointerSource(v, isStr) {
|
|
238
|
+
const body = `{let _i=0;while(_i<${v}.length){const _c=${v}.charCodeAt(_i);if(_c<48||_c>57)break;_i++}let _ok=_i>0&&!(_i>1&&${v}.charCodeAt(0)===48);if(_ok){const _r=${v}.slice(_i);if(_r!=='#'){_ok=_r==='';if(!_ok&&_r.charCodeAt(0)===47){_ok=true;for(let _j=0;_j<_r.length;_j++){if(_r.charCodeAt(_j)!==126)continue;const _n=_r.charCodeAt(_j+1);if(_n!==48&&_n!==49){_ok=false;break}}}}}if(!_ok)return false}`;
|
|
239
|
+
return isStr ? body : `if(typeof ${v}==='string')${body}`;
|
|
240
|
+
}
|
|
241
|
+
function uriTemplateSource (v, isStr) {
|
|
242
|
+
const inner =
|
|
243
|
+
`let _i=0;` +
|
|
244
|
+
`while(_i<${v}.length){` +
|
|
245
|
+
`const _c=${v}.charCodeAt(_i);` +
|
|
246
|
+
`if(_c===125)return false;` +
|
|
247
|
+
`if(_c!==123&&(_c<=32||_c===127))return false;` +
|
|
248
|
+
`if(_c!==123){_i++;continue}` +
|
|
249
|
+
// `_ute`, not `_e`: this source is spliced into a generated function whose
|
|
250
|
+
// error accumulator is `_e`, and a `const _e` here shadowed it, so
|
|
251
|
+
// `(_e||(_e=[])).push(...)` ran against a number and threw. The verdict was
|
|
252
|
+
// right, so the suite never saw it; only a caller reading `.errors` did.
|
|
253
|
+
`const _ute=${v}.indexOf('}',_i+1);` +
|
|
254
|
+
`if(_ute===-1)return false;` +
|
|
255
|
+
`const _x=${v}.slice(_i+1,_ute);` +
|
|
256
|
+
`if(_x===''||_x.indexOf('{')!==-1)return false;` +
|
|
257
|
+
`if(!${URI_TEMPLATE_EXPR.toString()}.test(_x))return false;` +
|
|
258
|
+
`_i=_ute+1` +
|
|
259
|
+
`}`;
|
|
260
|
+
return isStr ? `{${inner}}` : `if(typeof ${v}==='string'){${inner}}`;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// --- RFC 5321 mailbox -------------------------------------------------------
|
|
264
|
+
// Local part is either a quoted string or dot-separated atoms, so a leading,
|
|
265
|
+
// trailing or doubled dot is not a mailbox. Domain is a hostname or a bracketed
|
|
266
|
+
// address literal.
|
|
267
|
+
// Local-part characters: alphanumerics, the RFC 5322 atext punctuation, and
|
|
268
|
+
// the dot that separates atoms. Written as code ranges rather than a regular
|
|
269
|
+
// expression because this lands inside every emitted module that validates an
|
|
270
|
+
// email, and the expression cost more bytes there than the whole check.
|
|
271
|
+
// The emitted form keeps the comparison chain: a standalone module pays for the
|
|
272
|
+
// bytes of a 128-entry table in every file that validates an email, and the
|
|
273
|
+
// runtime pays for the chain on every call. Different budgets, same answers,
|
|
274
|
+
// which `tests/test_format_engine_parity.js` holds.
|
|
275
|
+
// Stated as the complement: of the printable range, only twelve characters are
|
|
276
|
+
// not atext or the dot, so excluding them is 54 source bytes shorter than listing
|
|
277
|
+
// what is allowed, and a standalone module carries this expression. Checked
|
|
278
|
+
// against the allowed-list form over every code point from 0 to 65536 in
|
|
279
|
+
// tests/test_formats_single_pass.js.
|
|
280
|
+
const EMAIL_LOCAL_SRC = (v) => `(${v}>32&&${v}<127&&${v}!==34&&${v}!==40&&${v}!==41&&${v}!==44&&${v}!==58&&${v}!==59&&${v}!==60&&${v}!==62&&${v}!==64&&${v}!==91&&${v}!==92&&${v}!==93)`;
|
|
281
|
+
// Emitted rather than bound as a closure: a standalone module embeds format
|
|
282
|
+
// functions by their source text, so anything reaching a free variable would
|
|
283
|
+
// not survive the trip. The domain half reuses hostnameSource, which keeps
|
|
284
|
+
// one hostname implementation for both halves of this file.
|
|
285
|
+
function emailSource (v, isStr) {
|
|
286
|
+
const inner =
|
|
287
|
+
// One forward pass, and neither half sliced out. '@' is not a local-part
|
|
288
|
+
// character, so in an unquoted address the first one is the separator and the
|
|
289
|
+
// local part is checked on the way to it. A quoted local part may contain
|
|
290
|
+
// '@' and keeps the backward scan, which is the rare shape.
|
|
291
|
+
// The bounds tests are merged into one condition per branch, and there is no
|
|
292
|
+
// separate length check: an empty local part fails `_at<=0` and an empty
|
|
293
|
+
// domain fails `_at===_n0-1`, so a short string is already refused. This
|
|
294
|
+
// lands in every emitted module that validates an email, and 25 bytes of it
|
|
295
|
+
// put the CLI smoke build over its --max-size once.
|
|
296
|
+
`const _n0=${v}.length;let _at;` +
|
|
297
|
+
`if(${v}.charCodeAt(0)===34){` +
|
|
298
|
+
`_at=${v}.lastIndexOf('@');` +
|
|
299
|
+
`if(_at<2||_at===_n0-1||_at>64||${v}.charCodeAt(_at-1)!==34)return false` +
|
|
300
|
+
`}else{` +
|
|
301
|
+
`if(${v}.charCodeAt(0)===46)return false;` +
|
|
302
|
+
`_at=-1;let _pd=false;` +
|
|
303
|
+
`for(let _i=0;_i<_n0;_i++){const _lc=${v}.charCodeAt(_i);if(_lc===64){_at=_i;break}if(!${EMAIL_LOCAL_SRC('_lc')})return false;if(_lc===46){if(_pd)return false;_pd=true}else _pd=false}` +
|
|
304
|
+
`if(_at<=0||_at===_n0-1||_at>64||${v}.charCodeAt(_at-1)===46)return false` +
|
|
305
|
+
`}` +
|
|
306
|
+
// An address literal is rare enough to pay for its own slice inside the
|
|
307
|
+
// branch that needs it; the hostname path, which is every real address,
|
|
308
|
+
// reads the domain in place.
|
|
309
|
+
`if(${v}.charCodeAt(_at+1)===91){` +
|
|
310
|
+
`const _dm=${v}.slice(_at+1);` +
|
|
311
|
+
`if(_dm.charCodeAt(_dm.length-1)!==93||_dm.length<=2)return false;` +
|
|
312
|
+
`const _in=_dm.slice(1,-1);` +
|
|
313
|
+
`if(_in.startsWith('IPv6:')){const _i6=_in.slice(5);${ipv6Source('_i6', true)}}` +
|
|
314
|
+
`else{${ipv4Source('_in', true)}}` +
|
|
315
|
+
`}` +
|
|
316
|
+
`else{${hostnameRangeSource(v, '_at+1', `${v}.length`)}}`;
|
|
317
|
+
return isStr ? `{${inner}}` : `if(typeof ${v}==='string'){${inner}}`;
|
|
318
|
+
}
|
|
319
|
+
function durationSource (v, isStr) {
|
|
320
|
+
const inner = `if(!${DURATION_RE.toString()}.test(${v}))return false`;
|
|
321
|
+
return isStr ? inner : `if(typeof ${v}==='string'&&${inner.slice(3)}`;
|
|
322
|
+
}
|
|
323
|
+
module.exports = { uriHelperSource, uriCharsSource, uriAuthoritySource, iriSource, iriReferenceSource, idnEmailSource, dateSource, ipv4Source, dateTimeSource, ipv6Source, hostnameSource, uriSource, uuidSource, timeSource, noReservedSource, jsonPointerSource, relativeJsonPointerSource, uriTemplateSource, emailSource, durationSource };
|