ata-validator 1.11.0 → 1.12.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/CHANGELOG.md +20 -0
- package/index.js +66 -20
- package/lib/formats.js +203 -22
- package/lib/interpreter.js +4 -4
- package/lib/js-compiler.js +6 -12
- package/lib/version.js +1 -1
- package/package.json +8 -8
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,26 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to ata-validator are documented here. The format follows [Keep a Changelog](https://keepachangelog.com/), and this project adheres to semantic versioning.
|
|
4
4
|
|
|
5
|
+
## 1.12.0 - 2026-09-06
|
|
6
|
+
|
|
7
|
+
### Performance
|
|
8
|
+
|
|
9
|
+
- `date-time` stopped asking every character where it sits. The scan tested each of nineteen positions against the separator indices before checking the digit; the separators are now read directly by index and each digit becomes its value in the same read that validates it, so the field numbers cost nothing extra. 40.8 to 19.5 ns on a valid value, medians across separate processes. Same answers as before on every month, day and clock boundary and under 150k random mutations.
|
|
10
|
+
|
|
11
|
+
- `uuid` reads the four hyphens by index and the 32 hex digits in five runs with fixed bounds, instead of a case-insensitive regular expression. A digit is one unsigned compare and a letter one more after folding case with a single OR. 48.4 to 35.1 ns; 300k mutated and random strings against the old expression with 0 mismatches.
|
|
12
|
+
|
|
13
|
+
- `time` reads fixed positions the same way instead of running a regular expression: 15.6 to 12.1 ns, and 200k mutations against the old pattern with 0 mismatches.
|
|
14
|
+
|
|
15
|
+
- `uri` reads the scheme from the front and scans the rest once instead of running two regular expressions: 39.3 to 32.8 ns, interleaved medians. The remaining scan then became two tiers, because the measured answer was not the expected one: a hand-written character loop beat the old `\s` expression in isolation but lost to the engine's scanner inside a compiled validator, since `\s` is what forced the Unicode machinery in. The first tier asks whether anything sits outside printable ASCII, a one-byte class the engine scans at its own speed and no ordinary URI ever trips; only a string that trips it pays for the loop that knows the exact reserved set. On a nested schema carrying six URLs, 215.5 to 189.4 ns per document, medians across separate processes. `uri-reference` shares the same scan. Fuzzed over every code point in the BMP in three positions, 0 mismatches.
|
|
16
|
+
|
|
17
|
+
- The Standard Schema bridge stopped paying for enrichment it throws away. An issue carries a message and a path and nothing else, but `~standard.validate` read the rich error list, which builds suggestions, ranks and source frames per error. It now takes the raw, schema-ordered list through the same build the rich path uses, and `parsePointerPath` walks the pointer in one pass instead of split, filter, map and a regex per segment. A 16-error rejection went from 17.6 to 4.3 microseconds; messages and order are unchanged, and `tests/test_standard_schema.js` holds issues to exact parity with `validate().errors`. Schemas using `errorMessage` keep their custom messages.
|
|
18
|
+
|
|
19
|
+
### Fixed
|
|
20
|
+
|
|
21
|
+
- The compiled engine refused a lowercase zone letter in `time` that the interpreter accepted: `00:00:00z` answered differently depending on which engine ran the schema, and `date-time` took either case in both. One implementation now answers for every engine, and it takes both cases, per RFC 3339.
|
|
22
|
+
|
|
23
|
+
- The ReDoS integration test measured the first call, which includes compiling the schema and the pattern, against a 50 ms budget. On a loaded CI machine that reads as a failure without anything being wrong: the gate exists to separate linear matching from catastrophic backtracking, which differ by minutes, not by milliseconds. It now warms up first and allows 500 ms.
|
|
24
|
+
|
|
5
25
|
## 1.11.0 - 2026-08-31
|
|
6
26
|
|
|
7
27
|
### Fixed
|
package/index.js
CHANGED
|
@@ -326,15 +326,22 @@ const ABORT_EARLY_RESULT = Object.freeze({
|
|
|
326
326
|
// the eager shape. Note for tests: deepStrictEqual against a plain object
|
|
327
327
|
// compares prototypes; read `.errors` and compare that.
|
|
328
328
|
class LazyRejection {
|
|
329
|
-
constructor(build, data) {
|
|
329
|
+
constructor(build, data, buildRaw) {
|
|
330
330
|
this.valid = false;
|
|
331
331
|
this._build = build;
|
|
332
332
|
this._data = data;
|
|
333
333
|
this._errors = null;
|
|
334
|
+
this._buildRaw = buildRaw;
|
|
334
335
|
}
|
|
335
336
|
toJSON() {
|
|
336
337
|
return { valid: false, errors: this.errors };
|
|
337
338
|
}
|
|
339
|
+
// Raw shape for consumers that carry only message and path, such as the
|
|
340
|
+
// Standard Schema bridge: schema order, no enrichment. Reading `errors`
|
|
341
|
+
// afterwards still enriches through its own build.
|
|
342
|
+
_ataRaw() {
|
|
343
|
+
return this._buildRaw ? this._buildRaw(this._data) : this.errors;
|
|
344
|
+
}
|
|
338
345
|
}
|
|
339
346
|
Object.defineProperty(LazyRejection.prototype, 'errors', {
|
|
340
347
|
enumerable: true,
|
|
@@ -463,18 +470,30 @@ function sortErrorsBySchemaOrder(rootSchema, errors) {
|
|
|
463
470
|
|
|
464
471
|
function parsePointerPath(path) {
|
|
465
472
|
if (!path) return [];
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
473
|
+
// One pass, no intermediate arrays. Per Standard Schema V1 an array index
|
|
474
|
+
// is emitted as a number and an object key as a string; a segment is an
|
|
475
|
+
// index when it is all digits with no leading zero.
|
|
476
|
+
const out = [];
|
|
477
|
+
const n = path.length;
|
|
478
|
+
let start = 1;
|
|
479
|
+
for (let i = 1; i <= n; i++) {
|
|
480
|
+
if (i !== n && path.charCodeAt(i) !== 47) continue;
|
|
481
|
+
if (i > start) {
|
|
482
|
+
let seg = path.slice(start, i);
|
|
483
|
+
if (seg.indexOf('~') >= 0) seg = seg.replace(/~1/g, '/').replace(/~0/g, '~');
|
|
484
|
+
const c0 = seg.charCodeAt(0);
|
|
485
|
+
let numeric = c0 >= 48 && c0 <= 57 && (seg.length === 1 || c0 !== 48);
|
|
486
|
+
if (numeric) {
|
|
487
|
+
for (let k = 1; k < seg.length; k++) {
|
|
488
|
+
const c = seg.charCodeAt(k);
|
|
489
|
+
if (c < 48 || c > 57) { numeric = false; break; }
|
|
490
|
+
}
|
|
475
491
|
}
|
|
476
|
-
|
|
477
|
-
}
|
|
492
|
+
out.push({ key: numeric ? Number(seg) : seg });
|
|
493
|
+
}
|
|
494
|
+
start = i + 1;
|
|
495
|
+
}
|
|
496
|
+
return out;
|
|
478
497
|
}
|
|
479
498
|
|
|
480
499
|
function createPaddedBuffer(jsonStr) {
|
|
@@ -1379,6 +1398,14 @@ class Validator {
|
|
|
1379
1398
|
let cached = null;
|
|
1380
1399
|
return {
|
|
1381
1400
|
valid: false,
|
|
1401
|
+
// Raw shape for consumers that carry only message and path, such
|
|
1402
|
+
// as the Standard Schema bridge: schema order, no enrichment.
|
|
1403
|
+
// Reading `errors` afterwards still enriches from the same build.
|
|
1404
|
+
_ataRaw() {
|
|
1405
|
+
let raw = result.errors || [];
|
|
1406
|
+
if (raw.length > 1) raw = sortErrorsBySchemaOrder(root, raw);
|
|
1407
|
+
return raw;
|
|
1408
|
+
},
|
|
1382
1409
|
get errors() {
|
|
1383
1410
|
if (cached === null) {
|
|
1384
1411
|
let raw = result.errors || [];
|
|
@@ -1550,17 +1577,24 @@ class Validator {
|
|
|
1550
1577
|
const _full = this.validate;
|
|
1551
1578
|
const _fast = this._fastVerdict;
|
|
1552
1579
|
const EMPTY_ERRORS = Object.freeze([]);
|
|
1580
|
+
const _verdictFallback = [{ keyword: 'validation', instancePath: '', schemaPath: '#', params: {}, message: 'schema validation failed' }];
|
|
1553
1581
|
const _buildErrors = (data) => {
|
|
1554
1582
|
const r = _full(data);
|
|
1555
1583
|
return (r && r.valid === false && r.errors && r.errors.length)
|
|
1556
1584
|
? r.errors
|
|
1557
1585
|
// The data changed between the verdict and this read; keep the
|
|
1558
1586
|
// verdict and say so rather than inventing a specific error.
|
|
1559
|
-
:
|
|
1587
|
+
: _verdictFallback;
|
|
1588
|
+
};
|
|
1589
|
+
const _buildRawErrors = (data) => {
|
|
1590
|
+
const r = _full(data);
|
|
1591
|
+
if (!r || r.valid !== false) return _verdictFallback;
|
|
1592
|
+
const raw = typeof r._ataRaw === 'function' ? r._ataRaw() : r.errors;
|
|
1593
|
+
return raw && raw.length ? raw : _verdictFallback;
|
|
1560
1594
|
};
|
|
1561
1595
|
this.validate = (data) => {
|
|
1562
1596
|
if (_fast(data)) return { valid: true, data, errors: EMPTY_ERRORS };
|
|
1563
|
-
return new LazyRejection(_buildErrors, data);
|
|
1597
|
+
return new LazyRejection(_buildErrors, data, _buildRawErrors);
|
|
1564
1598
|
};
|
|
1565
1599
|
}
|
|
1566
1600
|
|
|
@@ -2018,12 +2052,24 @@ Object.defineProperty(Validator.prototype, "~standard", {
|
|
|
2018
2052
|
if (result.valid) {
|
|
2019
2053
|
return { value };
|
|
2020
2054
|
}
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2055
|
+
// An issue carries a message and a path and nothing else, so the
|
|
2056
|
+
// suggestion and source-frame work the rich error path does would be
|
|
2057
|
+
// thrown away here. Take the raw, schema-ordered list when the result
|
|
2058
|
+
// offers one; fall back to the public list otherwise.
|
|
2059
|
+
const raw = typeof result._ataRaw === 'function' ? result._ataRaw() : result.errors;
|
|
2060
|
+
const issues = new Array(raw.length);
|
|
2061
|
+
for (let i = 0; i < raw.length; i++) {
|
|
2062
|
+
const err = raw[i];
|
|
2063
|
+
const path = err.instancePath != null ? err.instancePath : (err.path || '');
|
|
2064
|
+
let message = err.message;
|
|
2065
|
+
if (!message) {
|
|
2066
|
+
// The native engine reports numeric codes without a message; the
|
|
2067
|
+
// enrich pass knows how to word those. Rare, so required lazily.
|
|
2068
|
+
message = require('./lib/enrich-error').enrich(err, {}).message;
|
|
2069
|
+
}
|
|
2070
|
+
issues[i] = { message, path: parsePointerPath(path) };
|
|
2071
|
+
}
|
|
2072
|
+
return { issues };
|
|
2027
2073
|
},
|
|
2028
2074
|
});
|
|
2029
2075
|
Object.defineProperty(this, "~standard", {
|
package/lib/formats.js
CHANGED
|
@@ -42,23 +42,36 @@ function dateTime (s) {
|
|
|
42
42
|
const n = s.length;
|
|
43
43
|
// The shortest accepted form is 2026-08-31T12:00:00Z
|
|
44
44
|
if (n < 20) return false;
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
45
|
+
// Separators sit at known indices, so they are read directly rather than
|
|
46
|
+
// asked for at every step of a scan. Each remaining digit is turned into
|
|
47
|
+
// its value once and checked as one unsigned compare, which also gives the
|
|
48
|
+
// field its number without a second read.
|
|
49
|
+
if (s.charCodeAt(4) !== 45 || s.charCodeAt(7) !== 45) return false;
|
|
50
|
+
const sep = s.charCodeAt(10);
|
|
51
|
+
if (sep !== 84 && sep !== 116) return false;
|
|
52
|
+
if (s.charCodeAt(13) !== 58 || s.charCodeAt(16) !== 58) return false;
|
|
53
|
+
|
|
54
|
+
const y0 = s.charCodeAt(0) - 48, y1 = s.charCodeAt(1) - 48;
|
|
55
|
+
const y2 = s.charCodeAt(2) - 48, y3 = s.charCodeAt(3) - 48;
|
|
56
|
+
if ((y0 >>> 0) > 9 || (y1 >>> 0) > 9 || (y2 >>> 0) > 9 || (y3 >>> 0) > 9) return false;
|
|
57
|
+
const year = y0 * 1000 + y1 * 100 + y2 * 10 + y3;
|
|
52
58
|
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
const month =
|
|
59
|
+
const mo0 = s.charCodeAt(5) - 48, mo1 = s.charCodeAt(6) - 48;
|
|
60
|
+
if ((mo0 >>> 0) > 9 || (mo1 >>> 0) > 9) return false;
|
|
61
|
+
const month = mo0 * 10 + mo1;
|
|
56
62
|
if (month < 1 || month > 12) return false;
|
|
57
|
-
|
|
63
|
+
|
|
64
|
+
const d0 = s.charCodeAt(8) - 48, d1 = s.charCodeAt(9) - 48;
|
|
65
|
+
if ((d0 >>> 0) > 9 || (d1 >>> 0) > 9) return false;
|
|
66
|
+
const day = d0 * 10 + d1;
|
|
58
67
|
if (day < 1 || day > daysInMonth(year, month)) return false;
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
if (
|
|
68
|
+
|
|
69
|
+
const h0 = s.charCodeAt(11) - 48, h1 = s.charCodeAt(12) - 48;
|
|
70
|
+
if ((h0 >>> 0) > 9 || (h1 >>> 0) > 9 || h0 * 10 + h1 > 23) return false;
|
|
71
|
+
const mi0 = s.charCodeAt(14) - 48, mi1 = s.charCodeAt(15) - 48;
|
|
72
|
+
if ((mi0 >>> 0) > 5 || (mi1 >>> 0) > 9) return false;
|
|
73
|
+
const se0 = s.charCodeAt(17) - 48, se1 = s.charCodeAt(18) - 48;
|
|
74
|
+
if ((se0 >>> 0) > 5 || (se1 >>> 0) > 9) return false;
|
|
62
75
|
|
|
63
76
|
let i = 19;
|
|
64
77
|
if (s.charCodeAt(i) === 46) {
|
|
@@ -195,6 +208,125 @@ function hostname (s) {
|
|
|
195
208
|
return labelLength !== 0 && previous !== 45;
|
|
196
209
|
}
|
|
197
210
|
|
|
211
|
+
// A scheme followed by a colon, and no control characters or spaces after it.
|
|
212
|
+
// Two expressions became one pass: the scheme is read from the front and the
|
|
213
|
+
// rest of the string is scanned once for characters a URI cannot hold.
|
|
214
|
+
function uri (s) {
|
|
215
|
+
const n = s.length;
|
|
216
|
+
if (n === 0) return false;
|
|
217
|
+
const first = s.charCodeAt(0);
|
|
218
|
+
if (!((first >= 97 && first <= 122) || (first >= 65 && first <= 90))) return false;
|
|
219
|
+
let colon = -1;
|
|
220
|
+
for (let i = 1; i < n; i++) {
|
|
221
|
+
const c = s.charCodeAt(i);
|
|
222
|
+
if (c === 58) { colon = i; break; }
|
|
223
|
+
// scheme characters: letters, digits, "+", "-", "."
|
|
224
|
+
if (!(isDigit(c) || (c >= 97 && c <= 122) || (c >= 65 && c <= 90) ||
|
|
225
|
+
c === 43 || c === 45 || c === 46)) return false;
|
|
226
|
+
}
|
|
227
|
+
if (colon === -1) return false;
|
|
228
|
+
return noReserved(s, colon + 1);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// The characters a URI cannot hold: C0 controls, space, DEL, and the rest of
|
|
232
|
+
// Unicode whitespace. Asking for that set directly costs more than it looks:
|
|
233
|
+
// `\s` pulls in the engine's Unicode machinery for eleven code points spread
|
|
234
|
+
// across the BMP. Asking instead whether anything at all sits outside printable
|
|
235
|
+
// ASCII is a one-byte character class, which the engine scans at its own speed
|
|
236
|
+
// and which no ordinary URI ever trips. Only a string that does trip it pays
|
|
237
|
+
// for the loop that decides the exact set, and that loop was measured slower
|
|
238
|
+
// than the engine on the common case, which is why it is not the first answer.
|
|
239
|
+
const NON_PRINTABLE = /[^\u0021-\u007e]/;
|
|
240
|
+
|
|
241
|
+
function noReserved (s, from) {
|
|
242
|
+
if (!NON_PRINTABLE.test(s)) return true;
|
|
243
|
+
const n = s.length;
|
|
244
|
+
for (let i = from; i < n; i++) {
|
|
245
|
+
const c = s.charCodeAt(i);
|
|
246
|
+
if (c > 32 && c < 127) continue;
|
|
247
|
+
if (c <= 32 || c === 127) return false;
|
|
248
|
+
if (c === 160 || c === 5760 || (c >= 8192 && c <= 8202) || c === 8232 ||
|
|
249
|
+
c === 8233 || c === 8239 || c === 8287 || c === 12288 || c === 65279) return false;
|
|
250
|
+
}
|
|
251
|
+
return true;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Source twin of noReserved, same two tiers. `from` is an expression for the
|
|
255
|
+
// first index the loop reads; the variable names stay clear of the ones the
|
|
256
|
+
// other format emitters use.
|
|
257
|
+
function noReservedSource (v, from) {
|
|
258
|
+
return `if(/[^\\u0021-\\u007e]/.test(${v})){` +
|
|
259
|
+
`for(let _ri=${from};_ri<${v}.length;_ri++){const _rc=${v}.charCodeAt(_ri);` +
|
|
260
|
+
'if(_rc>32&&_rc<127)continue;' +
|
|
261
|
+
'if(_rc<=32||_rc===127)return false;' +
|
|
262
|
+
'if(_rc===160||_rc===5760||(_rc>=8192&&_rc<=8202)||_rc===8232||' +
|
|
263
|
+
'_rc===8233||_rc===8239||_rc===8287||_rc===12288||_rc===65279)return false}}';
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// 8-4-4-4-12 hexadecimal digits. The four hyphens are read by index, then
|
|
267
|
+
// each run of hex digits is read with fixed bounds, so no character pays for a
|
|
268
|
+
// test of where it sits. A digit is one unsigned compare and a letter is one
|
|
269
|
+
// more after folding case with a single OR, which is what a case-insensitive
|
|
270
|
+
// regex spends a state machine on.
|
|
271
|
+
function hexRun (s, from, to) {
|
|
272
|
+
for (let i = from; i < to; i++) {
|
|
273
|
+
const c = s.charCodeAt(i);
|
|
274
|
+
if (!((c - 48 >>> 0) < 10 || ((c | 32) - 97 >>> 0) < 6)) return false;
|
|
275
|
+
}
|
|
276
|
+
return true;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function uuid (s) {
|
|
280
|
+
if (s.length !== 36) return false;
|
|
281
|
+
if (s.charCodeAt(8) !== 45 || s.charCodeAt(13) !== 45 ||
|
|
282
|
+
s.charCodeAt(18) !== 45 || s.charCodeAt(23) !== 45) return false;
|
|
283
|
+
return hexRun(s, 0, 8) && hexRun(s, 9, 13) && hexRun(s, 14, 18) &&
|
|
284
|
+
hexRun(s, 19, 23) && hexRun(s, 24, 36);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// RFC 3339 full-time: HH:MM:SS, an optional fraction, an optional offset.
|
|
288
|
+
// Every position is known in advance, so the check reads fixed indices and
|
|
289
|
+
// compares digits as numbers instead of running a state machine over the
|
|
290
|
+
// string. The zone letter is accepted in either case, which is what the
|
|
291
|
+
// interpreter and the date-time check already did.
|
|
292
|
+
function time (s) {
|
|
293
|
+
const n = s.length;
|
|
294
|
+
if (n < 8) return false;
|
|
295
|
+
const h1 = s.charCodeAt(0) - 48, h2 = s.charCodeAt(1) - 48;
|
|
296
|
+
if ((h1 >>> 0) > 9 || (h2 >>> 0) > 9 || h1 * 10 + h2 > 23) return false;
|
|
297
|
+
if (s.charCodeAt(2) !== 58 || s.charCodeAt(5) !== 58) return false;
|
|
298
|
+
const m1 = s.charCodeAt(3) - 48, m2 = s.charCodeAt(4) - 48;
|
|
299
|
+
if ((m1 >>> 0) > 5 || (m2 >>> 0) > 9) return false;
|
|
300
|
+
const c1 = s.charCodeAt(6) - 48, c2 = s.charCodeAt(7) - 48;
|
|
301
|
+
if ((c1 >>> 0) > 5 || (c2 >>> 0) > 9) return false;
|
|
302
|
+
let i = 8;
|
|
303
|
+
if (i < n && s.charCodeAt(i) === 46) {
|
|
304
|
+
i++;
|
|
305
|
+
const start = i;
|
|
306
|
+
while (i < n) {
|
|
307
|
+
const c = s.charCodeAt(i) - 48;
|
|
308
|
+
if ((c >>> 0) > 9) break;
|
|
309
|
+
i++;
|
|
310
|
+
}
|
|
311
|
+
if (i === start) return false;
|
|
312
|
+
}
|
|
313
|
+
if (i === n) return true;
|
|
314
|
+
const z = s.charCodeAt(i);
|
|
315
|
+
if (z === 90 || z === 122) return i === n - 1;
|
|
316
|
+
if (z !== 43 && z !== 45) return false;
|
|
317
|
+
if (n - i !== 6 || s.charCodeAt(i + 3) !== 58) return false;
|
|
318
|
+
for (let k = 1; k <= 5; k++) {
|
|
319
|
+
if (k === 3) continue;
|
|
320
|
+
if ((s.charCodeAt(i + k) - 48 >>> 0) > 9) return false;
|
|
321
|
+
}
|
|
322
|
+
return true;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// Anything without a control character or whitespace.
|
|
326
|
+
function uriReference (s) {
|
|
327
|
+
return noReserved(s, 0);
|
|
328
|
+
}
|
|
329
|
+
|
|
198
330
|
// Source twins. `v` is the expression holding the string; when `isStr` is
|
|
199
331
|
// false the check is wrapped in a typeof guard, matching FORMAT_CODEGEN's
|
|
200
332
|
// convention. Each returns a statement that `return false`s on mismatch.
|
|
@@ -205,14 +337,24 @@ function dateSource (v, isStr) {
|
|
|
205
337
|
}
|
|
206
338
|
|
|
207
339
|
function dateTimeSource (v, isStr) {
|
|
208
|
-
return guard(v, isStr, `const _n=${v}.length;if(_n<20)return false
|
|
209
|
-
`
|
|
210
|
-
`const
|
|
340
|
+
return guard(v, isStr, `const _n=${v}.length;if(_n<20)return false;` +
|
|
341
|
+
`if(${v}.charCodeAt(4)!==45||${v}.charCodeAt(7)!==45)return false;` +
|
|
342
|
+
`const _sep=${v}.charCodeAt(10);if(_sep!==84&&_sep!==116)return false;` +
|
|
343
|
+
`if(${v}.charCodeAt(13)!==58||${v}.charCodeAt(16)!==58)return false;` +
|
|
344
|
+
`const _y0=${v}.charCodeAt(0)-48,_y1=${v}.charCodeAt(1)-48,_y2=${v}.charCodeAt(2)-48,_y3=${v}.charCodeAt(3)-48;` +
|
|
345
|
+
'if((_y0>>>0)>9||(_y1>>>0)>9||(_y2>>>0)>9||(_y3>>>0)>9)return false;' +
|
|
346
|
+
'const _y=_y0*1000+_y1*100+_y2*10+_y3;' +
|
|
347
|
+
`const _mo0=${v}.charCodeAt(5)-48,_mo1=${v}.charCodeAt(6)-48;` +
|
|
348
|
+
'if((_mo0>>>0)>9||(_mo1>>>0)>9)return false;const _mo=_mo0*10+_mo1;if(_mo<1||_mo>12)return false;' +
|
|
211
349
|
`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);` +
|
|
212
|
-
`const
|
|
213
|
-
|
|
214
|
-
`
|
|
215
|
-
|
|
350
|
+
`const _d0=${v}.charCodeAt(8)-48,_d1=${v}.charCodeAt(9)-48;` +
|
|
351
|
+
'if((_d0>>>0)>9||(_d1>>>0)>9)return false;const _d=_d0*10+_d1;if(_d<1||_d>_dm)return false;' +
|
|
352
|
+
`const _h0=${v}.charCodeAt(11)-48,_h1=${v}.charCodeAt(12)-48;` +
|
|
353
|
+
'if((_h0>>>0)>9||(_h1>>>0)>9||_h0*10+_h1>23)return false;' +
|
|
354
|
+
`const _mi0=${v}.charCodeAt(14)-48,_mi1=${v}.charCodeAt(15)-48;` +
|
|
355
|
+
'if((_mi0>>>0)>5||(_mi1>>>0)>9)return false;' +
|
|
356
|
+
`const _se0=${v}.charCodeAt(17)-48,_se1=${v}.charCodeAt(18)-48;` +
|
|
357
|
+
'if((_se0>>>0)>5||(_se1>>>0)>9)return false;' +
|
|
216
358
|
`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}` +
|
|
217
359
|
`const _tz=${v}.charCodeAt(_i2);` +
|
|
218
360
|
`if(_tz===90||_tz===122){if(_i2!==_n-1)return false}` +
|
|
@@ -234,6 +376,45 @@ function hostnameSource (v, isStr) {
|
|
|
234
376
|
`if(_ll===0||_pv===45)return false`);
|
|
235
377
|
}
|
|
236
378
|
|
|
379
|
+
function uuidSource (v, isStr) {
|
|
380
|
+
const run = (from, to) => `for(let _ui=${from};_ui<${to};_ui++){const _uc=${v}.charCodeAt(_ui);` +
|
|
381
|
+
'if(!((_uc-48>>>0)<10||((_uc|32)-97>>>0)<6))return false}';
|
|
382
|
+
return guard(v, isStr, `if(${v}.length!==36)return false;` +
|
|
383
|
+
`if(${v}.charCodeAt(8)!==45||${v}.charCodeAt(13)!==45||` +
|
|
384
|
+
`${v}.charCodeAt(18)!==45||${v}.charCodeAt(23)!==45)return false;` +
|
|
385
|
+
run(0, 8) + run(9, 13) + run(14, 18) + run(19, 23) + run(24, 36));
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
function timeSource (v, isStr) {
|
|
389
|
+
return guard(v, isStr, `const _n=${v}.length;if(_n<8)return false;` +
|
|
390
|
+
`const _h1=${v}.charCodeAt(0)-48,_h2=${v}.charCodeAt(1)-48;` +
|
|
391
|
+
'if((_h1>>>0)>9||(_h2>>>0)>9||_h1*10+_h2>23)return false;' +
|
|
392
|
+
`if(${v}.charCodeAt(2)!==58||${v}.charCodeAt(5)!==58)return false;` +
|
|
393
|
+
`const _m1=${v}.charCodeAt(3)-48,_m2=${v}.charCodeAt(4)-48;` +
|
|
394
|
+
'if((_m1>>>0)>5||(_m2>>>0)>9)return false;' +
|
|
395
|
+
`const _s1=${v}.charCodeAt(6)-48,_s2=${v}.charCodeAt(7)-48;` +
|
|
396
|
+
'if((_s1>>>0)>5||(_s2>>>0)>9)return false;' +
|
|
397
|
+
`let _ti=8;if(_ti<_n&&${v}.charCodeAt(_ti)===46){_ti++;const _tf=_ti;` +
|
|
398
|
+
`while(_ti<_n){const _tc=${v}.charCodeAt(_ti)-48;if((_tc>>>0)>9)break;_ti++}` +
|
|
399
|
+
'if(_ti===_tf)return false}' +
|
|
400
|
+
'if(_ti!==_n){' +
|
|
401
|
+
`const _tz=${v}.charCodeAt(_ti);` +
|
|
402
|
+
'if(_tz===90||_tz===122){if(_ti!==_n-1)return false}' +
|
|
403
|
+
'else{if(_tz!==43&&_tz!==45)return false;' +
|
|
404
|
+
`if(_n-_ti!==6||${v}.charCodeAt(_ti+3)!==58)return false;` +
|
|
405
|
+
`if((${v}.charCodeAt(_ti+1)-48>>>0)>9||(${v}.charCodeAt(_ti+2)-48>>>0)>9||` +
|
|
406
|
+
`(${v}.charCodeAt(_ti+4)-48>>>0)>9||(${v}.charCodeAt(_ti+5)-48>>>0)>9)return false}}`);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
function uriSource (v, isStr) {
|
|
410
|
+
return guard(v, isStr, `const _n=${v}.length;if(_n===0)return false;` +
|
|
411
|
+
`const _f=${v}.charCodeAt(0);if(!((_f>=97&&_f<=122)||(_f>=65&&_f<=90)))return false;` +
|
|
412
|
+
`let _co=-1;for(let _i=1;_i<_n;_i++){const _c=${v}.charCodeAt(_i);if(_c===58){_co=_i;break}` +
|
|
413
|
+
`if(!((_c>=48&&_c<=57)||(_c>=97&&_c<=122)||(_c>=65&&_c<=90)||_c===43||_c===45||_c===46))return false}` +
|
|
414
|
+
`if(_co===-1)return false;` +
|
|
415
|
+
noReservedSource(v, '_co+1'));
|
|
416
|
+
}
|
|
417
|
+
|
|
237
418
|
function ipv6Source (v, isStr) {
|
|
238
419
|
return guard(v, isStr, `const _n=${v}.length;if(_n<2||_n>45)return false;let _end=_n,_g=0;` +
|
|
239
420
|
`const _dot=${v}.indexOf('.');` +
|
|
@@ -259,4 +440,4 @@ function ipv4Source (v, isStr) {
|
|
|
259
440
|
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`);
|
|
260
441
|
}
|
|
261
442
|
|
|
262
|
-
module.exports = { date, ipv4, dateTime, ipv6, hostname, dateSource, ipv4Source, dateTimeSource, ipv6Source, hostnameSource };
|
|
443
|
+
module.exports = { date, ipv4, dateTime, ipv6, hostname, uri, uriReference, uuid, time, noReserved, dateSource, ipv4Source, dateTimeSource, ipv6Source, hostnameSource, uriSource, uuidSource, timeSource, noReservedSource };
|
package/lib/interpreter.js
CHANGED
|
@@ -252,11 +252,11 @@ const FORMAT_CHECKS = {
|
|
|
252
252
|
email: (s) => { const at = s.indexOf('@'); return at > 0 && at < s.length - 1 && s.indexOf('.', at) > at + 1; },
|
|
253
253
|
date: _formats.date,
|
|
254
254
|
'date-time': _formats.dateTime,
|
|
255
|
-
time:
|
|
255
|
+
time: _formats.time,
|
|
256
256
|
duration: (s) => /^P(?:\d+Y)?(?:\d+M)?(?:\d+W)?(?:\d+D)?(?:T(?:\d+H)?(?:\d+M)?(?:\d+(?:\.\d+)?S)?)?$/.test(s) && s !== 'P' && !s.endsWith('T'),
|
|
257
|
-
uuid:
|
|
258
|
-
uri:
|
|
259
|
-
'uri-reference':
|
|
257
|
+
uuid: _formats.uuid,
|
|
258
|
+
uri: _formats.uri,
|
|
259
|
+
'uri-reference': _formats.uriReference,
|
|
260
260
|
ipv4: _formats.ipv4,
|
|
261
261
|
ipv6: _formats.ipv6,
|
|
262
262
|
hostname: _formats.hostname,
|
package/lib/js-compiler.js
CHANGED
|
@@ -762,7 +762,7 @@ const _formats = require('./formats')
|
|
|
762
762
|
const FORMAT_CHECKS = {
|
|
763
763
|
email: (s) => { const at = s.indexOf('@'); return at > 0 && at < s.length - 1 && s.indexOf('.', at) > at + 1 },
|
|
764
764
|
date: _formats.date,
|
|
765
|
-
uuid:
|
|
765
|
+
uuid: _formats.uuid,
|
|
766
766
|
ipv4: _formats.ipv4,
|
|
767
767
|
hostname: _formats.hostname,
|
|
768
768
|
}
|
|
@@ -2967,22 +2967,16 @@ const FORMAT_CODEGEN = {
|
|
|
2967
2967
|
: `if(typeof ${v}==='string'){const _at=${v}.indexOf('@');if(_at<=0||_at>=${v}.length-1||${v}.indexOf('.',_at)<=_at+1)return false}`
|
|
2968
2968
|
},
|
|
2969
2969
|
date: _formats.dateSource,
|
|
2970
|
-
uuid:
|
|
2971
|
-
? `if(${v}.length!==36||!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(${v}))return false`
|
|
2972
|
-
: `if(typeof ${v}==='string'&&(${v}.length!==36||!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(${v})))return false`,
|
|
2970
|
+
uuid: _formats.uuidSource,
|
|
2973
2971
|
'date-time': _formats.dateTimeSource,
|
|
2974
|
-
time:
|
|
2975
|
-
? `if(!/^([01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(\\.\\d+)?(Z|[+-]\\d{2}:\\d{2})?$/.test(${v}))return false`
|
|
2976
|
-
: `if(typeof ${v}==='string'&&!/^([01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(\\.\\d+)?(Z|[+-]\\d{2}:\\d{2})?$/.test(${v}))return false`,
|
|
2972
|
+
time: _formats.timeSource,
|
|
2977
2973
|
duration: (v, isStr) => isStr
|
|
2978
2974
|
? `if(!/^P(?:\\d+Y)?(?:\\d+M)?(?:\\d+W)?(?:\\d+D)?(?:T(?:\\d+H)?(?:\\d+M)?(?:\\d+(?:\\.\\d+)?S)?)?$/.test(${v})||${v}==='P'||${v}.endsWith('T'))return false`
|
|
2979
2975
|
: `if(typeof ${v}==='string'&&(!/^P(?:\\d+Y)?(?:\\d+M)?(?:\\d+W)?(?:\\d+D)?(?:T(?:\\d+H)?(?:\\d+M)?(?:\\d+(?:\\.\\d+)?S)?)?$/.test(${v})||${v}==='P'||${v}.endsWith('T')))return false`,
|
|
2980
|
-
uri:
|
|
2981
|
-
? `if(!/^[a-zA-Z][a-zA-Z0-9+.-]*:/.test(${v})||/[\\s\\u0000-\\u001f\\u007f]/.test(${v}))return false`
|
|
2982
|
-
: `if(typeof ${v}==='string'&&(!/^[a-zA-Z][a-zA-Z0-9+.-]*:/.test(${v})||/[\\s\\u0000-\\u001f\\u007f]/.test(${v})))return false`,
|
|
2976
|
+
uri: _formats.uriSource,
|
|
2983
2977
|
'uri-reference': (v, isStr) => isStr
|
|
2984
|
-
? `
|
|
2985
|
-
: `if(typeof ${v}==='string'
|
|
2978
|
+
? `{${_formats.noReservedSource(v, '0')}}`
|
|
2979
|
+
: `if(typeof ${v}==='string'){${_formats.noReservedSource(v, '0')}}`,
|
|
2986
2980
|
ipv4: _formats.ipv4Source,
|
|
2987
2981
|
ipv6: _formats.ipv6Source,
|
|
2988
2982
|
hostname: _formats.hostnameSource,
|
package/lib/version.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ata-validator",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.12.0",
|
|
4
4
|
"description": "JSON Schema validation with first-class TypeScript and zero runtime cost. AOT compile to per-schema ESM modules with zero validator dependency. Generic Validator<T> for TypeBox/Zod/Valibot composition. Optional runtime API. Standard Schema V1 compatible.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"module": "index.mjs",
|
|
@@ -112,13 +112,13 @@
|
|
|
112
112
|
"LICENSE"
|
|
113
113
|
],
|
|
114
114
|
"optionalDependencies": {
|
|
115
|
-
"@ata-validator/native-darwin-arm64": "1.
|
|
116
|
-
"@ata-validator/native-darwin-x64": "1.
|
|
117
|
-
"@ata-validator/native-linux-x64-gnu": "1.
|
|
118
|
-
"@ata-validator/native-linux-arm64-gnu": "1.
|
|
119
|
-
"@ata-validator/native-linux-x64-musl": "1.
|
|
120
|
-
"@ata-validator/native-linux-arm64-musl": "1.
|
|
121
|
-
"@ata-validator/native-win32-x64": "1.
|
|
115
|
+
"@ata-validator/native-darwin-arm64": "1.12.0",
|
|
116
|
+
"@ata-validator/native-darwin-x64": "1.12.0",
|
|
117
|
+
"@ata-validator/native-linux-x64-gnu": "1.12.0",
|
|
118
|
+
"@ata-validator/native-linux-arm64-gnu": "1.12.0",
|
|
119
|
+
"@ata-validator/native-linux-x64-musl": "1.12.0",
|
|
120
|
+
"@ata-validator/native-linux-arm64-musl": "1.12.0",
|
|
121
|
+
"@ata-validator/native-win32-x64": "1.12.0"
|
|
122
122
|
},
|
|
123
123
|
"peerDependencies": {
|
|
124
124
|
"yaml": "^2.0.0"
|