ascertain 0.14.39 → 1.0.0-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/LICENSE +21 -0
- package/README.md +97 -11
- package/build/index.cjs +265 -93
- package/build/index.cjs.map +1 -1
- package/build/index.d.ts +50 -32
- package/build/index.js +257 -91
- package/build/index.js.map +1 -1
- package/package.json +25 -12
- package/src/index.js +0 -201
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2019-2024 Ivan Zakharchanka
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
### Ascertain what data is not suitable for your library
|
|
4
4
|
|
|
5
|
-
0-Deps, simple, blazing fast, for browser and
|
|
5
|
+
0-Deps, simple, blazing fast, for browser and Node.js object schema validator
|
|
6
6
|
|
|
7
7
|
[![Coverage Status][codecov-image]][codecov-url]
|
|
8
8
|
[![Build Status][github-image]][github-url]
|
|
@@ -12,19 +12,64 @@
|
|
|
12
12
|
|
|
13
13
|
## Features
|
|
14
14
|
|
|
15
|
-
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
15
|
+
- Type-safe validation: Ensures your data conforms to predefined schemas.
|
|
16
|
+
- Composite schemas: Supports logical AND, OR, and optional schemas.
|
|
17
|
+
- Type casting: Automatically parses and casts strings to other types.
|
|
18
|
+
- Error handling: Provides detailed error messages for invalid data.
|
|
19
|
+
|
|
20
|
+
## Schema description
|
|
21
|
+
|
|
22
|
+
- Primitive Values: Any primitive value (e.g., string, number, bigint, boolean, undefined, symbol, null) is used as an expected constant to match against.
|
|
23
|
+
- Function Types: Functions are used as constructors for non-objects and instance types for object types.
|
|
24
|
+
- Array Values: Arrays are used to represent an expected array type, where every item in the array must match the specified type (acting as an "and" operator).
|
|
25
|
+
- Regular Expressions: Regular expressions are used to validate that a value matches a specified string pattern.
|
|
26
|
+
- Object Types: Non-null objects are used as templates for expected properties, where each property of the object must match the corresponding schema definition.
|
|
22
27
|
|
|
23
28
|
## Usage Example
|
|
24
29
|
|
|
30
|
+
### Schema compilation
|
|
31
|
+
```typescript
|
|
32
|
+
import { compile, optional, and, or, $keys, $values, Schema, as } from 'ascertain';
|
|
33
|
+
|
|
34
|
+
const validate = compile({
|
|
35
|
+
number: Number,
|
|
36
|
+
string: String,
|
|
37
|
+
boolean: Boolean,
|
|
38
|
+
function: Function,
|
|
39
|
+
array: Array,
|
|
40
|
+
object: Object,
|
|
41
|
+
date: and(Date, { toJSON: Function }),
|
|
42
|
+
regexp: /regexp/,
|
|
43
|
+
oneOfValue: or(1, 2, 3),
|
|
44
|
+
arrayOfNumbers: [Number],
|
|
45
|
+
objectSchema: {
|
|
46
|
+
number: Number,
|
|
47
|
+
},
|
|
48
|
+
optional: optional({
|
|
49
|
+
number: Number,
|
|
50
|
+
}),
|
|
51
|
+
keyValue: {
|
|
52
|
+
[$keys]: /^key[A-Z]/,
|
|
53
|
+
[$values]: Number
|
|
54
|
+
},
|
|
55
|
+
parsedNumber: Number,
|
|
56
|
+
parsedString: String,
|
|
57
|
+
parsedBoolean: Boolean,
|
|
58
|
+
parsedArray: [String],
|
|
59
|
+
parsedJSON: {
|
|
60
|
+
number: 1,
|
|
61
|
+
},
|
|
62
|
+
parsedBase64: String,
|
|
63
|
+
parsedTime: 2 * 60 * 1000, // two minutes
|
|
64
|
+
parsedDate: Date,
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Runtime validation
|
|
25
70
|
Create data ascertain
|
|
26
71
|
```typescript
|
|
27
|
-
import ascertain,
|
|
72
|
+
import { ascertain, optional, and, or, $keys, $values, Schema, as } from 'ascertain';
|
|
28
73
|
|
|
29
74
|
// create data sample
|
|
30
75
|
const data = {
|
|
@@ -49,11 +94,13 @@ const data = {
|
|
|
49
94
|
},
|
|
50
95
|
// fault tolernat type casting
|
|
51
96
|
parsedNumber: as.number('1'),
|
|
52
|
-
parsedString: as.
|
|
97
|
+
parsedString: as.string('string'),
|
|
53
98
|
parsedBoolean: as.boolean('false'),
|
|
54
99
|
parsedArray: as.array('1,2,3,4,5', ','),
|
|
55
100
|
parsedJSON: as.json('{ "number": 1 }'),
|
|
56
101
|
parsedBase64: as.base64('dGVzdA=='),
|
|
102
|
+
parsedTime: as.time('2m'),
|
|
103
|
+
parsedDate: as.date('31-12-2024'),
|
|
57
104
|
};
|
|
58
105
|
|
|
59
106
|
// create data schema
|
|
@@ -86,15 +133,54 @@ const schema: Schema<typeof data> = {
|
|
|
86
133
|
number: 1,
|
|
87
134
|
},
|
|
88
135
|
parsedBase64: String,
|
|
136
|
+
parsedTime: 2 * 60 * 1000, // two minutes
|
|
137
|
+
parsedDate: Date,
|
|
89
138
|
};
|
|
90
139
|
|
|
91
140
|
// validate
|
|
92
141
|
const validate = ascertain<typeof data>(schema, data, '[DATA]');
|
|
93
142
|
```
|
|
94
143
|
|
|
144
|
+
### Benchmark VS zod and ajv
|
|
145
|
+
```
|
|
146
|
+
⭐ Script ajv-vs-zod-vs-ascertain.js
|
|
147
|
+
⇶ Suite ajv vs zod vs ascertain
|
|
148
|
+
➤ Perform benchmark
|
|
149
|
+
✓ Measure 500000 zod static schema validation
|
|
150
|
+
┌──────────┬──────────┬──────────┬──────────┬────────────┬────────┐
|
|
151
|
+
│ (index) │ med │ p95 │ p99 │ total │ count │
|
|
152
|
+
├──────────┼──────────┼──────────┼──────────┼────────────┼────────┤
|
|
153
|
+
│ 0.000699 │ 0.000788 │ 0.000996 │ 0.001624 │ 462.602373 │ 500000 │
|
|
154
|
+
└──────────┴──────────┴──────────┴──────────┴────────────┴────────┘
|
|
155
|
+
✓ Measure 500000 zod dynamic schema validation
|
|
156
|
+
┌──────────┬──────────┬──────────┬──────────┬─────────────┬────────┐
|
|
157
|
+
│ (index) │ med │ p95 │ p99 │ total │ count │
|
|
158
|
+
├──────────┼──────────┼──────────┼──────────┼─────────────┼────────┤
|
|
159
|
+
│ 0.006248 │ 0.006918 │ 0.007524 │ 0.016948 │ 3780.465563 │ 500000 │
|
|
160
|
+
└──────────┴──────────┴──────────┴──────────┴─────────────┴────────┘
|
|
161
|
+
✓ Measure 500000 ascertain static schema validation
|
|
162
|
+
┌──────────┬──────────┬──────────┬──────────┬───────────┬────────┐
|
|
163
|
+
│ (index) │ med │ p95 │ p99 │ total │ count │
|
|
164
|
+
├──────────┼──────────┼──────────┼──────────┼───────────┼────────┤
|
|
165
|
+
│ 0.000063 │ 0.000071 │ 0.000098 │ 0.000267 │ 41.673271 │ 500000 │
|
|
166
|
+
└──────────┴──────────┴──────────┴──────────┴───────────┴────────┘
|
|
167
|
+
✓ Measure 500000 ascertain dynamic schema validation
|
|
168
|
+
┌──────────┬──────────┬──────────┬──────────┬────────────┬────────┐
|
|
169
|
+
│ (index) │ med │ p95 │ p99 │ total │ count │
|
|
170
|
+
├──────────┼──────────┼──────────┼──────────┼────────────┼────────┤
|
|
171
|
+
│ 0.000367 │ 0.000415 │ 0.000525 │ 0.001055 │ 239.078129 │ 500000 │
|
|
172
|
+
└──────────┴──────────┴──────────┴──────────┴────────────┴────────┘
|
|
173
|
+
✓ Measure 500000 ajv compiled schema validation
|
|
174
|
+
┌──────────┬──────────┬──────────┬──────────┬───────────┬────────┐
|
|
175
|
+
│ (index) │ med │ p95 │ p99 │ total │ count │
|
|
176
|
+
├──────────┼──────────┼──────────┼──────────┼───────────┼────────┤
|
|
177
|
+
│ 0.000063 │ 0.000072 │ 0.000124 │ 0.000307 │ 44.542936 │ 500000 │
|
|
178
|
+
└──────────┴──────────┴──────────┴──────────┴───────────┴────────┘
|
|
179
|
+
```
|
|
180
|
+
|
|
95
181
|
## License
|
|
96
182
|
License [The MIT License](http://opensource.org/licenses/MIT)
|
|
97
|
-
Copyright (c) 2024 Ivan Zakharchanka
|
|
183
|
+
Copyright (c) 2019-2024 Ivan Zakharchanka
|
|
98
184
|
|
|
99
185
|
[npm-url]: https://www.npmjs.com/package/ascertain
|
|
100
186
|
[downloads-image]: https://img.shields.io/npm/dw/ascertain.svg?maxAge=43200
|
package/build/index.cjs
CHANGED
|
@@ -15,6 +15,9 @@ _export(exports, {
|
|
|
15
15
|
$values: function() {
|
|
16
16
|
return $values;
|
|
17
17
|
},
|
|
18
|
+
AssertError: function() {
|
|
19
|
+
return AssertError;
|
|
20
|
+
},
|
|
18
21
|
and: function() {
|
|
19
22
|
return and;
|
|
20
23
|
},
|
|
@@ -24,8 +27,8 @@ _export(exports, {
|
|
|
24
27
|
ascertain: function() {
|
|
25
28
|
return ascertain;
|
|
26
29
|
},
|
|
27
|
-
|
|
28
|
-
return
|
|
30
|
+
compile: function() {
|
|
31
|
+
return compile;
|
|
29
32
|
},
|
|
30
33
|
fromBase64: function() {
|
|
31
34
|
return fromBase64;
|
|
@@ -35,63 +38,50 @@ _export(exports, {
|
|
|
35
38
|
},
|
|
36
39
|
or: function() {
|
|
37
40
|
return or;
|
|
41
|
+
},
|
|
42
|
+
tuple: function() {
|
|
43
|
+
return tuple;
|
|
38
44
|
}
|
|
39
45
|
});
|
|
40
|
-
|
|
46
|
+
class AssertError extends TypeError {
|
|
47
|
+
value;
|
|
48
|
+
expected;
|
|
49
|
+
path;
|
|
50
|
+
subject;
|
|
41
51
|
constructor(value, expected, path, subject = 'value'){
|
|
52
|
+
super(`Invalid ${subject} ${JSON.stringify(value)} for path ${path}, expected ${expected}.`);
|
|
42
53
|
this.value = value;
|
|
43
54
|
this.expected = expected;
|
|
44
55
|
this.path = path;
|
|
45
56
|
this.subject = subject;
|
|
46
57
|
}
|
|
47
|
-
toString() {
|
|
48
|
-
return `Invalid ${this.subject} ${JSON.stringify(this.value)} specified by path ${this.path} expected ${this.expected}`;
|
|
49
|
-
}
|
|
50
|
-
};
|
|
51
|
-
function findFirstError(array, map) {
|
|
52
|
-
for(let i = 0; i < array.length; i++){
|
|
53
|
-
const error = map(array[i], i);
|
|
54
|
-
if (error) {
|
|
55
|
-
return error;
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
return false;
|
|
59
58
|
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
if (
|
|
65
|
-
|
|
66
|
-
} else {
|
|
67
|
-
errors.push(error);
|
|
59
|
+
class Operation {
|
|
60
|
+
schemas;
|
|
61
|
+
constructor(schemas){
|
|
62
|
+
this.schemas = schemas;
|
|
63
|
+
if (schemas.length === 0) {
|
|
64
|
+
throw new TypeError(`Operation schema ${this.constructor.name} must have at least one element`);
|
|
68
65
|
}
|
|
69
66
|
}
|
|
70
|
-
return errors.reduce((result, error)=>{
|
|
71
|
-
return new AssertError(result.value, [
|
|
72
|
-
result.expected,
|
|
73
|
-
error.expected
|
|
74
|
-
].join(' or '), result.path, result.string);
|
|
75
|
-
});
|
|
76
67
|
}
|
|
77
|
-
|
|
78
|
-
this.schema = schema;
|
|
68
|
+
class Or extends Operation {
|
|
79
69
|
}
|
|
80
|
-
|
|
81
|
-
|
|
70
|
+
const or = (...schemas)=>new Or(schemas);
|
|
71
|
+
class And extends Operation {
|
|
82
72
|
}
|
|
83
|
-
|
|
84
|
-
|
|
73
|
+
const and = (...schemas)=>new And(schemas);
|
|
74
|
+
class Optional extends Operation {
|
|
75
|
+
constructor(schema){
|
|
76
|
+
super([
|
|
77
|
+
schema
|
|
78
|
+
]);
|
|
79
|
+
}
|
|
85
80
|
}
|
|
86
|
-
const optional = (schema)=>
|
|
87
|
-
|
|
88
|
-
}
|
|
89
|
-
const
|
|
90
|
-
return new And(schema);
|
|
91
|
-
};
|
|
92
|
-
const or = (...schema)=>{
|
|
93
|
-
return new Or(schema);
|
|
94
|
-
};
|
|
81
|
+
const optional = (schema)=>new Optional(schema);
|
|
82
|
+
class Tuple extends Operation {
|
|
83
|
+
}
|
|
84
|
+
const tuple = (...schemas)=>new Tuple(schemas);
|
|
95
85
|
const $keys = Symbol.for('@@keys');
|
|
96
86
|
const $values = Symbol.for('@@values');
|
|
97
87
|
const fromBase64 = typeof Buffer === 'undefined' ? (value)=>atob(value) : (value)=>Buffer.from(value, 'base64').toString('utf-8');
|
|
@@ -118,13 +108,13 @@ const as = {
|
|
|
118
108
|
time: (value)=>{
|
|
119
109
|
const matches = value?.match(/^(\d+)(ms|s|m|h|d|w)?$/);
|
|
120
110
|
if (matches) {
|
|
121
|
-
const [
|
|
111
|
+
const [, amount, unit = 'ms'] = matches;
|
|
122
112
|
return parseInt(amount, 10) * MULTIPLIERS[unit];
|
|
123
113
|
}
|
|
124
114
|
return undefined;
|
|
125
115
|
},
|
|
126
116
|
boolean: (value)=>/^(0|1|true|false|enabled|disabled)$/i.test(value) ? /^(1|true|enabled)$/i.test(value) : undefined,
|
|
127
|
-
array: (value, delimiter)=>value?.split(delimiter) ?? undefined,
|
|
117
|
+
array: (value, delimiter)=>value?.split?.(delimiter) ?? undefined,
|
|
128
118
|
json: (value)=>{
|
|
129
119
|
try {
|
|
130
120
|
return JSON.parse(value);
|
|
@@ -140,84 +130,266 @@ const as = {
|
|
|
140
130
|
}
|
|
141
131
|
}
|
|
142
132
|
};
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
133
|
+
class Context {
|
|
134
|
+
options;
|
|
135
|
+
Error;
|
|
136
|
+
registry;
|
|
137
|
+
varIndex;
|
|
138
|
+
constructor(options = {}){
|
|
139
|
+
this.options = options;
|
|
140
|
+
this.registry = [];
|
|
141
|
+
this.varIndex = 0;
|
|
142
|
+
this.Error = options.error ?? AssertError;
|
|
146
143
|
}
|
|
147
|
-
|
|
148
|
-
|
|
144
|
+
register(value) {
|
|
145
|
+
if (!this.registry.includes(value)) {
|
|
146
|
+
this.registry.push(value);
|
|
147
|
+
}
|
|
148
|
+
return this.registry.indexOf(value);
|
|
149
149
|
}
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
return;
|
|
150
|
+
unique(prefix) {
|
|
151
|
+
return `${prefix}$$${this.varIndex++}`;
|
|
153
152
|
}
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
153
|
+
}
|
|
154
|
+
const codeGen = (schema, context, valuePath, path)=>{
|
|
155
|
+
if (schema instanceof And) {
|
|
156
|
+
const valueAlias = context.unique('v');
|
|
157
|
+
const errorsAlias = context.unique('err');
|
|
158
|
+
const code = schema.schemas.map((s)=>`try { ${codeGen(s, context, valueAlias, path)} } catch (e) { ${errorsAlias}.push(e); }`).join('\n');
|
|
159
|
+
return `// And
|
|
160
|
+
const ${errorsAlias} = [];
|
|
161
|
+
const ${valueAlias} = ${valuePath};
|
|
162
|
+
${code}
|
|
163
|
+
if (${errorsAlias}.length !== 0) { throw new AggregateError(${errorsAlias}, 'Invalid value for path "${path}"'); }
|
|
164
|
+
`;
|
|
165
|
+
} else if (schema instanceof Or) {
|
|
166
|
+
const valueAlias = context.unique('v');
|
|
167
|
+
const errorsAlias = context.unique('err');
|
|
168
|
+
const code = schema.schemas.map((s)=>codeGen(s, context, valueAlias, path)).reduceRight((result, code)=>`try {${code}} catch (e) {${errorsAlias}.push(e);${result}}`, `throw new AggregateError(${errorsAlias}, 'Invalid value for path "${path}"');`);
|
|
169
|
+
return `// Or
|
|
170
|
+
const ${errorsAlias} = [];
|
|
171
|
+
const ${valueAlias} = ${valuePath};
|
|
172
|
+
${code}
|
|
173
|
+
`;
|
|
174
|
+
} else if (schema instanceof Optional) {
|
|
175
|
+
const valueAlias = context.unique('v');
|
|
176
|
+
return `// Optional
|
|
177
|
+
const ${valueAlias} = ${valuePath};
|
|
178
|
+
if (${valueAlias} !== undefined && ${valueAlias} !== null) { ${codeGen(schema.schemas[0], context, valueAlias, path)} }
|
|
179
|
+
`;
|
|
180
|
+
} else if (schema instanceof Tuple) {
|
|
181
|
+
const valueAlias = context.unique('v');
|
|
182
|
+
const errorsAlias = context.unique('err');
|
|
183
|
+
const code = [
|
|
184
|
+
'// Tuple',
|
|
185
|
+
`const ${valueAlias} = ${valuePath};`,
|
|
186
|
+
`const ${errorsAlias} = [];`,
|
|
187
|
+
`if (!Array.isArray(${valueAlias})) { throw new ctx.Error(${valueAlias}, 'array', \`${path}\`); }`,
|
|
188
|
+
...schema.schemas.map((s, idx)=>`try { ${codeGen(s, context, `${valueAlias}[${idx}]`, `${path}[${idx}]`)} } catch (e) { ${errorsAlias}.push(e); }`),
|
|
189
|
+
`if (${errorsAlias}.length !== 0) { throw new AggregateError(${errorsAlias}, 'Invalid value for path "${path}"'); }`
|
|
190
|
+
];
|
|
191
|
+
return code.join('\n');
|
|
192
|
+
} else if (typeof schema === 'function') {
|
|
193
|
+
const index = context.register(schema);
|
|
194
|
+
const valueAlias = context.unique('v');
|
|
195
|
+
const registryAlias = context.unique('r');
|
|
196
|
+
return `
|
|
197
|
+
const ${valueAlias} = ${valuePath};
|
|
198
|
+
const ${registryAlias} = ctx.registry[${index}];
|
|
199
|
+
if (${valueAlias} === null || ${valueAlias} === undefined) { throw new ctx.Error(${valueAlias}, 'a non-nullable', \`${path}\`); }
|
|
200
|
+
if (typeof ${valueAlias} === 'object' && !(${valueAlias} instanceof ${registryAlias})) { throw new ctx.Error(${valueAlias}?.constructor?.name, \`instance of \${${registryAlias}.name}\`, \`${path}\`, 'instance of'); }
|
|
201
|
+
if (typeof ${valueAlias} !== 'object' && ${valueAlias}?.constructor !== ${registryAlias}) { throw new ctx.Error(${valueAlias}?.constructor?.name, ${registryAlias}.name, \`${path}\`, 'type'); }
|
|
202
|
+
`;
|
|
203
|
+
} else if (Array.isArray(schema)) {
|
|
204
|
+
const valueAlias = context.unique('v');
|
|
205
|
+
const code = [
|
|
206
|
+
`const ${valueAlias} = ${valuePath};`,
|
|
207
|
+
`if (!Array.isArray(${valueAlias})) { throw new ctx.Error(${valueAlias}, 'array', \`${path}\`); }`
|
|
208
|
+
];
|
|
209
|
+
if (schema.length > 0) {
|
|
210
|
+
const value = context.unique('val');
|
|
211
|
+
const key = context.unique('key');
|
|
212
|
+
const errorsAlias = context.unique('err');
|
|
213
|
+
code.push(`const ${errorsAlias} = [];`);
|
|
214
|
+
code.push(...schema.map((s)=>`${valueAlias}.forEach((${value},${key}) => { try { ${codeGen(s, context, value, `${path}[\${${key}}]`)} } catch(e){ ${errorsAlias}.push(e); } });`));
|
|
215
|
+
code.push(`if (${errorsAlias}.length !== 0) { throw new AggregateError(${errorsAlias}, 'Invalid value for path "${path}"'); }`);
|
|
157
216
|
}
|
|
158
|
-
return
|
|
217
|
+
return code.join('\n');
|
|
218
|
+
} else if (typeof schema === 'object' && schema !== null) {
|
|
219
|
+
if (schema instanceof RegExp) {
|
|
220
|
+
const valueAlias = context.unique('v');
|
|
221
|
+
return `
|
|
222
|
+
const ${valueAlias} = ${valuePath};
|
|
223
|
+
if (!${schema.toString()}.test('' + ${valueAlias})) { throw new ctx.Error(${valueAlias}, 'matching ${schema.toString()}', \`${path}\`); }
|
|
224
|
+
`;
|
|
225
|
+
} else {
|
|
226
|
+
const valueAlias = context.unique('v');
|
|
227
|
+
const code = [
|
|
228
|
+
`const ${valueAlias} = ${valuePath};`,
|
|
229
|
+
`if (${valueAlias} === null || ${valueAlias} === undefined) { throw new ctx.Error(${valueAlias}, 'object', \`${path}\`); }`,
|
|
230
|
+
`if (typeof ${valueAlias} !== 'object') { throw new ctx.Error(${valueAlias}, '${schema.constructor.name}', \`${path}\`); }`
|
|
231
|
+
];
|
|
232
|
+
if ($keys in schema) {
|
|
233
|
+
const keysAlias = context.unique('key');
|
|
234
|
+
const errorsAlias = context.unique('err');
|
|
235
|
+
const value = context.unique('v');
|
|
236
|
+
code.push(`
|
|
237
|
+
const ${keysAlias} = Object.keys(${valueAlias});
|
|
238
|
+
const ${errorsAlias} = ${keysAlias}.flatMap((${value}) => { ${codeGen(schema[$keys], context, value, path)} }).filter(Boolean);
|
|
239
|
+
if (${errorsAlias}.length !== 0) { throw new AggregateError(${errorsAlias}, 'Invalid value for path "${path}"'); }
|
|
240
|
+
`);
|
|
241
|
+
}
|
|
242
|
+
if ($values in schema) {
|
|
243
|
+
const vAlias = context.unique('val');
|
|
244
|
+
const valuesAlias = context.unique('vals');
|
|
245
|
+
const errorsAlias = context.unique('err');
|
|
246
|
+
code.push(`{
|
|
247
|
+
const ${valuesAlias} = Object.values(${valuePath});
|
|
248
|
+
const ${errorsAlias} = ${valuesAlias}.flatMap((${vAlias}) => { ${codeGen(schema[$values], context, vAlias, path)} }).filter(Boolean);
|
|
249
|
+
if (${errorsAlias}.length !== 0) { throw new AggregateError(${errorsAlias}, 'Invalid value for path "${path}"'); }
|
|
250
|
+
}`);
|
|
251
|
+
}
|
|
252
|
+
const keys = Object.keys(schema);
|
|
253
|
+
code.push(...keys.map((key)=>codeGen(schema[key], context, `${valueAlias}['${key}']`, `${path}.${key}`)));
|
|
254
|
+
return `{${code.join('\n')}}`;
|
|
255
|
+
}
|
|
256
|
+
} else if (typeof schema === 'symbol') {
|
|
257
|
+
const index = context.register(schema);
|
|
258
|
+
const valueAlias = context.unique('v');
|
|
259
|
+
const registryAlias = context.unique('r');
|
|
260
|
+
return `
|
|
261
|
+
const ${valueAlias} = ${valuePath};
|
|
262
|
+
const ${registryAlias} = ctx.registry[${index}];
|
|
263
|
+
if (typeof ${valueAlias} !== 'symbol') { throw new ctx.Error(typeof ${valueAlias}, 'symbol', '${path}', 'type of'); }
|
|
264
|
+
if (${valueAlias} !== ${registryAlias}) { throw new ctx.Error(${valueAlias}.toString(), ${registryAlias}.toString(), '${path}', 'symbol'); }
|
|
265
|
+
`;
|
|
266
|
+
} else if (schema === null || schema === undefined) {
|
|
267
|
+
const valueAlias = context.unique('v');
|
|
268
|
+
return `
|
|
269
|
+
const ${valueAlias} = ${valuePath};
|
|
270
|
+
if (${valueAlias} !== null && ${valueAlias} !== undefined ) { throw new ctx.Error(${valueAlias}, 'nullable', '${path}'); }
|
|
271
|
+
`;
|
|
272
|
+
} else {
|
|
273
|
+
const valueAlias = context.unique('v');
|
|
274
|
+
const typeAlias = context.unique('t');
|
|
275
|
+
const value = context.unique('val');
|
|
276
|
+
return `
|
|
277
|
+
const ${valueAlias} = ${valuePath};
|
|
278
|
+
const ${typeAlias} = '${typeof schema}';
|
|
279
|
+
const ${value} = ${JSON.stringify(schema)};
|
|
280
|
+
if (typeof ${valueAlias} !== ${typeAlias}) { throw new ctx.Error(typeof ${valueAlias}, ${typeAlias}, '${path}', 'type of'); }
|
|
281
|
+
if (${valueAlias} !== ${value}) { throw new ctx.Error(${valueAlias}, ${value}, '${path}'); }
|
|
282
|
+
`;
|
|
159
283
|
}
|
|
284
|
+
};
|
|
285
|
+
const flatAggregateError = (error)=>{
|
|
286
|
+
return error.errors.flatMap((e)=>e instanceof AggregateError ? flatAggregateError(e) : e);
|
|
287
|
+
};
|
|
288
|
+
const compile = (schema, rootName, options = {})=>{
|
|
289
|
+
const context = new Context(options);
|
|
290
|
+
const code = codeGen(schema, context, 'data', rootName);
|
|
291
|
+
const validator = new Function('ctx', 'data', code);
|
|
292
|
+
return (data)=>{
|
|
293
|
+
try {
|
|
294
|
+
validator(context, data);
|
|
295
|
+
} catch (e) {
|
|
296
|
+
const errors = e instanceof AggregateError ? flatAggregateError(e) : [
|
|
297
|
+
e
|
|
298
|
+
];
|
|
299
|
+
throw new AggregateError(errors, 'Validation failure');
|
|
300
|
+
}
|
|
301
|
+
};
|
|
302
|
+
};
|
|
303
|
+
const assert = (target, schema, path)=>{
|
|
160
304
|
if (schema instanceof And) {
|
|
161
|
-
|
|
162
|
-
|
|
305
|
+
return schema.schemas.flatMap((schema)=>assert(target, schema, path)).filter((error)=>!!error);
|
|
306
|
+
} else if (schema instanceof Or) {
|
|
307
|
+
const errors = schema.schemas.flatMap((schema)=>assert(target, schema, path));
|
|
308
|
+
const filteredErrors = errors.filter((error)=>!!error);
|
|
309
|
+
if (filteredErrors.length === schema.schemas.length) {
|
|
310
|
+
return filteredErrors;
|
|
163
311
|
}
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
312
|
+
} else if (schema instanceof Optional) {
|
|
313
|
+
if (target !== undefined && target !== null) {
|
|
314
|
+
return assert(target, schema.schemas[0], path);
|
|
315
|
+
}
|
|
316
|
+
} else if (schema instanceof Tuple) {
|
|
317
|
+
if (!Array.isArray(target)) {
|
|
318
|
+
return [
|
|
319
|
+
new AssertError(target, 'array', path)
|
|
320
|
+
];
|
|
321
|
+
}
|
|
322
|
+
return schema.schemas.flatMap((s, idx)=>assert(target[idx], s, `${path}[${idx}]`)).filter((error)=>!!error);
|
|
323
|
+
} else if (typeof schema === 'function') {
|
|
324
|
+
if (target === null || target === undefined) {
|
|
325
|
+
return [
|
|
326
|
+
new AssertError(target, 'a non-nullable', path)
|
|
327
|
+
];
|
|
169
328
|
}
|
|
170
329
|
if (typeof target === 'object' && !(target instanceof schema)) {
|
|
171
|
-
return
|
|
330
|
+
return [
|
|
331
|
+
new AssertError(target?.constructor?.name, `instance of ${schema.name}`, path, 'instance of')
|
|
332
|
+
];
|
|
172
333
|
}
|
|
173
|
-
if (typeof target !== 'object' && target
|
|
174
|
-
return
|
|
334
|
+
if (typeof target !== 'object' && target?.constructor !== schema) {
|
|
335
|
+
return [
|
|
336
|
+
new AssertError(target?.constructor?.name, schema.name, path, 'type')
|
|
337
|
+
];
|
|
175
338
|
}
|
|
176
339
|
} else if (Array.isArray(schema)) {
|
|
177
340
|
if (!Array.isArray(target)) {
|
|
178
|
-
return
|
|
341
|
+
return [
|
|
342
|
+
new AssertError(target, 'array', path)
|
|
343
|
+
];
|
|
179
344
|
}
|
|
180
|
-
return
|
|
181
|
-
|
|
182
|
-
});
|
|
183
|
-
} else if (typeof schema === 'object') {
|
|
345
|
+
return schema.flatMap((s)=>target.flatMap((value, idx)=>assert(value, s, `${path}[${idx}]`))).filter((error)=>!!error);
|
|
346
|
+
} else if (typeof schema === 'object' && schema !== null) {
|
|
184
347
|
if (schema instanceof RegExp) {
|
|
185
348
|
if (!schema.test('' + target)) {
|
|
186
|
-
return
|
|
349
|
+
return [
|
|
350
|
+
new AssertError(target, `matching ${schema.toString()}`, path)
|
|
351
|
+
];
|
|
187
352
|
}
|
|
353
|
+
return [];
|
|
188
354
|
} else {
|
|
189
|
-
if (
|
|
190
|
-
return
|
|
355
|
+
if (target === null || target === undefined) {
|
|
356
|
+
return [
|
|
357
|
+
new AssertError(target, 'object', path)
|
|
358
|
+
];
|
|
191
359
|
}
|
|
192
|
-
if (target
|
|
193
|
-
return
|
|
360
|
+
if (typeof target !== 'object') {
|
|
361
|
+
return [
|
|
362
|
+
new AssertError(target, schema.constructor.name, path)
|
|
363
|
+
];
|
|
194
364
|
}
|
|
195
365
|
if ($keys in schema) {
|
|
196
366
|
const targetKeys = Object.keys(target);
|
|
197
|
-
|
|
198
|
-
if (assertError) {
|
|
199
|
-
return assertError;
|
|
200
|
-
}
|
|
367
|
+
return targetKeys.flatMap((key)=>assert(key, schema[$keys], path)).filter((error)=>!!error);
|
|
201
368
|
}
|
|
202
369
|
if ($values in schema) {
|
|
203
370
|
const targetKeys = Object.keys(target);
|
|
204
|
-
|
|
205
|
-
if (assertError) {
|
|
206
|
-
return assertError;
|
|
207
|
-
}
|
|
371
|
+
return targetKeys.flatMap((key)=>assert(target[key], schema[$values], path)).filter((error)=>!!error);
|
|
208
372
|
}
|
|
209
|
-
return
|
|
373
|
+
return Object.keys(schema).flatMap((key)=>assert(target[key], schema[key], path)).filter((error)=>!!error);
|
|
374
|
+
}
|
|
375
|
+
} else if (schema === null || schema === undefined) {
|
|
376
|
+
if (target !== null && target !== undefined) {
|
|
377
|
+
return [
|
|
378
|
+
new AssertError(target, 'nullable', path)
|
|
379
|
+
];
|
|
210
380
|
}
|
|
211
381
|
} else if (target !== schema) {
|
|
212
|
-
return
|
|
382
|
+
return [
|
|
383
|
+
new AssertError(target, schema, path)
|
|
384
|
+
];
|
|
213
385
|
}
|
|
214
|
-
|
|
386
|
+
return [];
|
|
387
|
+
};
|
|
215
388
|
const ascertain = (schema, data, rootName = '[root]')=>{
|
|
216
|
-
const result =
|
|
217
|
-
if (result
|
|
218
|
-
throw new
|
|
389
|
+
const result = assert(data, schema, rootName).filter((error)=>!!error);
|
|
390
|
+
if (result.length > 0) {
|
|
391
|
+
throw new AggregateError(result, 'Validation failure');
|
|
219
392
|
}
|
|
220
393
|
};
|
|
221
|
-
const _default = ascertain;
|
|
222
394
|
|
|
223
395
|
//# sourceMappingURL=index.cjs.map
|