ascertain 3.2.21 → 3.2.22
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 +49 -29
- package/build/index.cjs +439 -74
- package/build/index.cjs.map +1 -1
- package/build/index.d.ts +7 -8
- package/build/index.js +439 -74
- package/build/index.js.map +1 -1
- package/package.json +6 -4
- package/src/index.ts +439 -81
- package/build/__bench__/benchmark.d.ts +0 -1
package/build/index.js
CHANGED
|
@@ -8,6 +8,11 @@ const OPTIONAL = Symbol.for('@@optional');
|
|
|
8
8
|
const TUPLE = Symbol.for('@@tuple');
|
|
9
9
|
const DISCRIMINATED = Symbol.for('@@discriminated');
|
|
10
10
|
const CHECK = Symbol.for('@@check');
|
|
11
|
+
const ERR = Symbol.for('@@err');
|
|
12
|
+
function ErrCtor(message) {
|
|
13
|
+
this.message = message;
|
|
14
|
+
}
|
|
15
|
+
ErrCtor.prototype[ERR] = true;
|
|
11
16
|
const OrCtor = function(schemas) {
|
|
12
17
|
this.schemas = schemas;
|
|
13
18
|
};
|
|
@@ -109,7 +114,39 @@ export const oneOf = (values, message)=>{
|
|
|
109
114
|
].map(toLiteral).join(', ')}], got \${${v}}\``
|
|
110
115
|
}));
|
|
111
116
|
};
|
|
112
|
-
|
|
117
|
+
const utf8Encoder = new TextEncoder();
|
|
118
|
+
const utf8Decoder = new TextDecoder('utf-8');
|
|
119
|
+
const fromBase64Bytes = (value)=>{
|
|
120
|
+
const bin = atob(value);
|
|
121
|
+
const out = new Uint8Array(bin.length);
|
|
122
|
+
for(let i = 0; i < bin.length; i++)out[i] = bin.charCodeAt(i);
|
|
123
|
+
return out;
|
|
124
|
+
};
|
|
125
|
+
export const fromBase64 = (value)=>utf8Decoder.decode(fromBase64Bytes(value));
|
|
126
|
+
const HEX_LUT = (()=>{
|
|
127
|
+
const t = new Int8Array(256).fill(-1);
|
|
128
|
+
for(let i = 0; i < 10; i++)t[48 + i] = i;
|
|
129
|
+
for(let i = 0; i < 6; i++){
|
|
130
|
+
t[97 + i] = 10 + i;
|
|
131
|
+
t[65 + i] = 10 + i;
|
|
132
|
+
}
|
|
133
|
+
return t;
|
|
134
|
+
})();
|
|
135
|
+
const fromHex = (value)=>{
|
|
136
|
+
let start = 0;
|
|
137
|
+
if (value.length >= 2 && value.charCodeAt(0) === 48 && (value.charCodeAt(1) | 32) === 120) {
|
|
138
|
+
start = 2;
|
|
139
|
+
}
|
|
140
|
+
const digits = value.length - start;
|
|
141
|
+
if (digits === 0 || digits % 2 !== 0) throw new TypeError('invalid hex length');
|
|
142
|
+
const out = new Uint8Array(digits / 2);
|
|
143
|
+
for(let i = 0; i < out.length; i++){
|
|
144
|
+
const byte = HEX_LUT[value.charCodeAt(start + i * 2)] << 4 | HEX_LUT[value.charCodeAt(start + i * 2 + 1)];
|
|
145
|
+
if (byte < 0) throw new TypeError('invalid hex digit');
|
|
146
|
+
out[i] = byte;
|
|
147
|
+
}
|
|
148
|
+
return out;
|
|
149
|
+
};
|
|
113
150
|
const MULTIPLIERS = {
|
|
114
151
|
ms: 1,
|
|
115
152
|
s: 1000,
|
|
@@ -119,7 +156,7 @@ const MULTIPLIERS = {
|
|
|
119
156
|
w: 604800000
|
|
120
157
|
};
|
|
121
158
|
const TIME_REGEX = /^(\d*\.?\d*)(ms|s|m|h|d|w)?$/;
|
|
122
|
-
export const asError = (message)=>new
|
|
159
|
+
export const asError = (message)=>new ErrCtor(message);
|
|
123
160
|
export const as = {
|
|
124
161
|
string: (value)=>{
|
|
125
162
|
return typeof value === 'string' ? value : asError(`Invalid value "${value}", expected a string`);
|
|
@@ -171,6 +208,16 @@ export const as = {
|
|
|
171
208
|
} catch {
|
|
172
209
|
return asError(`Invalid value ${value}, expected a valid base64 string`);
|
|
173
210
|
}
|
|
211
|
+
},
|
|
212
|
+
data: (value, type = 'utf-8')=>{
|
|
213
|
+
if (typeof value !== 'string') return asError(`Invalid value ${value}, expected a string`);
|
|
214
|
+
try {
|
|
215
|
+
if (type === 'hex') return fromHex(value);
|
|
216
|
+
if (type === 'base64') return fromBase64Bytes(value);
|
|
217
|
+
return utf8Encoder.encode(value);
|
|
218
|
+
} catch {
|
|
219
|
+
return asError(`Invalid value ${value}, expected a valid ${type} string`);
|
|
220
|
+
}
|
|
174
221
|
}
|
|
175
222
|
};
|
|
176
223
|
const DATETIME_RE = /^\d{4}-[01]\d-[0-3]\d[t\s](?:[0-2]\d:[0-5]\d:[0-5]\d|23:59:60)(?:\.\d+)?(?:z|[+-]\d{2}(?::?\d{2})?)$/i;
|
|
@@ -257,6 +304,7 @@ class Context {
|
|
|
257
304
|
registry = [];
|
|
258
305
|
lookupMap = new Map();
|
|
259
306
|
varIndex = 0;
|
|
307
|
+
pure = false;
|
|
260
308
|
register(value) {
|
|
261
309
|
const index = this.lookupMap.get(value);
|
|
262
310
|
if (index !== undefined) {
|
|
@@ -273,32 +321,128 @@ class Context {
|
|
|
273
321
|
}
|
|
274
322
|
}
|
|
275
323
|
const isTagged = (schema)=>schema?.[$op] !== undefined;
|
|
276
|
-
const
|
|
324
|
+
const buildDynamicPathExpr = (staticPath, dynamicParts)=>`[${[
|
|
325
|
+
...staticPath.map((k)=>JSON.stringify(k)),
|
|
326
|
+
...dynamicParts
|
|
327
|
+
].join(',')}]`;
|
|
328
|
+
const childMode = (mode, key, ctx)=>{
|
|
329
|
+
const carryReady = !mode.firstError && mode.issuesReady;
|
|
277
330
|
if (typeof key === 'object' && 'dynamic' in key) {
|
|
278
|
-
|
|
331
|
+
const dynamicParts = [
|
|
332
|
+
...mode.dynamicParts ?? [],
|
|
333
|
+
key.dynamic
|
|
334
|
+
];
|
|
335
|
+
const m = {
|
|
336
|
+
fast: false,
|
|
337
|
+
firstError: mode.firstError,
|
|
338
|
+
issues: mode.issues,
|
|
339
|
+
path: mode.path,
|
|
340
|
+
pathExpr: buildDynamicPathExpr(mode.path, dynamicParts),
|
|
341
|
+
dynamicParts
|
|
342
|
+
};
|
|
343
|
+
if (carryReady) m.issuesReady = true;
|
|
344
|
+
return m;
|
|
345
|
+
}
|
|
346
|
+
if (mode.dynamicParts) {
|
|
347
|
+
const dynamicParts = [
|
|
348
|
+
...mode.dynamicParts,
|
|
349
|
+
JSON.stringify(key)
|
|
350
|
+
];
|
|
351
|
+
const m = {
|
|
279
352
|
fast: false,
|
|
280
353
|
firstError: mode.firstError,
|
|
281
354
|
issues: mode.issues,
|
|
282
355
|
path: mode.path,
|
|
283
|
-
pathExpr:
|
|
356
|
+
pathExpr: buildDynamicPathExpr(mode.path, dynamicParts),
|
|
357
|
+
dynamicParts
|
|
284
358
|
};
|
|
359
|
+
if (carryReady) m.issuesReady = true;
|
|
360
|
+
return m;
|
|
285
361
|
}
|
|
286
362
|
const newPath = [
|
|
287
363
|
...mode.path,
|
|
288
364
|
key
|
|
289
365
|
];
|
|
290
|
-
|
|
366
|
+
const pathExpr = `reg[${ctx.register(Object.freeze(newPath))}]`;
|
|
367
|
+
const m = {
|
|
291
368
|
fast: false,
|
|
292
369
|
firstError: mode.firstError,
|
|
293
370
|
issues: mode.issues,
|
|
294
371
|
path: newPath,
|
|
295
|
-
pathExpr
|
|
372
|
+
pathExpr
|
|
296
373
|
};
|
|
374
|
+
if (carryReady) m.issuesReady = true;
|
|
375
|
+
return m;
|
|
297
376
|
};
|
|
298
377
|
const toLiteral = (value)=>typeof value === 'bigint' ? `${value}n` : JSON.stringify(value);
|
|
378
|
+
const collapsibleTypeOf = (s)=>s === String ? 'string' : s === Number ? 'number' : s === Boolean ? 'boolean' : s === BigInt ? 'bigint' : s === Symbol ? 'symbol' : typeof s === 'function' && s?.name === 'Function' ? 'function' : null;
|
|
379
|
+
const buildCollapsedOr = (schemas, v)=>{
|
|
380
|
+
let hasNull = false;
|
|
381
|
+
let hasUndefined = false;
|
|
382
|
+
const groups = new Map();
|
|
383
|
+
for (const s of schemas){
|
|
384
|
+
if (s === null) {
|
|
385
|
+
hasNull = true;
|
|
386
|
+
continue;
|
|
387
|
+
}
|
|
388
|
+
if (s === undefined) {
|
|
389
|
+
hasUndefined = true;
|
|
390
|
+
continue;
|
|
391
|
+
}
|
|
392
|
+
const ct = collapsibleTypeOf(s);
|
|
393
|
+
if (ct) {
|
|
394
|
+
let g = groups.get(ct);
|
|
395
|
+
if (!g) {
|
|
396
|
+
g = {
|
|
397
|
+
ctor: false,
|
|
398
|
+
literals: []
|
|
399
|
+
};
|
|
400
|
+
groups.set(ct, g);
|
|
401
|
+
}
|
|
402
|
+
g.ctor = true;
|
|
403
|
+
continue;
|
|
404
|
+
}
|
|
405
|
+
const t = typeof s;
|
|
406
|
+
let g = groups.get(t);
|
|
407
|
+
if (!g) {
|
|
408
|
+
g = {
|
|
409
|
+
ctor: false,
|
|
410
|
+
literals: []
|
|
411
|
+
};
|
|
412
|
+
groups.set(t, g);
|
|
413
|
+
}
|
|
414
|
+
g.literals.push(s);
|
|
415
|
+
}
|
|
416
|
+
const clauses = [];
|
|
417
|
+
if (hasNull) clauses.push(`${v} === null`);
|
|
418
|
+
if (hasUndefined) clauses.push(`${v} === undefined`);
|
|
419
|
+
for (const [type, { ctor, literals }] of groups){
|
|
420
|
+
if (ctor) {
|
|
421
|
+
clauses.push(type === 'number' ? `(typeof ${v} === 'number' && ${v} === ${v})` : `typeof ${v} === '${type}'`);
|
|
422
|
+
} else if (literals.length === 1) {
|
|
423
|
+
clauses.push(`${v} === ${toLiteral(literals[0])}`);
|
|
424
|
+
} else {
|
|
425
|
+
clauses.push(`(typeof ${v} === '${type}' && (${literals.map((l)=>`${v} === ${toLiteral(l)}`).join(' || ')}))`);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
return clauses.join(' || ');
|
|
429
|
+
};
|
|
430
|
+
const buildExpectedDesc = (schemas)=>{
|
|
431
|
+
const parts = [];
|
|
432
|
+
for (const s of schemas){
|
|
433
|
+
if (s === null) parts.push('null');
|
|
434
|
+
else if (s === undefined) parts.push('undefined');
|
|
435
|
+
else if (collapsibleTypeOf(s) !== null) parts.push(s.name);
|
|
436
|
+
else parts.push(toLiteral(s));
|
|
437
|
+
}
|
|
438
|
+
return parts.join(', ');
|
|
439
|
+
};
|
|
440
|
+
const isCollapsible = (s)=>s === null || s === undefined || typeof s !== 'object' && typeof s !== 'function' && typeof s !== 'symbol' || collapsibleTypeOf(s) !== null;
|
|
299
441
|
const codeGen = (schema, context, valuePath, mode)=>{
|
|
300
|
-
const emit = mode.fast ? null : mode.firstError ? (msg)=>`${mode.issues} = [{ message: ${msg}, path: ${mode.pathExpr} }]; return ${mode.issues};` : (msg)=>`(${mode.issues} || (${mode.issues} = [])).push({ message: ${msg}, path: ${mode.pathExpr} });`;
|
|
442
|
+
const emit = mode.fast ? null : mode.firstError ? (msg)=>`${mode.issues} = [{ message: ${msg}, path: ${mode.pathExpr} }]; return ${mode.issues};` : !mode.firstError && mode.issuesReady ? (msg)=>`${mode.issues}.push({ message: ${msg}, path: ${mode.pathExpr} });` : (msg)=>`(${mode.issues} || (${mode.issues} = [])).push({ message: ${msg}, path: ${mode.pathExpr} });`;
|
|
301
443
|
const fail = mode.fast ? mode.onFail ?? 'return false;' : '';
|
|
444
|
+
const errChk = (v)=>context.pure ? '' : ` || (typeof ${v} === 'object' && ${v} !== null && ${v}[err] === true)`;
|
|
445
|
+
const errBranch = (v)=>context.pure ? '' : `else if (typeof ${v} === 'object' && ${v} !== null && ${v}[err] === true) { ${emit(`\`\${${v}.message}\``)} }`;
|
|
302
446
|
if (isTagged(schema)) {
|
|
303
447
|
const tag = schema[$op];
|
|
304
448
|
if (tag === AND) {
|
|
@@ -309,6 +453,27 @@ const codeGen = (schema, context, valuePath, mode)=>{
|
|
|
309
453
|
const valueAlias = context.unique('v');
|
|
310
454
|
const foundValid = context.unique('valid');
|
|
311
455
|
if (mode.fast) {
|
|
456
|
+
const collapsible = [];
|
|
457
|
+
const complex = [];
|
|
458
|
+
for (const s of schema.schemas){
|
|
459
|
+
if (isCollapsible(s)) collapsible.push(s);
|
|
460
|
+
else complex.push(s);
|
|
461
|
+
}
|
|
462
|
+
if (collapsible.length > 0 && complex.length === 0) {
|
|
463
|
+
return `const ${valueAlias} = ${valuePath};\nif (!(${buildCollapsedOr(collapsible, valueAlias)})) { ${fail} }`;
|
|
464
|
+
}
|
|
465
|
+
if (collapsible.length > 0) {
|
|
466
|
+
const condition = buildCollapsedOr(collapsible, valueAlias);
|
|
467
|
+
const branches = complex.map((s)=>{
|
|
468
|
+
const branchValid = context.unique('valid');
|
|
469
|
+
const branchCode = codeGen(s, context, valueAlias, {
|
|
470
|
+
...mode,
|
|
471
|
+
onFail: `${branchValid} = false;`
|
|
472
|
+
});
|
|
473
|
+
return `if (!${foundValid}) { let ${branchValid} = true; ${branchCode} if (${branchValid}) { ${foundValid} = true; } }`;
|
|
474
|
+
});
|
|
475
|
+
return `const ${valueAlias} = ${valuePath};\nlet ${foundValid} = ${condition};\n${branches.join('\n')}\nif (!${foundValid}) { ${fail} }`;
|
|
476
|
+
}
|
|
312
477
|
const branches = schema.schemas.map((s)=>{
|
|
313
478
|
const branchValid = context.unique('valid');
|
|
314
479
|
const branchCode = codeGen(s, context, valueAlias, {
|
|
@@ -319,6 +484,11 @@ const codeGen = (schema, context, valuePath, mode)=>{
|
|
|
319
484
|
});
|
|
320
485
|
return `const ${valueAlias} = ${valuePath};\nlet ${foundValid} = false;\n${branches.join('\n')}\nif (!${foundValid}) { ${fail} }`;
|
|
321
486
|
} else if (mode.firstError) {
|
|
487
|
+
if (schema.schemas.every(isCollapsible)) {
|
|
488
|
+
const condition = buildCollapsedOr(schema.schemas, valueAlias);
|
|
489
|
+
const expected = buildExpectedDesc(schema.schemas);
|
|
490
|
+
return `const ${valueAlias} = ${valuePath};\nif (!(${condition})) { ${mode.issues} = [{ message: \`Invalid value \${${valueAlias}}, expected one of: ${expected}\`, path: ${mode.pathExpr} }]; return ${mode.issues}; }`;
|
|
491
|
+
}
|
|
322
492
|
const firstBranchIssues = context.unique('iss');
|
|
323
493
|
const branches = schema.schemas.map((s, idx)=>{
|
|
324
494
|
const branchIssues = context.unique('iss');
|
|
@@ -336,6 +506,12 @@ const codeGen = (schema, context, valuePath, mode)=>{
|
|
|
336
506
|
});
|
|
337
507
|
return `const ${valueAlias} = ${valuePath};\nlet ${firstBranchIssues};\nlet ${foundValid} = false;\n${branches.join('\n')}\nif (!${foundValid}) { return ${firstBranchIssues}; }`;
|
|
338
508
|
} else {
|
|
509
|
+
if (schema.schemas.every(isCollapsible)) {
|
|
510
|
+
const condition = buildCollapsedOr(schema.schemas, valueAlias);
|
|
511
|
+
const expected = buildExpectedDesc(schema.schemas);
|
|
512
|
+
const push = mode.issuesReady ? `${mode.issues}.push({ message: \`Invalid value \${${valueAlias}}, expected one of: ${expected}\`, path: ${mode.pathExpr} });` : `(${mode.issues} || (${mode.issues} = [])).push({ message: \`Invalid value \${${valueAlias}}, expected one of: ${expected}\`, path: ${mode.pathExpr} });`;
|
|
513
|
+
return `const ${valueAlias} = ${valuePath};\nif (!(${condition})) { ${push} }`;
|
|
514
|
+
}
|
|
339
515
|
const localIssues = context.unique('iss');
|
|
340
516
|
const branches = schema.schemas.map((s)=>{
|
|
341
517
|
const branchIssues = context.unique('iss');
|
|
@@ -348,11 +524,42 @@ const codeGen = (schema, context, valuePath, mode)=>{
|
|
|
348
524
|
});
|
|
349
525
|
return `if (!${foundValid}) { let ${branchIssues}; ${branchCode} if (!${branchIssues}) { ${foundValid} = true; } else { ${localIssues}.push(...${branchIssues}); } }`;
|
|
350
526
|
});
|
|
351
|
-
|
|
527
|
+
const pushExpr = !mode.fast && !mode.firstError && mode.issuesReady ? `${mode.issues}.push(...${localIssues})` : `(${mode.issues} || (${mode.issues} = [])).push(...${localIssues})`;
|
|
528
|
+
return `const ${valueAlias} = ${valuePath};\nconst ${localIssues} = [];\nlet ${foundValid} = false;\n${branches.join('\n')}\nif (!${foundValid}) { ${pushExpr}; }`;
|
|
352
529
|
}
|
|
353
530
|
} else if (tag === OPTIONAL) {
|
|
354
531
|
const valueAlias = context.unique('v');
|
|
355
|
-
|
|
532
|
+
const inner = schema.schemas[0];
|
|
533
|
+
if (!mode.fast && typeof inner === 'function') {
|
|
534
|
+
const iname = inner?.name;
|
|
535
|
+
const is = inner;
|
|
536
|
+
const pt = is === String ? 'string' : is === Number ? 'number' : is === Boolean ? 'boolean' : is === BigInt ? 'bigint' : is === Symbol ? 'symbol' : null;
|
|
537
|
+
if (pt) {
|
|
538
|
+
const typeMsgs = Object.fromEntries([
|
|
539
|
+
'string',
|
|
540
|
+
'number',
|
|
541
|
+
'boolean',
|
|
542
|
+
'bigint',
|
|
543
|
+
'symbol',
|
|
544
|
+
'undefined',
|
|
545
|
+
'object',
|
|
546
|
+
'function'
|
|
547
|
+
].map((t)=>[
|
|
548
|
+
t,
|
|
549
|
+
`Invalid type ${t}, expected type ${iname}`
|
|
550
|
+
]));
|
|
551
|
+
const typeMsgIdx = context.register(typeMsgs);
|
|
552
|
+
const lines = [
|
|
553
|
+
`const ${valueAlias} = ${valuePath};`,
|
|
554
|
+
`if (${valueAlias} !== undefined && ${valueAlias} !== null) {`
|
|
555
|
+
];
|
|
556
|
+
lines.push(`if (typeof ${valueAlias} !== '${pt}') { ${emit(`reg[${typeMsgIdx}][typeof ${valueAlias}]`)} }`);
|
|
557
|
+
if (pt === 'number') lines.push(`else if (${valueAlias} !== ${valueAlias}) { ${emit(`"Invalid value NaN, expected a valid ${iname}"`)} }`);
|
|
558
|
+
lines.push(`}`);
|
|
559
|
+
return lines.join('\n');
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
return `const ${valueAlias} = ${valuePath};\nif (${valueAlias} !== undefined && ${valueAlias} !== null) { ${codeGen(inner, context, valueAlias, mode)} }`;
|
|
356
563
|
} else if (tag === TUPLE) {
|
|
357
564
|
const valueAlias = context.unique('v');
|
|
358
565
|
if (mode.fast) {
|
|
@@ -364,12 +571,12 @@ const codeGen = (schema, context, valuePath, mode)=>{
|
|
|
364
571
|
`else if (typeof ${valueAlias} !== 'object') { ${emit(`\`Invalid type \${typeof ${valueAlias}}, expected an instance of Array\``)} }`,
|
|
365
572
|
`else if (!Array.isArray(${valueAlias})) { ${emit(`\`Invalid instance of \${${valueAlias}.constructor?.name}, expected an instance of Array\``)} }`,
|
|
366
573
|
`else if (${valueAlias}.length !== ${schema.schemas.length}) { ${emit(`\`Invalid tuple length \${${valueAlias}.length}, expected ${schema.schemas.length}\``)} }`,
|
|
367
|
-
`else { ${schema.schemas.map((s, idx)=>codeGen(s, context, `${valueAlias}[${idx}]`, childMode(mode, idx))).join('\n')} }`
|
|
574
|
+
`else { ${schema.schemas.map((s, idx)=>codeGen(s, context, `${valueAlias}[${idx}]`, childMode(mode, idx, context))).join('\n')} }`
|
|
368
575
|
].join('\n');
|
|
369
576
|
}
|
|
370
577
|
} else if (tag === CHECK) {
|
|
371
578
|
const valueAlias = context.unique('v');
|
|
372
|
-
const ref = (v)=>`
|
|
579
|
+
const ref = (v)=>`reg[${context.register(v)}]`;
|
|
373
580
|
const { check: cond, message } = schema.compile(valueAlias, {
|
|
374
581
|
ref
|
|
375
582
|
});
|
|
@@ -396,31 +603,33 @@ const codeGen = (schema, context, valuePath, mode)=>{
|
|
|
396
603
|
schema: s
|
|
397
604
|
});
|
|
398
605
|
}
|
|
606
|
+
const genVariantProps = (s, variantMode)=>{
|
|
607
|
+
const obj = s;
|
|
608
|
+
return Object.entries(obj).filter(([k])=>k !== key).map(([k, ps])=>codeGen(ps, context, `${valueAlias}[${JSON.stringify(k)}]`, variantMode.fast ? variantMode : childMode(variantMode, k, context))).join('\n');
|
|
609
|
+
};
|
|
399
610
|
if (mode.fast) {
|
|
400
611
|
const branches = variants.map(({ value, schema: s })=>{
|
|
401
|
-
|
|
402
|
-
return `if (${discriminantAlias} === ${JSON.stringify(value)}) { ${branchCode} }`;
|
|
612
|
+
return `if (${discriminantAlias} === ${JSON.stringify(value)}) { ${genVariantProps(s, mode)} }`;
|
|
403
613
|
});
|
|
404
614
|
return [
|
|
405
615
|
`const ${valueAlias} = ${valuePath};`,
|
|
406
|
-
`if (${valueAlias} === null || ${valueAlias} === undefined || typeof ${valueAlias} !== 'object'
|
|
616
|
+
`if (${valueAlias} === null || ${valueAlias} === undefined || typeof ${valueAlias} !== 'object'${errChk(valueAlias)}) { ${fail} }`,
|
|
407
617
|
`const ${discriminantAlias} = ${valueAlias}[${keyStr}];`,
|
|
408
618
|
branches.join(' else ') + ` else { ${fail} }`
|
|
409
619
|
].join('\n');
|
|
410
620
|
} else {
|
|
411
621
|
const validValues = variants.map((v)=>JSON.stringify(v.value)).join(', ');
|
|
412
622
|
const branches = variants.map(({ value, schema: s })=>{
|
|
413
|
-
|
|
414
|
-
return `if (${discriminantAlias} === ${JSON.stringify(value)}) { ${branchCode} }`;
|
|
623
|
+
return `if (${discriminantAlias} === ${JSON.stringify(value)}) { ${genVariantProps(s, mode)} }`;
|
|
415
624
|
});
|
|
416
625
|
return [
|
|
417
626
|
`const ${valueAlias} = ${valuePath};`,
|
|
418
627
|
`if (${valueAlias} === null || ${valueAlias} === undefined) { ${emit(`\`Invalid value \${${valueAlias}}, expected non-nullable\``)} }`,
|
|
419
628
|
`else if (typeof ${valueAlias} !== 'object') { ${emit(`\`Invalid type \${typeof ${valueAlias}}, expected an object\``)} }`,
|
|
420
|
-
|
|
629
|
+
`${errBranch(valueAlias)}`,
|
|
421
630
|
`else {`,
|
|
422
631
|
` const ${discriminantAlias} = ${valueAlias}[${keyStr}];`,
|
|
423
|
-
` ${branches.join(' else ')} else { ${emit(
|
|
632
|
+
` ${branches.join(' else ')} else { ${emit(`"Invalid discriminant value " + String(${discriminantAlias}) + ", expected one of: ${validValues.replace(/"/g, "'")}"`)} }`,
|
|
424
633
|
`}`
|
|
425
634
|
].join('\n');
|
|
426
635
|
}
|
|
@@ -436,7 +645,7 @@ const codeGen = (schema, context, valuePath, mode)=>{
|
|
|
436
645
|
const checks = [
|
|
437
646
|
`typeof ${valueAlias} !== '${primitiveType}'`
|
|
438
647
|
];
|
|
439
|
-
if (primitiveType === 'number') checks.push(
|
|
648
|
+
if (primitiveType === 'number') checks.push(`${valueAlias} !== ${valueAlias}`);
|
|
440
649
|
return `const ${valueAlias} = ${valuePath};\nif (${checks.join(' || ')}) { ${fail} }`;
|
|
441
650
|
} else if (name === 'Function') {
|
|
442
651
|
return `const ${valueAlias} = ${valuePath};\nif (typeof ${valueAlias} !== 'function') { ${fail} }`;
|
|
@@ -444,28 +653,42 @@ const codeGen = (schema, context, valuePath, mode)=>{
|
|
|
444
653
|
const isError = schema === Error || schema?.prototype instanceof Error;
|
|
445
654
|
const index = context.register(schema);
|
|
446
655
|
const registryAlias = context.unique('r');
|
|
447
|
-
return `const ${valueAlias} = ${valuePath};\nconst ${registryAlias} =
|
|
656
|
+
return `const ${valueAlias} = ${valuePath};\nconst ${registryAlias} = reg[${index}];\nif (${valueAlias} === null || ${valueAlias} === undefined${isError || context.pure ? '' : ` || (typeof ${valueAlias} === 'object' && ${valueAlias}[err] === true)`} || (typeof ${valueAlias} === 'object' && !(${valueAlias} instanceof ${registryAlias})) || (typeof ${valueAlias} !== 'object' && ${valueAlias}?.constructor !== ${registryAlias}) || Number.isNaN(${valueAlias}?.valueOf?.())) { ${fail} }`;
|
|
448
657
|
}
|
|
449
658
|
} else {
|
|
450
659
|
const code = [
|
|
451
660
|
`const ${valueAlias} = ${valuePath};`
|
|
452
661
|
];
|
|
453
662
|
if (primitiveType) {
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
663
|
+
const typeMsgs = Object.fromEntries([
|
|
664
|
+
'string',
|
|
665
|
+
'number',
|
|
666
|
+
'boolean',
|
|
667
|
+
'bigint',
|
|
668
|
+
'symbol',
|
|
669
|
+
'undefined',
|
|
670
|
+
'object',
|
|
671
|
+
'function'
|
|
672
|
+
].map((t)=>[
|
|
673
|
+
t,
|
|
674
|
+
`Invalid type ${t}, expected type ${name}`
|
|
675
|
+
]));
|
|
676
|
+
const typeMsgIdx = context.register(typeMsgs);
|
|
677
|
+
code.push(`if (${valueAlias} === null || ${valueAlias} === undefined) { ${emit(`${valueAlias} === null ? "Invalid value null, expected non-nullable" : "Invalid value undefined, expected non-nullable"`)} }`);
|
|
678
|
+
code.push(`${errBranch(valueAlias)}`);
|
|
679
|
+
code.push(`else if (typeof ${valueAlias} !== '${primitiveType}') { ${emit(`reg[${typeMsgIdx}][typeof ${valueAlias}]`)} }`);
|
|
680
|
+
if (primitiveType === 'number') code.push(`else if (${valueAlias} !== ${valueAlias}) { ${emit(`"Invalid value NaN, expected a valid ${name}"`)} }`);
|
|
458
681
|
} else if (name === 'Function') {
|
|
459
682
|
code.push(`if (${valueAlias} === null || ${valueAlias} === undefined) { ${emit(`\`Invalid value \${${valueAlias}}, expected non-nullable\``)} }`);
|
|
460
|
-
code.push(
|
|
683
|
+
code.push(`${errBranch(valueAlias)}`);
|
|
461
684
|
code.push(`else if (typeof ${valueAlias} !== 'function') { ${emit(`\`Invalid type \${typeof ${valueAlias}}, expected type Function\``)} }`);
|
|
462
685
|
} else {
|
|
463
686
|
const isError = schema === Error || schema?.prototype instanceof Error;
|
|
464
687
|
const index = context.register(schema);
|
|
465
688
|
const registryAlias = context.unique('r');
|
|
466
|
-
code.push(`const ${registryAlias} =
|
|
689
|
+
code.push(`const ${registryAlias} = reg[${index}];`);
|
|
467
690
|
code.push(`if (${valueAlias} === null || ${valueAlias} === undefined) { ${emit(`\`Invalid value \${${valueAlias}}, expected non-nullable\``)} }`);
|
|
468
|
-
if (!isError) code.push(
|
|
691
|
+
if (!isError) code.push(`${errBranch(valueAlias)}`);
|
|
469
692
|
code.push(`else if (typeof ${valueAlias} === 'object' && !(${valueAlias} instanceof ${registryAlias})) { ${emit(`\`Invalid instance of \${${valueAlias}?.constructor?.name}, expected an instance of ${name}\``)} }`);
|
|
470
693
|
code.push(`else if (typeof ${valueAlias} !== 'object' && ${valueAlias}?.constructor !== ${registryAlias}) { ${emit(`\`Invalid type \${${valueAlias}?.constructor?.name}, expected type ${name}\``)} }`);
|
|
471
694
|
code.push(`else if (Number.isNaN(${valueAlias}?.valueOf?.())) { ${emit(`\`Invalid value \${${valueAlias}}, expected a valid ${name}\``)} }`);
|
|
@@ -487,11 +710,24 @@ const codeGen = (schema, context, valuePath, mode)=>{
|
|
|
487
710
|
}
|
|
488
711
|
return code;
|
|
489
712
|
} else {
|
|
713
|
+
const arrTypeMsgs = Object.fromEntries([
|
|
714
|
+
'string',
|
|
715
|
+
'number',
|
|
716
|
+
'boolean',
|
|
717
|
+
'bigint',
|
|
718
|
+
'symbol',
|
|
719
|
+
'undefined',
|
|
720
|
+
'function'
|
|
721
|
+
].map((t)=>[
|
|
722
|
+
t,
|
|
723
|
+
`Invalid type ${t}, expected an instance of Array`
|
|
724
|
+
]));
|
|
725
|
+
const arrTypeMsgIdx = context.register(arrTypeMsgs);
|
|
490
726
|
const code = [
|
|
491
727
|
`const ${valueAlias} = ${valuePath};`,
|
|
492
|
-
`if (${valueAlias} === null || ${valueAlias} === undefined) { ${emit(
|
|
493
|
-
|
|
494
|
-
`else if (typeof ${valueAlias} !== 'object') { ${emit(
|
|
728
|
+
`if (${valueAlias} === null || ${valueAlias} === undefined) { ${emit(`${valueAlias} === null ? "Invalid value null, expected non-nullable" : "Invalid value undefined, expected non-nullable"`)} }`,
|
|
729
|
+
`${errBranch(valueAlias)}`,
|
|
730
|
+
`else if (typeof ${valueAlias} !== 'object') { ${emit(`reg[${arrTypeMsgIdx}][typeof ${valueAlias}]`)} }`,
|
|
495
731
|
`else if (!Array.isArray(${valueAlias})) { ${emit(`\`Invalid instance of \${${valueAlias}.constructor?.name}, expected an instance of Array\``)} }`
|
|
496
732
|
];
|
|
497
733
|
if (schema.length > 0) {
|
|
@@ -500,10 +736,10 @@ const codeGen = (schema, context, valuePath, mode)=>{
|
|
|
500
736
|
if (schema.length === 1) {
|
|
501
737
|
code.push(`else { for (let ${key} = 0; ${key} < ${valueAlias}.length; ${key}++) { const ${value} = ${valueAlias}[${key}]; ${codeGen(schema[0], context, value, childMode(mode, {
|
|
502
738
|
dynamic: key
|
|
503
|
-
}))} } }`);
|
|
739
|
+
}, context))} } }`);
|
|
504
740
|
} else {
|
|
505
741
|
code.push(`else if (${valueAlias}.length > ${schema.length}) { ${emit(`\`Invalid tuple length \${${valueAlias}.length}, expected ${schema.length}\``)} }`);
|
|
506
|
-
code.push(`else { ${schema.map((s, idx)=>codeGen(s, context, `${valueAlias}[${idx}]`, childMode(mode, idx))).join('\n')} }`);
|
|
742
|
+
code.push(`else { ${schema.map((s, idx)=>codeGen(s, context, `${valueAlias}[${idx}]`, childMode(mode, idx, context))).join('\n')} }`);
|
|
507
743
|
}
|
|
508
744
|
}
|
|
509
745
|
return code.join('\n');
|
|
@@ -513,68 +749,127 @@ const codeGen = (schema, context, valuePath, mode)=>{
|
|
|
513
749
|
if (schema instanceof RegExp) {
|
|
514
750
|
const valueAlias = context.unique('v');
|
|
515
751
|
if (mode.fast) {
|
|
516
|
-
return `const ${valueAlias} = ${valuePath};\nif (${valueAlias} === null || ${valueAlias} === undefined
|
|
752
|
+
return `const ${valueAlias} = ${valuePath};\nif (${valueAlias} === null || ${valueAlias} === undefined${errChk(valueAlias)} || !${schema.toString()}.test(String(${valueAlias}))) { ${fail} }`;
|
|
517
753
|
} else {
|
|
518
|
-
return `const ${valueAlias} = ${valuePath};\nif (${valueAlias} === null || ${valueAlias} === undefined) { ${emit(`\`Invalid value \${${valueAlias}}, expected non-nullable\``)} }\
|
|
754
|
+
return `const ${valueAlias} = ${valuePath};\nif (${valueAlias} === null || ${valueAlias} === undefined) { ${emit(`\`Invalid value \${${valueAlias}}, expected non-nullable\``)} }\n${errBranch(valueAlias)}\n${context.pure ? '' : 'else '}if (!${schema.toString()}.test(String(${valueAlias}))) { ${emit(`\`Invalid value \${${valueAlias}}, expected to match ${schema.toString()}\``)} }`;
|
|
519
755
|
}
|
|
520
756
|
} else {
|
|
521
757
|
const valueAlias = context.unique('v');
|
|
522
758
|
if (mode.fast) {
|
|
523
|
-
|
|
759
|
+
const indexed = mode.indexed && !mode.onFail;
|
|
760
|
+
const rootFail = indexed ? 'return 0;' : fail;
|
|
761
|
+
let code = `const ${valueAlias} = ${valuePath};\nif (${valueAlias} === null || typeof ${valueAlias} !== 'object'${errChk(valueAlias)}) { ${rootFail} }`;
|
|
524
762
|
if ($keys in schema) {
|
|
525
|
-
const keysAlias = context.unique('k');
|
|
526
763
|
const kAlias = context.unique('k');
|
|
527
|
-
code += `\
|
|
764
|
+
code += `\nfor (const ${kAlias} in ${valueAlias}) { ${codeGen(schema[$keys], context, kAlias, indexed ? {
|
|
765
|
+
fast: true,
|
|
766
|
+
onFail: 'return 0;'
|
|
767
|
+
} : mode)} }`;
|
|
528
768
|
}
|
|
529
769
|
if ($values in schema) {
|
|
530
|
-
const vAlias = context.unique('val');
|
|
531
770
|
const kAlias = context.unique('k');
|
|
532
|
-
const
|
|
533
|
-
|
|
771
|
+
code += `\nfor (const ${kAlias} in ${valueAlias}) { ${codeGen(schema[$values], context, `${valueAlias}[${kAlias}]`, indexed ? {
|
|
772
|
+
fast: true,
|
|
773
|
+
onFail: 'return 0;'
|
|
774
|
+
} : mode)} }`;
|
|
534
775
|
}
|
|
535
776
|
if ($strict in schema && schema[$strict]) {
|
|
536
|
-
const
|
|
777
|
+
const allowedRef = `reg[${context.register(Object.fromEntries(Object.keys(schema).map((k)=>[
|
|
778
|
+
k,
|
|
779
|
+
1
|
|
780
|
+
])))}]`;
|
|
537
781
|
const kAlias = context.unique('k');
|
|
538
|
-
const
|
|
539
|
-
code += `\nconst ${keysAlias} = new Set(${JSON.stringify(Object.keys(schema))});\nconst ${extraAlias} = Object.keys(${valueAlias}).filter(${kAlias} => !${keysAlias}.has(${kAlias}));\nif (${extraAlias}.length !== 0) { ${fail} }`;
|
|
782
|
+
code += `\nfor (const ${kAlias} in ${valueAlias}) { if (!Object.hasOwn(${allowedRef}, ${kAlias})) { ${rootFail} } }`;
|
|
540
783
|
}
|
|
541
|
-
|
|
784
|
+
const entries = Object.entries(schema);
|
|
785
|
+
code += '\n' + entries.map(([key, s], idx)=>{
|
|
786
|
+
const propMode = indexed ? {
|
|
787
|
+
fast: true,
|
|
788
|
+
onFail: `return ${idx + 1};`
|
|
789
|
+
} : mode;
|
|
790
|
+
return codeGen(s, context, `${valueAlias}[${JSON.stringify(key)}]`, propMode);
|
|
791
|
+
}).join('\n');
|
|
792
|
+
if (indexed) code += `\nreturn -1;`;
|
|
542
793
|
return code;
|
|
543
794
|
} else {
|
|
795
|
+
const sv = !mode.fast && mode.startVar;
|
|
544
796
|
const code = [
|
|
545
|
-
`const ${valueAlias} = ${valuePath}
|
|
546
|
-
`if (${valueAlias} === null || ${valueAlias} === undefined) { ${emit(`\`Invalid value \${${valueAlias}}, expected non-nullable\``)} }`,
|
|
547
|
-
`else if (typeof ${valueAlias} !== 'object') { ${emit(`\`Invalid type \${typeof ${valueAlias}}, expected an instance of Object\``)} }`,
|
|
548
|
-
`else if (${valueAlias} instanceof Error) { ${emit(`\`\${${valueAlias}.message}\``)} }`,
|
|
549
|
-
'else {'
|
|
797
|
+
`const ${valueAlias} = ${valuePath};`
|
|
550
798
|
];
|
|
799
|
+
if (sv) {
|
|
800
|
+
const rootCase = [];
|
|
801
|
+
if (mode.firstError) {
|
|
802
|
+
rootCase.push(`if (${valueAlias} === null || ${valueAlias} === undefined) { ${emit(`\`Invalid value \${${valueAlias}}, expected non-nullable\``)} }`);
|
|
803
|
+
rootCase.push(`else if (typeof ${valueAlias} !== 'object') { ${emit(`\`Invalid type \${typeof ${valueAlias}}, expected an instance of Object\``)} }`);
|
|
804
|
+
rootCase.push(`${errBranch(valueAlias)}`);
|
|
805
|
+
} else {
|
|
806
|
+
rootCase.push(`if (${valueAlias} === null || ${valueAlias} === undefined) { ${emit(`\`Invalid value \${${valueAlias}}, expected non-nullable\``)} break; }`);
|
|
807
|
+
rootCase.push(`else if (typeof ${valueAlias} !== 'object') { ${emit(`\`Invalid type \${typeof ${valueAlias}}, expected an instance of Object\``)} break; }`);
|
|
808
|
+
if (!context.pure) rootCase.push(`else if (typeof ${valueAlias} === 'object' && ${valueAlias}[err] === true) { ${emit(`\`\${${valueAlias}.message}\``)} break; }`);
|
|
809
|
+
}
|
|
810
|
+
if ($keys in schema) {
|
|
811
|
+
const kAlias = context.unique('k');
|
|
812
|
+
rootCase.push(`for (const ${kAlias} in ${valueAlias}) { ${codeGen(schema[$keys], context, kAlias, childMode(mode, {
|
|
813
|
+
dynamic: kAlias
|
|
814
|
+
}, context))} }`);
|
|
815
|
+
}
|
|
816
|
+
if ($values in schema) {
|
|
817
|
+
const kAlias = context.unique('k');
|
|
818
|
+
rootCase.push(`for (const ${kAlias} in ${valueAlias}) { ${codeGen(schema[$values], context, `${valueAlias}[${kAlias}]`, childMode(mode, {
|
|
819
|
+
dynamic: kAlias
|
|
820
|
+
}, context))} }`);
|
|
821
|
+
}
|
|
822
|
+
if ($strict in schema && schema[$strict]) {
|
|
823
|
+
const allowedRef = `reg[${context.register(Object.fromEntries(Object.keys(schema).map((k)=>[
|
|
824
|
+
k,
|
|
825
|
+
1
|
|
826
|
+
])))}]`;
|
|
827
|
+
const kAlias = context.unique('k');
|
|
828
|
+
const extraAlias = context.unique('ex');
|
|
829
|
+
rootCase.push(`const ${extraAlias} = [];`);
|
|
830
|
+
rootCase.push(`for (const ${kAlias} in ${valueAlias}) { if (!Object.hasOwn(${allowedRef}, ${kAlias})) ${extraAlias}.push(${kAlias}); }`);
|
|
831
|
+
rootCase.push(`if (${extraAlias}.length !== 0) { ${emit(`\`Extra properties: \${${extraAlias}}, are not allowed\``)} }`);
|
|
832
|
+
}
|
|
833
|
+
code.push(`switch (${sv}) {`);
|
|
834
|
+
code.push(`case 0: { ${rootCase.join('\n')} }`);
|
|
835
|
+
const entries = Object.entries(schema);
|
|
836
|
+
entries.forEach(([key, s], idx)=>{
|
|
837
|
+
const propCode = codeGen(s, context, `${valueAlias}[${JSON.stringify(key)}]`, childMode(mode, key, context));
|
|
838
|
+
code.push(`case ${idx + 1}: { ${propCode} }`);
|
|
839
|
+
});
|
|
840
|
+
code.push(`}`);
|
|
841
|
+
return code.join('\n');
|
|
842
|
+
}
|
|
843
|
+
code.push(`if (${valueAlias} === null || ${valueAlias} === undefined) { ${emit(`\`Invalid value \${${valueAlias}}, expected non-nullable\``)} }`);
|
|
844
|
+
code.push(`else if (typeof ${valueAlias} !== 'object') { ${emit(`\`Invalid type \${typeof ${valueAlias}}, expected an instance of Object\``)} }`);
|
|
845
|
+
code.push(`${errBranch(valueAlias)}`);
|
|
846
|
+
code.push('else {');
|
|
551
847
|
const innerCode = [];
|
|
552
848
|
if ($keys in schema) {
|
|
553
|
-
const keysAlias = context.unique('k');
|
|
554
849
|
const kAlias = context.unique('k');
|
|
555
|
-
innerCode.push(`const ${
|
|
556
|
-
innerCode.push(`for (const ${kAlias} of ${keysAlias}) { ${codeGen(schema[$keys], context, kAlias, childMode(mode, {
|
|
850
|
+
innerCode.push(`for (const ${kAlias} in ${valueAlias}) { ${codeGen(schema[$keys], context, kAlias, childMode(mode, {
|
|
557
851
|
dynamic: kAlias
|
|
558
|
-
}))} }`);
|
|
852
|
+
}, context))} }`);
|
|
559
853
|
}
|
|
560
854
|
if ($values in schema) {
|
|
561
|
-
const vAlias = context.unique('val');
|
|
562
855
|
const kAlias = context.unique('k');
|
|
563
|
-
const
|
|
564
|
-
innerCode.push(`const ${entriesAlias} = Object.entries(${valueAlias});`);
|
|
565
|
-
innerCode.push(`for (const [${kAlias}, ${vAlias}] of ${entriesAlias}) { ${codeGen(schema[$values], context, vAlias, childMode(mode, {
|
|
856
|
+
innerCode.push(`for (const ${kAlias} in ${valueAlias}) { ${codeGen(schema[$values], context, `${valueAlias}[${kAlias}]`, childMode(mode, {
|
|
566
857
|
dynamic: kAlias
|
|
567
|
-
}))} }`);
|
|
858
|
+
}, context))} }`);
|
|
568
859
|
}
|
|
569
860
|
if ($strict in schema && schema[$strict]) {
|
|
570
|
-
const
|
|
861
|
+
const allowedRef = `reg[${context.register(Object.fromEntries(Object.keys(schema).map((k)=>[
|
|
862
|
+
k,
|
|
863
|
+
1
|
|
864
|
+
])))}]`;
|
|
571
865
|
const kAlias = context.unique('k');
|
|
572
866
|
const extraAlias = context.unique('ex');
|
|
573
|
-
innerCode.push(`const ${
|
|
574
|
-
innerCode.push(`const ${
|
|
867
|
+
innerCode.push(`const ${extraAlias} = [];`);
|
|
868
|
+
innerCode.push(`for (const ${kAlias} in ${valueAlias}) { if (!Object.hasOwn(${allowedRef}, ${kAlias})) ${extraAlias}.push(${kAlias}); }`);
|
|
575
869
|
innerCode.push(`if (${extraAlias}.length !== 0) { ${emit(`\`Extra properties: \${${extraAlias}}, are not allowed\``)} }`);
|
|
576
870
|
}
|
|
577
|
-
|
|
871
|
+
const entries = Object.entries(schema);
|
|
872
|
+
innerCode.push(...entries.map(([key, s])=>codeGen(s, context, `${valueAlias}[${JSON.stringify(key)}]`, childMode(mode, key, context))));
|
|
578
873
|
code.push(innerCode.join('\n'), '}');
|
|
579
874
|
return code.join('\n');
|
|
580
875
|
}
|
|
@@ -585,9 +880,9 @@ const codeGen = (schema, context, valuePath, mode)=>{
|
|
|
585
880
|
const valueAlias = context.unique('v');
|
|
586
881
|
const registryAlias = context.unique('r');
|
|
587
882
|
if (mode.fast) {
|
|
588
|
-
return `const ${valueAlias} = ${valuePath};\nconst ${registryAlias} =
|
|
883
|
+
return `const ${valueAlias} = ${valuePath};\nconst ${registryAlias} = reg[${index}];\nif (typeof ${valueAlias} !== 'symbol' || ${valueAlias} !== ${registryAlias}) { ${fail} }`;
|
|
589
884
|
} else {
|
|
590
|
-
return `const ${valueAlias} = ${valuePath};\nconst ${registryAlias} =
|
|
885
|
+
return `const ${valueAlias} = ${valuePath};\nconst ${registryAlias} = reg[${index}];\nif (typeof ${valueAlias} !== 'symbol') { ${emit(`\`Invalid type \${typeof ${valueAlias}}, expected symbol\``)} }\nelse if (${valueAlias} !== ${registryAlias}) { ${emit(`\`Invalid value \${${valueAlias}.toString()}, expected ${schema.toString()}\``)} }`;
|
|
591
886
|
}
|
|
592
887
|
}
|
|
593
888
|
if (schema === null || schema === undefined) {
|
|
@@ -603,27 +898,63 @@ const codeGen = (schema, context, valuePath, mode)=>{
|
|
|
603
898
|
return `const ${valueAlias} = ${valuePath};\nif (typeof ${valueAlias} !== '${typeof schema}' || ${valueAlias} !== ${toLiteral(schema)}) { ${fail} }`;
|
|
604
899
|
} else {
|
|
605
900
|
const value = context.unique('val');
|
|
606
|
-
return `const ${valueAlias} = ${valuePath};\nconst ${value} = ${toLiteral(schema)};\nif (${valueAlias}
|
|
901
|
+
return context.pure ? `const ${valueAlias} = ${valuePath};\nconst ${value} = ${toLiteral(schema)};\nif (typeof ${valueAlias} !== '${typeof schema}') { ${emit(`\`Invalid type \${typeof ${valueAlias}}, expected ${typeof schema}\``)} }\nelse if (${valueAlias} !== ${value}) { ${emit(`\`Invalid value \${String(${valueAlias})}, expected ${toLiteral(schema)}\``)} }` : `const ${valueAlias} = ${valuePath};\nconst ${value} = ${toLiteral(schema)};\nif (typeof ${valueAlias} === 'object' && ${valueAlias} !== null && ${valueAlias}[err] === true) { ${emit(`\`\${${valueAlias}.message}\``)} }\nelse if (typeof ${valueAlias} !== '${typeof schema}') { ${emit(`\`Invalid type \${typeof ${valueAlias}}, expected ${typeof schema}\``)} }\nelse if (${valueAlias} !== ${value}) { ${emit(`\`Invalid value \${String(${valueAlias})}, expected ${toLiteral(schema)}\``)} }`;
|
|
607
902
|
}
|
|
608
903
|
};
|
|
609
904
|
const emptyIssues = [];
|
|
610
905
|
export const compile = (schema, options)=>{
|
|
611
906
|
const allErrors = options?.allErrors ?? false;
|
|
907
|
+
const pure = options?.pure ?? false;
|
|
908
|
+
const canIndex = typeof schema === 'object' && schema !== null && !isTagged(schema) && !Array.isArray(schema) && !(schema instanceof RegExp);
|
|
612
909
|
if (allErrors) {
|
|
910
|
+
if (canIndex) {
|
|
911
|
+
const fastContext = new Context();
|
|
912
|
+
fastContext.pure = pure;
|
|
913
|
+
const fastCode = codeGen(schema, fastContext, 'data', {
|
|
914
|
+
fast: true,
|
|
915
|
+
indexed: true
|
|
916
|
+
});
|
|
917
|
+
const indexedFast = new Function('reg', 'err', `return (data) => {\n${fastCode}\n};`)(fastContext.registry, ERR);
|
|
918
|
+
const issueContext = new Context();
|
|
919
|
+
issueContext.pure = pure;
|
|
920
|
+
const rootPath = `reg[${issueContext.register(Object.freeze([]))}]`;
|
|
921
|
+
const issueCode = `let issues = [];\n${codeGen(schema, issueContext, 'data', {
|
|
922
|
+
fast: false,
|
|
923
|
+
firstError: false,
|
|
924
|
+
issues: 'issues',
|
|
925
|
+
path: [],
|
|
926
|
+
pathExpr: rootPath,
|
|
927
|
+
startVar: 'start',
|
|
928
|
+
issuesReady: true
|
|
929
|
+
})}\nreturn issues;`;
|
|
930
|
+
const issueValidator = new Function('reg', 'err', `return (data, start) => {\n${issueCode}\n};`)(issueContext.registry, ERR);
|
|
931
|
+
const validator = (data)=>{
|
|
932
|
+
const idx = indexedFast(data);
|
|
933
|
+
if (idx === -1) return true;
|
|
934
|
+
validator.issues = issueValidator(data, idx);
|
|
935
|
+
return false;
|
|
936
|
+
};
|
|
937
|
+
validator.issues = emptyIssues;
|
|
938
|
+
return validator;
|
|
939
|
+
}
|
|
613
940
|
const fastContext = new Context();
|
|
941
|
+
fastContext.pure = pure;
|
|
614
942
|
const fastCode = `${codeGen(schema, fastContext, 'data', {
|
|
615
943
|
fast: true
|
|
616
944
|
})}\nreturn true;`;
|
|
617
|
-
const fastValidator = new Function('
|
|
945
|
+
const fastValidator = new Function('reg', 'err', `return (data) => {\n${fastCode}\n};`)(fastContext.registry, ERR);
|
|
618
946
|
const issueContext = new Context();
|
|
619
|
-
|
|
947
|
+
issueContext.pure = pure;
|
|
948
|
+
const rootPath2 = `reg[${issueContext.register(Object.freeze([]))}]`;
|
|
949
|
+
const issueCode = `let issues = [];\n${codeGen(schema, issueContext, 'data', {
|
|
620
950
|
fast: false,
|
|
621
951
|
firstError: false,
|
|
622
952
|
issues: 'issues',
|
|
623
953
|
path: [],
|
|
624
|
-
pathExpr:
|
|
625
|
-
|
|
626
|
-
|
|
954
|
+
pathExpr: rootPath2,
|
|
955
|
+
issuesReady: true
|
|
956
|
+
})}\nreturn issues;`;
|
|
957
|
+
const issueValidator = new Function('reg', 'err', `return (data) => {\n${issueCode}\n};`)(issueContext.registry, ERR);
|
|
627
958
|
const validator = (data)=>{
|
|
628
959
|
if (fastValidator(data)) {
|
|
629
960
|
return true;
|
|
@@ -634,20 +965,54 @@ export const compile = (schema, options)=>{
|
|
|
634
965
|
validator.issues = emptyIssues;
|
|
635
966
|
return validator;
|
|
636
967
|
}
|
|
968
|
+
if (canIndex) {
|
|
969
|
+
const fastContext = new Context();
|
|
970
|
+
fastContext.pure = pure;
|
|
971
|
+
const fastCode = codeGen(schema, fastContext, 'data', {
|
|
972
|
+
fast: true,
|
|
973
|
+
indexed: true
|
|
974
|
+
});
|
|
975
|
+
const indexedFast = new Function('reg', 'err', `return (data) => {\n${fastCode}\n};`)(fastContext.registry, ERR);
|
|
976
|
+
const context = new Context();
|
|
977
|
+
context.pure = pure;
|
|
978
|
+
const rootPath = `reg[${context.register(Object.freeze([]))}]`;
|
|
979
|
+
const code = codeGen(schema, context, 'data', {
|
|
980
|
+
fast: false,
|
|
981
|
+
firstError: true,
|
|
982
|
+
issues: 'issues',
|
|
983
|
+
path: [],
|
|
984
|
+
pathExpr: rootPath,
|
|
985
|
+
startVar: 'start'
|
|
986
|
+
});
|
|
987
|
+
const firstErrorValidator = new Function('reg', 'err', `return (data, start) => {\nlet issues;\n${code}\nreturn issues;\n};`)(context.registry, ERR);
|
|
988
|
+
const validator = (data)=>{
|
|
989
|
+
const idx = indexedFast(data);
|
|
990
|
+
if (idx === -1) {
|
|
991
|
+
return true;
|
|
992
|
+
}
|
|
993
|
+
validator.issues = firstErrorValidator(data, idx);
|
|
994
|
+
return false;
|
|
995
|
+
};
|
|
996
|
+
validator.issues = emptyIssues;
|
|
997
|
+
return validator;
|
|
998
|
+
}
|
|
637
999
|
const fastContext = new Context();
|
|
1000
|
+
fastContext.pure = pure;
|
|
638
1001
|
const fastCode = `${codeGen(schema, fastContext, 'data', {
|
|
639
1002
|
fast: true
|
|
640
1003
|
})}\nreturn true;`;
|
|
641
|
-
const fastValidator = new Function('
|
|
1004
|
+
const fastValidator = new Function('reg', 'err', `return (data) => {\n${fastCode}\n};`)(fastContext.registry, ERR);
|
|
642
1005
|
const context = new Context();
|
|
1006
|
+
context.pure = pure;
|
|
1007
|
+
const rootPath3 = `reg[${context.register(Object.freeze([]))}]`;
|
|
643
1008
|
const code = codeGen(schema, context, 'data', {
|
|
644
1009
|
fast: false,
|
|
645
1010
|
firstError: true,
|
|
646
1011
|
issues: 'issues',
|
|
647
1012
|
path: [],
|
|
648
|
-
pathExpr:
|
|
1013
|
+
pathExpr: rootPath3
|
|
649
1014
|
});
|
|
650
|
-
const firstErrorValidator = new Function('
|
|
1015
|
+
const firstErrorValidator = new Function('reg', 'err', `return (data) => {\nlet issues;\n${code}\nreturn issues;\n};`)(context.registry, ERR);
|
|
651
1016
|
const validator = (data)=>{
|
|
652
1017
|
if (fastValidator(data)) {
|
|
653
1018
|
return true;
|