@webergency-utils/typechecker 0.1.10 → 0.2.1
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 +86 -36
- package/dist/engine/generators.d.ts +2 -1
- package/dist/engine/generators.js +37 -11
- package/dist/engine/resolver.js +279 -68
- package/dist/engine/staticAsserts.js +2 -1
- package/dist/index.d.ts +12 -6
- package/dist/index.js +19 -0
- package/dist/runtime/tags/format.d.ts +7 -0
- package/dist/runtime/tags/tag.d.ts +1 -1
- package/dist/runtime/tags.d.ts +1 -1
- package/dist/runtime/validators.d.ts +34 -10
- package/dist/runtime/validators.js +915 -212
- package/dist/transformer.js +5 -1
- package/package.json +12 -3
|
@@ -2,133 +2,519 @@ const report = (ctx, path, expected, value, message) => {
|
|
|
2
2
|
ctx.success = false;
|
|
3
3
|
ctx.errors.push({ path, value, error: message || expected });
|
|
4
4
|
};
|
|
5
|
+
function isPlainObject(v) {
|
|
6
|
+
if (v === null || typeof v !== 'object' || Array.isArray(v)) {
|
|
7
|
+
return false;
|
|
8
|
+
}
|
|
9
|
+
if (v instanceof Date || v instanceof RegExp || v instanceof Map || v instanceof Set) {
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
if (typeof Buffer !== 'undefined' && typeof Buffer.isBuffer === 'function' && Buffer.isBuffer(v)) {
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
if (ArrayBuffer.isView(v) || v instanceof ArrayBuffer) {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
const proto = Object.getPrototypeOf(v);
|
|
19
|
+
return proto === Object.prototype || proto === null;
|
|
20
|
+
}
|
|
21
|
+
function testRegex(regex, value) {
|
|
22
|
+
if (!regex.global && !regex.sticky) {
|
|
23
|
+
return regex.test(value);
|
|
24
|
+
}
|
|
25
|
+
const copy = new RegExp(regex.source, regex.flags);
|
|
26
|
+
return copy.test(value);
|
|
27
|
+
}
|
|
28
|
+
function isMultipleOfNumber(v, n) {
|
|
29
|
+
if (n === 0 || !Number.isFinite(n)) {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
if (!Number.isFinite(v)) {
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
const q = v / n;
|
|
36
|
+
return Math.abs(q - Math.round(q)) <= 1e-8 * Math.max(1, Math.abs(q));
|
|
37
|
+
}
|
|
38
|
+
function shouldMutate(ctx) {
|
|
39
|
+
return ctx.mutate === true;
|
|
40
|
+
}
|
|
41
|
+
function wantsQuery(ctx) {
|
|
42
|
+
return ctx.from === 'query';
|
|
43
|
+
}
|
|
44
|
+
function wantsJsonRevive(ctx) {
|
|
45
|
+
return ctx.from === 'json' || ctx.from === 'query';
|
|
46
|
+
}
|
|
47
|
+
function pathKey(path) {
|
|
48
|
+
if (!path) {
|
|
49
|
+
return '';
|
|
50
|
+
}
|
|
51
|
+
const bracket = path.lastIndexOf('[');
|
|
52
|
+
const dot = path.lastIndexOf('.');
|
|
53
|
+
if (bracket > dot) {
|
|
54
|
+
const end = path.lastIndexOf(']');
|
|
55
|
+
if (end > bracket) {
|
|
56
|
+
return path.slice(bracket + 1, end);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
if (dot >= 0) {
|
|
60
|
+
return path.slice(dot + 1);
|
|
61
|
+
}
|
|
62
|
+
return path;
|
|
63
|
+
}
|
|
64
|
+
function fromCustom(ctx, path, value, type) {
|
|
65
|
+
if (typeof ctx.from !== 'function') {
|
|
66
|
+
return value;
|
|
67
|
+
}
|
|
68
|
+
return ctx.from(pathKey(path), value, type);
|
|
69
|
+
}
|
|
70
|
+
/** Query-style number coercion — shared by `from: 'query'` and `transform.ToNumber`. */
|
|
71
|
+
export function coerceQueryNumber(v) {
|
|
72
|
+
if (typeof v === 'number') {
|
|
73
|
+
return v;
|
|
74
|
+
}
|
|
75
|
+
if (typeof v === 'string' && v.trim() !== '') {
|
|
76
|
+
const parsed = parseFloat(v);
|
|
77
|
+
if (!Number.isNaN(parsed)) {
|
|
78
|
+
return parsed;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return v;
|
|
82
|
+
}
|
|
83
|
+
/** Query-style boolean coercion — shared by `from: 'query'` and `transform.ToBoolean`. */
|
|
84
|
+
export function coerceQueryBoolean(v) {
|
|
85
|
+
if (typeof v === 'boolean') {
|
|
86
|
+
return v;
|
|
87
|
+
}
|
|
88
|
+
if (typeof v === 'string' || typeof v === 'number') {
|
|
89
|
+
const s = String(v).toLowerCase();
|
|
90
|
+
if (s === 'true' || s === '1' || s === 'yes' || s === 'on') {
|
|
91
|
+
return true;
|
|
92
|
+
}
|
|
93
|
+
if (s === 'false' || s === '0' || s === 'no' || s === 'off') {
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return v;
|
|
98
|
+
}
|
|
99
|
+
/** JSON-wire Date revival (ISO / date-parseable strings only). */
|
|
100
|
+
export function coerceJsonDate(v) {
|
|
101
|
+
if (v instanceof Date && !Number.isNaN(v.getTime())) {
|
|
102
|
+
return v;
|
|
103
|
+
}
|
|
104
|
+
if (typeof v === 'string') {
|
|
105
|
+
const parsed = new Date(v);
|
|
106
|
+
if (!Number.isNaN(parsed.getTime())) {
|
|
107
|
+
return parsed;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return v;
|
|
111
|
+
}
|
|
112
|
+
/** Query-style Date coercion — shared by `from: 'query'` and `transform.ToDate`. */
|
|
113
|
+
export function coerceQueryDate(v) {
|
|
114
|
+
const fromJson = coerceJsonDate(v);
|
|
115
|
+
if (fromJson !== v) {
|
|
116
|
+
return fromJson;
|
|
117
|
+
}
|
|
118
|
+
if (v instanceof Date && !Number.isNaN(v.getTime())) {
|
|
119
|
+
return v;
|
|
120
|
+
}
|
|
121
|
+
if (typeof v === 'number' && Number.isFinite(v)) {
|
|
122
|
+
const parsed = new Date(v);
|
|
123
|
+
if (!Number.isNaN(parsed.getTime())) {
|
|
124
|
+
return parsed;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return v;
|
|
128
|
+
}
|
|
129
|
+
const EMAIL_LOCAL_RE = /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+$/;
|
|
130
|
+
const EMAIL_DOMAIN_RE = /^(?=.{1,253}$)(?:(?!-)[A-Za-z0-9-]{1,63}(?<!-)\.)+[A-Za-z]{2,63}$/i;
|
|
131
|
+
const IDN_EMAIL_LOCAL_RE = /^[\p{L}\p{N}.!#$%&'*+/=?^_`{|}~-]+$/u;
|
|
132
|
+
const IDN_EMAIL_DOMAIN_RE = /^(?=.{1,253}$)(?:(?!-)[\p{L}\p{N}-]{1,63}(?<!-)\.)+[\p{L}]{2,63}$/u;
|
|
133
|
+
const HOSTNAME_RE = /^(?=.{1,253}$)(?:localhost|(?:(?!-)[A-Za-z0-9-]{1,63}(?<!-)\.)+[A-Za-z]{2,63})$/i;
|
|
134
|
+
const IDN_HOSTNAME_RE = /^(?=.{1,253}$)(?:localhost|(?:(?!-)[\p{L}\p{N}-]{1,63}(?<!-)\.)+[\p{L}]{2,63})$/iu;
|
|
135
|
+
const URI_RE = /^[a-zA-Z][a-zA-Z0-9+.-]*:[^\s<>"{}|\\^`]*$/;
|
|
136
|
+
const IRI_RE = /^[a-zA-Z][a-zA-Z0-9+.-]*:\S+$/u;
|
|
137
|
+
const URI_TEMPLATE_RE = /^(?:[^{}\s]|\{[+#./;?&=,!@|]?(?:[A-Za-z0-9_]|%[0-9A-Fa-f]{2})(?:\.?(?:[A-Za-z0-9_]|%[0-9A-Fa-f]{2}))*(?::[1-9]\d{0,3}|\*)?(?:,(?:[A-Za-z0-9_]|%[0-9A-Fa-f]{2})(?:\.?(?:[A-Za-z0-9_]|%[0-9A-Fa-f]{2}))*(?::[1-9]\d{0,3}|\*)?)*\})+$/;
|
|
138
|
+
function isEmail(value) {
|
|
139
|
+
if (value.length > 254) {
|
|
140
|
+
return false;
|
|
141
|
+
}
|
|
142
|
+
const at = value.lastIndexOf('@');
|
|
143
|
+
if (at < 1 || at !== value.indexOf('@') || at === value.length - 1) {
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
146
|
+
const local = value.slice(0, at);
|
|
147
|
+
const domain = value.slice(at + 1);
|
|
148
|
+
if (local.length > 64) {
|
|
149
|
+
return false;
|
|
150
|
+
}
|
|
151
|
+
if (local.startsWith('.') || local.endsWith('.') || local.includes('..')) {
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
if (!EMAIL_LOCAL_RE.test(local)) {
|
|
155
|
+
return false;
|
|
156
|
+
}
|
|
157
|
+
if (!EMAIL_DOMAIN_RE.test(domain)) {
|
|
158
|
+
return false;
|
|
159
|
+
}
|
|
160
|
+
return true;
|
|
161
|
+
}
|
|
162
|
+
function isIdnEmail(value) {
|
|
163
|
+
if (value.length > 254) {
|
|
164
|
+
return false;
|
|
165
|
+
}
|
|
166
|
+
const at = value.lastIndexOf('@');
|
|
167
|
+
if (at < 1 || at !== value.indexOf('@') || at === value.length - 1) {
|
|
168
|
+
return false;
|
|
169
|
+
}
|
|
170
|
+
const local = value.slice(0, at);
|
|
171
|
+
const domain = value.slice(at + 1);
|
|
172
|
+
if (local.length > 64) {
|
|
173
|
+
return false;
|
|
174
|
+
}
|
|
175
|
+
if (local.startsWith('.') || local.endsWith('.') || local.includes('..')) {
|
|
176
|
+
return false;
|
|
177
|
+
}
|
|
178
|
+
if (!IDN_EMAIL_LOCAL_RE.test(local)) {
|
|
179
|
+
return false;
|
|
180
|
+
}
|
|
181
|
+
if (!IDN_EMAIL_DOMAIN_RE.test(domain)) {
|
|
182
|
+
return false;
|
|
183
|
+
}
|
|
184
|
+
return true;
|
|
185
|
+
}
|
|
186
|
+
function isHostname(value) {
|
|
187
|
+
return HOSTNAME_RE.test(value);
|
|
188
|
+
}
|
|
189
|
+
function isIdnHostname(value) {
|
|
190
|
+
return IDN_HOSTNAME_RE.test(value);
|
|
191
|
+
}
|
|
192
|
+
function isUri(value) {
|
|
193
|
+
if (!URI_RE.test(value)) {
|
|
194
|
+
return false;
|
|
195
|
+
}
|
|
196
|
+
try {
|
|
197
|
+
// eslint-disable-next-line no-new
|
|
198
|
+
new URL(value);
|
|
199
|
+
return true;
|
|
200
|
+
}
|
|
201
|
+
catch {
|
|
202
|
+
// mailto:, urn:, and some other schemes are valid URIs but may throw in URL()
|
|
203
|
+
return /^[a-zA-Z][a-zA-Z0-9+.-]*:[^\s<>"{}|\\^`]+$/.test(value);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
function isUriReference(value) {
|
|
207
|
+
if (value === '') {
|
|
208
|
+
return true;
|
|
209
|
+
}
|
|
210
|
+
if (isUri(value)) {
|
|
211
|
+
return true;
|
|
212
|
+
}
|
|
213
|
+
if (/[\s<>"{}|\\^`]/.test(value)) {
|
|
214
|
+
return false;
|
|
215
|
+
}
|
|
216
|
+
try {
|
|
217
|
+
// eslint-disable-next-line no-new
|
|
218
|
+
new URL(value, 'http://example.com');
|
|
219
|
+
return true;
|
|
220
|
+
}
|
|
221
|
+
catch {
|
|
222
|
+
return false;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
function isIri(value) {
|
|
226
|
+
if (!IRI_RE.test(value)) {
|
|
227
|
+
return false;
|
|
228
|
+
}
|
|
229
|
+
if (/[\u0000-\u001F\u007F]/.test(value)) {
|
|
230
|
+
return false;
|
|
231
|
+
}
|
|
232
|
+
try {
|
|
233
|
+
// eslint-disable-next-line no-new
|
|
234
|
+
new URL(value);
|
|
235
|
+
return true;
|
|
236
|
+
}
|
|
237
|
+
catch {
|
|
238
|
+
return /^[a-zA-Z][a-zA-Z0-9+.-]*:\S+$/u.test(value);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
function isIriReference(value) {
|
|
242
|
+
if (value === '') {
|
|
243
|
+
return true;
|
|
244
|
+
}
|
|
245
|
+
if (isIri(value)) {
|
|
246
|
+
return true;
|
|
247
|
+
}
|
|
248
|
+
if (/\s|[\u0000-\u001F\u007F]/.test(value)) {
|
|
249
|
+
return false;
|
|
250
|
+
}
|
|
251
|
+
try {
|
|
252
|
+
// eslint-disable-next-line no-new
|
|
253
|
+
new URL(value, 'http://example.com');
|
|
254
|
+
return true;
|
|
255
|
+
}
|
|
256
|
+
catch {
|
|
257
|
+
return /^[^<>"{}|\\^`]+$/u.test(value);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
function isUriTemplate(value) {
|
|
261
|
+
if (!value || /\s/.test(value)) {
|
|
262
|
+
return false;
|
|
263
|
+
}
|
|
264
|
+
let depth = 0;
|
|
265
|
+
for (const ch of value) {
|
|
266
|
+
if (ch === '{') {
|
|
267
|
+
depth++;
|
|
268
|
+
}
|
|
269
|
+
else if (ch === '}') {
|
|
270
|
+
if (depth === 0) {
|
|
271
|
+
return false;
|
|
272
|
+
}
|
|
273
|
+
depth--;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
if (depth !== 0) {
|
|
277
|
+
return false;
|
|
278
|
+
}
|
|
279
|
+
return URI_TEMPLATE_RE.test(value);
|
|
280
|
+
}
|
|
281
|
+
function parseFormatDate(value) {
|
|
282
|
+
if (typeof value !== 'string' || !/^\d{4}-\d{2}-\d{2}$/.test(value)) {
|
|
283
|
+
return undefined;
|
|
284
|
+
}
|
|
285
|
+
const parsed = new Date(value);
|
|
286
|
+
if (Number.isNaN(parsed.getTime())) {
|
|
287
|
+
return undefined;
|
|
288
|
+
}
|
|
289
|
+
return parsed;
|
|
290
|
+
}
|
|
291
|
+
function parseFormatDateTime(value) {
|
|
292
|
+
if (typeof value !== 'string') {
|
|
293
|
+
return undefined;
|
|
294
|
+
}
|
|
295
|
+
const parsed = new Date(value);
|
|
296
|
+
if (Number.isNaN(parsed.getTime())) {
|
|
297
|
+
return undefined;
|
|
298
|
+
}
|
|
299
|
+
return parsed;
|
|
300
|
+
}
|
|
301
|
+
function stableStringify(value, seen = new WeakSet()) {
|
|
302
|
+
if (value === null || typeof value !== 'object') {
|
|
303
|
+
return JSON.stringify(value);
|
|
304
|
+
}
|
|
305
|
+
if (seen.has(value)) {
|
|
306
|
+
return undefined;
|
|
307
|
+
}
|
|
308
|
+
seen.add(value);
|
|
309
|
+
if (Array.isArray(value)) {
|
|
310
|
+
const parts = value.map(item => {
|
|
311
|
+
const s = stableStringify(item, seen);
|
|
312
|
+
return s === undefined ? '"[Circular]"' : s;
|
|
313
|
+
});
|
|
314
|
+
return `[${parts.join(',')}]`;
|
|
315
|
+
}
|
|
316
|
+
const keys = Object.keys(value).sort();
|
|
317
|
+
const parts = keys.map(key => `${JSON.stringify(key)}:${stableStringify(value[key], seen) ?? '"[Circular]"'}`);
|
|
318
|
+
return `{${parts.join(',')}}`;
|
|
319
|
+
}
|
|
5
320
|
export const validators = {
|
|
321
|
+
coerceQueryNumber,
|
|
322
|
+
coerceQueryBoolean,
|
|
323
|
+
coerceQueryDate,
|
|
324
|
+
coerceJsonDate,
|
|
6
325
|
string: (v, path, ctx) => {
|
|
7
|
-
if (typeof v
|
|
8
|
-
|
|
326
|
+
if (typeof v === 'string') {
|
|
327
|
+
return v;
|
|
9
328
|
}
|
|
329
|
+
if (typeof ctx.from === 'function') {
|
|
330
|
+
const converted = fromCustom(ctx, path, v, 'string');
|
|
331
|
+
if (typeof converted === 'string') {
|
|
332
|
+
return converted;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
report(ctx, path, 'Type<string>', v);
|
|
10
336
|
return v;
|
|
11
337
|
},
|
|
12
338
|
number: (v, path, ctx) => {
|
|
13
|
-
if (
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
339
|
+
if (wantsQuery(ctx)) {
|
|
340
|
+
v = coerceQueryNumber(v);
|
|
341
|
+
}
|
|
342
|
+
if (typeof v === 'number') {
|
|
343
|
+
if (Number.isNaN(v)) {
|
|
344
|
+
report(ctx, path, 'Type<number>', v);
|
|
19
345
|
}
|
|
20
|
-
|
|
346
|
+
return v;
|
|
21
347
|
}
|
|
348
|
+
if (typeof ctx.from === 'function') {
|
|
349
|
+
const converted = fromCustom(ctx, path, v, 'number');
|
|
350
|
+
if (typeof converted === 'number' && !Number.isNaN(converted)) {
|
|
351
|
+
return converted;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
report(ctx, path, 'Type<number>', v);
|
|
22
355
|
return v;
|
|
23
356
|
},
|
|
24
357
|
bigint: (v, path, ctx) => {
|
|
25
|
-
if (typeof v
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
358
|
+
if (typeof v === 'bigint') {
|
|
359
|
+
return v;
|
|
360
|
+
}
|
|
361
|
+
if (wantsJsonRevive(ctx) && typeof v === 'string' && v.trim() !== '') {
|
|
362
|
+
try {
|
|
363
|
+
return BigInt(v);
|
|
31
364
|
}
|
|
32
|
-
|
|
365
|
+
catch (e) { /* ignore */ }
|
|
33
366
|
}
|
|
367
|
+
if (wantsQuery(ctx) && typeof v === 'number' && Number.isFinite(v) && Number.isInteger(v)) {
|
|
368
|
+
try {
|
|
369
|
+
return BigInt(v);
|
|
370
|
+
}
|
|
371
|
+
catch (e) { /* ignore */ }
|
|
372
|
+
}
|
|
373
|
+
if (typeof ctx.from === 'function') {
|
|
374
|
+
const converted = fromCustom(ctx, path, v, 'bigint');
|
|
375
|
+
if (typeof converted === 'bigint') {
|
|
376
|
+
return converted;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
report(ctx, path, 'Type<bigint>', v);
|
|
34
380
|
return v;
|
|
35
381
|
},
|
|
36
382
|
boolean: (v, path, ctx) => {
|
|
37
|
-
if (
|
|
38
|
-
|
|
39
|
-
|
|
383
|
+
if (wantsQuery(ctx)) {
|
|
384
|
+
v = coerceQueryBoolean(v);
|
|
385
|
+
}
|
|
386
|
+
if (typeof v === 'boolean') {
|
|
387
|
+
return v;
|
|
388
|
+
}
|
|
389
|
+
if (typeof ctx.from === 'function') {
|
|
390
|
+
const converted = fromCustom(ctx, path, v, 'boolean');
|
|
391
|
+
if (typeof converted === 'boolean') {
|
|
392
|
+
return converted;
|
|
40
393
|
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
394
|
+
}
|
|
395
|
+
report(ctx, path, 'Type<boolean>', v);
|
|
396
|
+
return v;
|
|
397
|
+
},
|
|
398
|
+
function: (v, path, ctx) => {
|
|
399
|
+
if (typeof v === 'function') {
|
|
400
|
+
return v;
|
|
401
|
+
}
|
|
402
|
+
if (typeof ctx.from === 'function') {
|
|
403
|
+
const converted = fromCustom(ctx, path, v, 'function');
|
|
404
|
+
if (typeof converted === 'function') {
|
|
405
|
+
return converted;
|
|
49
406
|
}
|
|
50
|
-
report(ctx, path, 'Type<boolean>', v);
|
|
51
407
|
}
|
|
408
|
+
report(ctx, path, 'Type<function>', v);
|
|
52
409
|
return v;
|
|
53
410
|
},
|
|
54
411
|
date: (v, path, ctx) => {
|
|
55
|
-
if (
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
412
|
+
if (wantsQuery(ctx)) {
|
|
413
|
+
v = coerceQueryDate(v);
|
|
414
|
+
}
|
|
415
|
+
else if (ctx.from === 'json') {
|
|
416
|
+
v = coerceJsonDate(v);
|
|
417
|
+
}
|
|
418
|
+
if (v instanceof Date && !Number.isNaN(v.getTime())) {
|
|
419
|
+
return v;
|
|
420
|
+
}
|
|
421
|
+
if (typeof ctx.from === 'function') {
|
|
422
|
+
const converted = fromCustom(ctx, path, v, 'Date');
|
|
423
|
+
if (converted instanceof Date && !Number.isNaN(converted.getTime())) {
|
|
424
|
+
return converted;
|
|
61
425
|
}
|
|
62
|
-
report(ctx, path, 'Type<Date>', v);
|
|
63
426
|
}
|
|
427
|
+
report(ctx, path, 'Type<Date>', v);
|
|
64
428
|
return v;
|
|
65
429
|
},
|
|
66
430
|
regexp: (v, path, ctx) => {
|
|
67
|
-
if (
|
|
68
|
-
|
|
431
|
+
if (v instanceof RegExp) {
|
|
432
|
+
return v;
|
|
433
|
+
}
|
|
434
|
+
if (wantsJsonRevive(ctx)) {
|
|
435
|
+
if (typeof v === 'string') {
|
|
69
436
|
const match = v.match(/^\/(.*)\/([gimuy]*)$/);
|
|
70
437
|
if (match) {
|
|
71
438
|
try {
|
|
72
439
|
return new RegExp(match[1], match[2]);
|
|
73
440
|
}
|
|
74
|
-
catch (e) { }
|
|
441
|
+
catch (e) { /* fall through */ }
|
|
75
442
|
}
|
|
76
|
-
else {
|
|
443
|
+
else if (wantsQuery(ctx)) {
|
|
77
444
|
try {
|
|
78
445
|
return new RegExp(v);
|
|
79
446
|
}
|
|
80
|
-
catch (e) { }
|
|
447
|
+
catch (e) { /* fall through */ }
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
if (v && typeof v === 'object' && typeof v.source === 'string') {
|
|
451
|
+
try {
|
|
452
|
+
return new RegExp(v.source, typeof v.flags === 'string' ? v.flags : '');
|
|
81
453
|
}
|
|
454
|
+
catch (e) { /* fall through */ }
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
if (typeof ctx.from === 'function') {
|
|
458
|
+
const converted = fromCustom(ctx, path, v, 'RegExp');
|
|
459
|
+
if (converted instanceof RegExp) {
|
|
460
|
+
return converted;
|
|
82
461
|
}
|
|
83
|
-
report(ctx, path, 'Type<RegExp>', v);
|
|
84
462
|
}
|
|
463
|
+
report(ctx, path, 'Type<RegExp>', v);
|
|
85
464
|
return v;
|
|
86
465
|
},
|
|
87
466
|
null: (v, path, ctx) => {
|
|
88
|
-
if (v
|
|
89
|
-
|
|
467
|
+
if (v === null) {
|
|
468
|
+
return null;
|
|
90
469
|
}
|
|
470
|
+
if (typeof ctx.from === 'function') {
|
|
471
|
+
const converted = fromCustom(ctx, path, v, 'null');
|
|
472
|
+
if (converted === null) {
|
|
473
|
+
return null;
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
report(ctx, path, 'Type<null>', v);
|
|
91
477
|
return null;
|
|
92
478
|
},
|
|
93
479
|
undefined: (v, path, ctx) => {
|
|
94
|
-
if (v
|
|
95
|
-
|
|
480
|
+
if (v === undefined) {
|
|
481
|
+
return undefined;
|
|
96
482
|
}
|
|
483
|
+
if (typeof ctx.from === 'function') {
|
|
484
|
+
const converted = fromCustom(ctx, path, v, 'undefined');
|
|
485
|
+
if (converted === undefined) {
|
|
486
|
+
return undefined;
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
report(ctx, path, 'Type<undefined>', v);
|
|
97
490
|
return undefined;
|
|
98
491
|
},
|
|
99
492
|
literal: (v, path, ctx, expected) => {
|
|
100
|
-
if (v
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
493
|
+
if (v === expected) {
|
|
494
|
+
return v;
|
|
495
|
+
}
|
|
496
|
+
if (wantsQuery(ctx)) {
|
|
497
|
+
if (typeof expected === 'number') {
|
|
498
|
+
const p = coerceQueryNumber(v);
|
|
499
|
+
if (p === expected) {
|
|
500
|
+
return p;
|
|
106
501
|
}
|
|
107
502
|
}
|
|
108
|
-
if (
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
return p;
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
if (typeof expected === 'boolean') {
|
|
116
|
-
const s = v.toLowerCase();
|
|
117
|
-
let val;
|
|
118
|
-
if (s === 'true' || s === '1' || s === 'yes' || s === 'on') {
|
|
119
|
-
val = true;
|
|
120
|
-
}
|
|
121
|
-
else if (s === 'false' || s === '0' || s === 'no' || s === 'off') {
|
|
122
|
-
val = false;
|
|
123
|
-
}
|
|
124
|
-
if (val === expected) {
|
|
125
|
-
return val;
|
|
126
|
-
}
|
|
503
|
+
if (typeof expected === 'boolean') {
|
|
504
|
+
const val = coerceQueryBoolean(v);
|
|
505
|
+
if (val === expected) {
|
|
506
|
+
return val;
|
|
127
507
|
}
|
|
128
508
|
}
|
|
129
|
-
const expStr = typeof expected === 'string' ? `'${expected}'` : expected;
|
|
130
|
-
report(ctx, path, `Literal<${expStr}>`, v);
|
|
131
509
|
}
|
|
510
|
+
if (typeof ctx.from === 'function') {
|
|
511
|
+
const converted = fromCustom(ctx, path, v, 'literal');
|
|
512
|
+
if (converted === expected) {
|
|
513
|
+
return converted;
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
const expStr = typeof expected === 'string' ? `'${expected}'` : expected;
|
|
517
|
+
report(ctx, path, `Literal<${expStr}>`, v);
|
|
132
518
|
return v;
|
|
133
519
|
},
|
|
134
520
|
array: (v, path, ctx, childValidator) => {
|
|
@@ -136,17 +522,26 @@ export const validators = {
|
|
|
136
522
|
if (ctx.wrapArrays && v !== undefined && v !== null) {
|
|
137
523
|
v = [v];
|
|
138
524
|
}
|
|
525
|
+
else if (typeof ctx.from === 'function') {
|
|
526
|
+
const converted = fromCustom(ctx, path, v, 'Array');
|
|
527
|
+
if (Array.isArray(converted)) {
|
|
528
|
+
v = converted;
|
|
529
|
+
}
|
|
530
|
+
else {
|
|
531
|
+
report(ctx, path, 'Type<Array>', v);
|
|
532
|
+
return v;
|
|
533
|
+
}
|
|
534
|
+
}
|
|
139
535
|
else {
|
|
140
536
|
report(ctx, path, 'Type<Array>', v);
|
|
141
537
|
return v;
|
|
142
538
|
}
|
|
143
539
|
}
|
|
144
|
-
const
|
|
540
|
+
const mutate = shouldMutate(ctx);
|
|
541
|
+
const data = mutate ? v : [];
|
|
145
542
|
for (let i = 0; i < v.length; i++) {
|
|
146
543
|
const val = childValidator(v[i], path + '[' + i + ']', ctx);
|
|
147
|
-
|
|
148
|
-
data.push(val);
|
|
149
|
-
}
|
|
544
|
+
data[i] = val;
|
|
150
545
|
}
|
|
151
546
|
return data;
|
|
152
547
|
},
|
|
@@ -154,20 +549,67 @@ export const validators = {
|
|
|
154
549
|
for (const [key, isOptional, validator] of props) {
|
|
155
550
|
const val = v[key];
|
|
156
551
|
const oldErrors = ctx.errors.length;
|
|
552
|
+
const wasSuccess = ctx.success;
|
|
157
553
|
const result = validator(val, path + '.' + key, ctx);
|
|
158
554
|
if (ctx.success) {
|
|
159
555
|
data[key] = result;
|
|
160
556
|
}
|
|
161
557
|
else if (isOptional && val === undefined) {
|
|
162
|
-
ctx.success = true;
|
|
163
558
|
ctx.errors.length = oldErrors;
|
|
559
|
+
ctx.success = wasSuccess;
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
},
|
|
563
|
+
objectShell: (v, ctx) => {
|
|
564
|
+
if (shouldMutate(ctx)) {
|
|
565
|
+
return v;
|
|
566
|
+
}
|
|
567
|
+
if (!isPlainObject(v)) {
|
|
568
|
+
return v;
|
|
569
|
+
}
|
|
570
|
+
if (ctx.mode === 'strip') {
|
|
571
|
+
return {};
|
|
572
|
+
}
|
|
573
|
+
return { ...v };
|
|
574
|
+
},
|
|
575
|
+
stripExtras: (data, ctx, allowedKeys) => {
|
|
576
|
+
if (!shouldMutate(ctx) || ctx.mode !== 'strip' || !allowedKeys || !data || typeof data !== 'object') {
|
|
577
|
+
return data;
|
|
578
|
+
}
|
|
579
|
+
for (const k of Object.keys(data)) {
|
|
580
|
+
if (!allowedKeys.includes(k)) {
|
|
581
|
+
delete data[k];
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
return data;
|
|
585
|
+
},
|
|
586
|
+
additionalProps: (v, data, path, ctx, knownKeys, childValidator) => {
|
|
587
|
+
if (!isPlainObject(v)) {
|
|
588
|
+
return;
|
|
589
|
+
}
|
|
590
|
+
for (const key of Object.keys(v)) {
|
|
591
|
+
if (knownKeys.includes(key)) {
|
|
592
|
+
continue;
|
|
164
593
|
}
|
|
594
|
+
data[key] = childValidator(v[key], path + '.' + key, ctx);
|
|
165
595
|
}
|
|
166
596
|
},
|
|
167
597
|
object: (v, path, ctx, allowedKeys, expected = 'Type<Object>') => {
|
|
168
|
-
if (!
|
|
169
|
-
|
|
170
|
-
|
|
598
|
+
if (!isPlainObject(v)) {
|
|
599
|
+
if (typeof ctx.from === 'function') {
|
|
600
|
+
const converted = fromCustom(ctx, path, v, 'Object');
|
|
601
|
+
if (isPlainObject(converted)) {
|
|
602
|
+
v = converted;
|
|
603
|
+
}
|
|
604
|
+
else {
|
|
605
|
+
report(ctx, path, expected, v);
|
|
606
|
+
return false;
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
else {
|
|
610
|
+
report(ctx, path, expected, v);
|
|
611
|
+
return false;
|
|
612
|
+
}
|
|
171
613
|
}
|
|
172
614
|
if (ctx.mode === 'strict' && allowedKeys) {
|
|
173
615
|
for (const k of Object.keys(v)) {
|
|
@@ -176,10 +618,10 @@ export const validators = {
|
|
|
176
618
|
}
|
|
177
619
|
}
|
|
178
620
|
}
|
|
179
|
-
return
|
|
621
|
+
return v;
|
|
180
622
|
},
|
|
181
623
|
templateLiteral: (v, path, ctx, regex, expected) => {
|
|
182
|
-
if (typeof v !== 'string' || !regex
|
|
624
|
+
if (typeof v !== 'string' || !testRegex(regex, v)) {
|
|
183
625
|
report(ctx, path, expected, v);
|
|
184
626
|
}
|
|
185
627
|
return v;
|
|
@@ -226,15 +668,13 @@ export const validators = {
|
|
|
226
668
|
report(ctx, path, `MultipleOf<${n}>`, v, message);
|
|
227
669
|
}
|
|
228
670
|
}
|
|
229
|
-
else {
|
|
230
|
-
|
|
231
|
-
report(ctx, path, `MultipleOf<${n}>`, v, message);
|
|
232
|
-
}
|
|
671
|
+
else if (!isMultipleOfNumber(v, n)) {
|
|
672
|
+
report(ctx, path, `MultipleOf<${n}>`, v, message);
|
|
233
673
|
}
|
|
234
674
|
return v;
|
|
235
675
|
},
|
|
236
676
|
pattern: (v, path, ctx, regex, expected, message) => {
|
|
237
|
-
if (!regex
|
|
677
|
+
if (!testRegex(regex, v)) {
|
|
238
678
|
report(ctx, path, expected, v, message);
|
|
239
679
|
}
|
|
240
680
|
return v;
|
|
@@ -242,9 +682,13 @@ export const validators = {
|
|
|
242
682
|
format: (v, path, ctx, format, message) => {
|
|
243
683
|
let regex;
|
|
244
684
|
let isValid = true;
|
|
685
|
+
let result = v;
|
|
245
686
|
switch (format) {
|
|
246
687
|
case 'email':
|
|
247
|
-
|
|
688
|
+
isValid = isEmail(v);
|
|
689
|
+
break;
|
|
690
|
+
case 'idn-email':
|
|
691
|
+
isValid = isIdnEmail(v);
|
|
248
692
|
break;
|
|
249
693
|
case 'uuid':
|
|
250
694
|
regex = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
@@ -259,11 +703,27 @@ export const validators = {
|
|
|
259
703
|
regex = /^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$/;
|
|
260
704
|
break;
|
|
261
705
|
case 'date':
|
|
262
|
-
|
|
263
|
-
|
|
706
|
+
{
|
|
707
|
+
const parsed = parseFormatDate(v);
|
|
708
|
+
if (!parsed) {
|
|
709
|
+
isValid = false;
|
|
710
|
+
}
|
|
711
|
+
else if (wantsQuery(ctx)) {
|
|
712
|
+
result = parsed;
|
|
713
|
+
}
|
|
714
|
+
break;
|
|
715
|
+
}
|
|
264
716
|
case 'date-time':
|
|
265
|
-
|
|
266
|
-
|
|
717
|
+
{
|
|
718
|
+
const parsed = parseFormatDateTime(v);
|
|
719
|
+
if (!parsed) {
|
|
720
|
+
isValid = false;
|
|
721
|
+
}
|
|
722
|
+
else if (wantsQuery(ctx)) {
|
|
723
|
+
result = parsed;
|
|
724
|
+
}
|
|
725
|
+
break;
|
|
726
|
+
}
|
|
267
727
|
case 'byte':
|
|
268
728
|
regex = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/;
|
|
269
729
|
break;
|
|
@@ -278,10 +738,25 @@ export const validators = {
|
|
|
278
738
|
;
|
|
279
739
|
break;
|
|
280
740
|
case 'hostname':
|
|
281
|
-
|
|
741
|
+
isValid = isHostname(v);
|
|
742
|
+
break;
|
|
743
|
+
case 'idn-hostname':
|
|
744
|
+
isValid = isIdnHostname(v);
|
|
282
745
|
break;
|
|
283
746
|
case 'uri':
|
|
284
|
-
|
|
747
|
+
isValid = isUri(v);
|
|
748
|
+
break;
|
|
749
|
+
case 'uri-reference':
|
|
750
|
+
isValid = isUriReference(v);
|
|
751
|
+
break;
|
|
752
|
+
case 'iri':
|
|
753
|
+
isValid = isIri(v);
|
|
754
|
+
break;
|
|
755
|
+
case 'iri-reference':
|
|
756
|
+
isValid = isIriReference(v);
|
|
757
|
+
break;
|
|
758
|
+
case 'uri-template':
|
|
759
|
+
isValid = isUriTemplate(v);
|
|
285
760
|
break;
|
|
286
761
|
case 'time':
|
|
287
762
|
regex = /^\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:[zZ]|[+-]\d{2}:\d{2})$/;
|
|
@@ -292,15 +767,17 @@ export const validators = {
|
|
|
292
767
|
case 'objectId':
|
|
293
768
|
regex = /^[0-9a-fA-F]{24}$/;
|
|
294
769
|
break;
|
|
295
|
-
default:
|
|
770
|
+
default:
|
|
771
|
+
isValid = false;
|
|
772
|
+
break;
|
|
296
773
|
}
|
|
297
|
-
if (regex && !regex
|
|
774
|
+
if (regex && !testRegex(regex, v)) {
|
|
298
775
|
isValid = false;
|
|
299
776
|
}
|
|
300
777
|
if (!isValid) {
|
|
301
778
|
report(ctx, path, `Format<${format}>`, v, message);
|
|
302
779
|
}
|
|
303
|
-
return
|
|
780
|
+
return result;
|
|
304
781
|
},
|
|
305
782
|
minItems: (v, path, ctx, min, message) => {
|
|
306
783
|
if (v.length < min) {
|
|
@@ -318,7 +795,9 @@ export const validators = {
|
|
|
318
795
|
const seen = new Set();
|
|
319
796
|
for (let i = 0; i < v.length; i++) {
|
|
320
797
|
const item = v[i];
|
|
321
|
-
const key = typeof item === 'object' && item !== null
|
|
798
|
+
const key = typeof item === 'object' && item !== null
|
|
799
|
+
? stableStringify(item) ?? item
|
|
800
|
+
: item;
|
|
322
801
|
if (seen.has(key)) {
|
|
323
802
|
report(ctx, path, 'UniqueItems', v, message);
|
|
324
803
|
break;
|
|
@@ -328,58 +807,107 @@ export const validators = {
|
|
|
328
807
|
return v;
|
|
329
808
|
},
|
|
330
809
|
custom: (v, path, ctx, fn, message) => {
|
|
331
|
-
const
|
|
810
|
+
const pathParts = tokenizePath(path);
|
|
811
|
+
const parentPath = joinPathSegments(pathParts.slice(0, -1));
|
|
332
812
|
const parent = getValueAtPath(ctx.root, parentPath);
|
|
333
|
-
const
|
|
334
|
-
const index =
|
|
335
|
-
if (!fn(v, { parent, root: ctx.root, path, index })) {
|
|
813
|
+
const last = pathParts[pathParts.length - 1];
|
|
814
|
+
const index = last && last.startsWith('[') ? parseInt(last.slice(1, -1), 10) : undefined;
|
|
815
|
+
if (!fn(v, { parent, root: ctx.root, path, index: Number.isNaN(index) ? undefined : index })) {
|
|
336
816
|
report(ctx, path, fn.name ? `Custom<${fn.name}>` : 'Custom', v, message);
|
|
337
817
|
}
|
|
338
818
|
return v;
|
|
339
819
|
},
|
|
340
820
|
union: (v, path, ctx, checks, expected = 'Type<Union>') => {
|
|
821
|
+
const unionErrors = [];
|
|
341
822
|
// Pass 1: No conversion
|
|
342
823
|
for (const check of checks) {
|
|
343
|
-
const subCtx = { ...ctx, success: true, errors: [],
|
|
824
|
+
const subCtx = { ...ctx, success: true, errors: [], from: undefined };
|
|
344
825
|
const val = check(v, path, subCtx);
|
|
345
826
|
if (subCtx.success) {
|
|
346
827
|
return val;
|
|
347
828
|
}
|
|
829
|
+
unionErrors.push(...subCtx.errors);
|
|
348
830
|
}
|
|
349
|
-
// Pass 2: With conversion
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
const
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
831
|
+
// Pass 2: With conversion (only when caller opted in)
|
|
832
|
+
if (ctx.from) {
|
|
833
|
+
unionErrors.length = 0;
|
|
834
|
+
for (const check of checks) {
|
|
835
|
+
const subCtx = { ...ctx, success: true, errors: [] };
|
|
836
|
+
const val = check(v, path, subCtx);
|
|
837
|
+
if (subCtx.success) {
|
|
838
|
+
return val;
|
|
839
|
+
}
|
|
840
|
+
unionErrors.push(...subCtx.errors);
|
|
356
841
|
}
|
|
357
|
-
unionErrors.push(...subCtx.errors);
|
|
358
842
|
}
|
|
359
843
|
ctx.success = false;
|
|
360
844
|
ctx.errors.push({
|
|
361
845
|
path,
|
|
362
846
|
value: v,
|
|
363
|
-
error: expected
|
|
847
|
+
error: expected,
|
|
848
|
+
issues: unionErrors.length > 0 ? unionErrors : undefined
|
|
364
849
|
});
|
|
365
|
-
ctx.errors.push(...unionErrors);
|
|
366
850
|
return v;
|
|
367
851
|
},
|
|
368
852
|
tuple: (v, path, ctx, checks) => {
|
|
369
853
|
if (!Array.isArray(v) || v.length !== checks.length) {
|
|
370
|
-
|
|
371
|
-
|
|
854
|
+
if (typeof ctx.from === 'function') {
|
|
855
|
+
const converted = fromCustom(ctx, path, v, 'tuple');
|
|
856
|
+
if (Array.isArray(converted) && converted.length === checks.length) {
|
|
857
|
+
v = converted;
|
|
858
|
+
}
|
|
859
|
+
else {
|
|
860
|
+
report(ctx, path, `Tuple<${checks.length}>`, v);
|
|
861
|
+
return v;
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
else {
|
|
865
|
+
report(ctx, path, `Tuple<${checks.length}>`, v);
|
|
866
|
+
return v;
|
|
867
|
+
}
|
|
372
868
|
}
|
|
373
|
-
const
|
|
869
|
+
const mutate = shouldMutate(ctx);
|
|
870
|
+
const data = mutate ? v : [];
|
|
374
871
|
for (let i = 0; i < checks.length; i++) {
|
|
375
|
-
|
|
376
|
-
if (ctx.mode === 'strip') {
|
|
377
|
-
data.push(val);
|
|
378
|
-
}
|
|
872
|
+
data[i] = checks[i](v[i], path + '[' + i + ']', ctx);
|
|
379
873
|
}
|
|
380
874
|
return data;
|
|
381
875
|
},
|
|
382
876
|
any: (v) => v,
|
|
877
|
+
never: (v, path, ctx) => {
|
|
878
|
+
if (typeof ctx.from === 'function') {
|
|
879
|
+
fromCustom(ctx, path, v, 'never');
|
|
880
|
+
}
|
|
881
|
+
report(ctx, path, 'Type<never>', v);
|
|
882
|
+
return v;
|
|
883
|
+
},
|
|
884
|
+
symbol: (v, path, ctx) => {
|
|
885
|
+
if (typeof v === 'symbol') {
|
|
886
|
+
return v;
|
|
887
|
+
}
|
|
888
|
+
if (typeof ctx.from === 'function') {
|
|
889
|
+
const converted = fromCustom(ctx, path, v, 'symbol');
|
|
890
|
+
if (typeof converted === 'symbol') {
|
|
891
|
+
return converted;
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
report(ctx, path, 'Type<symbol>', v);
|
|
895
|
+
return v;
|
|
896
|
+
},
|
|
897
|
+
instanceOf: (v, path, ctx, typeName) => {
|
|
898
|
+
const ctor = globalThis[typeName];
|
|
899
|
+
if (ctor && v instanceof ctor) {
|
|
900
|
+
return v;
|
|
901
|
+
}
|
|
902
|
+
if (typeof ctx.from === 'function') {
|
|
903
|
+
const converted = fromCustom(ctx, path, v, 'instance');
|
|
904
|
+
if (ctor && converted instanceof ctor) {
|
|
905
|
+
return converted;
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
report(ctx, path, `Type<${typeName}>`, v);
|
|
909
|
+
return v;
|
|
910
|
+
},
|
|
383
911
|
requires: (v, path, ctx, reqs, message) => {
|
|
384
912
|
if (v === undefined || v === null) {
|
|
385
913
|
return v;
|
|
@@ -393,64 +921,151 @@ export const validators = {
|
|
|
393
921
|
return v;
|
|
394
922
|
},
|
|
395
923
|
record: (v, path, ctx, childValidator) => {
|
|
396
|
-
if (!
|
|
397
|
-
|
|
398
|
-
|
|
924
|
+
if (!isPlainObject(v)) {
|
|
925
|
+
if (typeof ctx.from === 'function') {
|
|
926
|
+
const converted = fromCustom(ctx, path, v, 'Object');
|
|
927
|
+
if (isPlainObject(converted)) {
|
|
928
|
+
v = converted;
|
|
929
|
+
}
|
|
930
|
+
else {
|
|
931
|
+
report(ctx, path, 'Type<Object>', v);
|
|
932
|
+
return v;
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
else {
|
|
936
|
+
report(ctx, path, 'Type<Object>', v);
|
|
937
|
+
return v;
|
|
938
|
+
}
|
|
399
939
|
}
|
|
400
|
-
const
|
|
940
|
+
const mutate = shouldMutate(ctx);
|
|
941
|
+
const data = mutate ? v : {};
|
|
401
942
|
for (const key of Object.keys(v)) {
|
|
402
|
-
|
|
403
|
-
if (ctx.mode === 'strip') {
|
|
404
|
-
data[key] = val;
|
|
405
|
-
}
|
|
943
|
+
data[key] = childValidator(v[key], path + '.' + key, ctx);
|
|
406
944
|
}
|
|
407
945
|
return data;
|
|
408
946
|
},
|
|
409
947
|
set: (v, path, ctx, childValidator, message) => {
|
|
410
948
|
if (!(v instanceof Set)) {
|
|
411
|
-
if (ctx
|
|
949
|
+
if (wantsJsonRevive(ctx) && Array.isArray(v)) {
|
|
412
950
|
v = new Set(v);
|
|
413
951
|
}
|
|
414
|
-
else if (ctx
|
|
952
|
+
else if (wantsQuery(ctx) && v !== undefined && v !== null) {
|
|
415
953
|
v = new Set([v]);
|
|
416
954
|
}
|
|
955
|
+
else if (typeof ctx.from === 'function') {
|
|
956
|
+
const converted = fromCustom(ctx, path, v, 'Set');
|
|
957
|
+
if (converted instanceof Set) {
|
|
958
|
+
v = converted;
|
|
959
|
+
}
|
|
960
|
+
else {
|
|
961
|
+
report(ctx, path, 'Type<Set>', v, message);
|
|
962
|
+
return v;
|
|
963
|
+
}
|
|
964
|
+
}
|
|
417
965
|
else {
|
|
418
966
|
report(ctx, path, 'Type<Set>', v, message);
|
|
419
967
|
return v;
|
|
420
968
|
}
|
|
421
969
|
}
|
|
422
|
-
const
|
|
970
|
+
const mutate = shouldMutate(ctx);
|
|
971
|
+
const source = [...v];
|
|
972
|
+
if (mutate) {
|
|
973
|
+
v.clear();
|
|
974
|
+
}
|
|
975
|
+
const data = mutate ? v : new Set();
|
|
423
976
|
let index = 0;
|
|
424
|
-
for (const item of
|
|
425
|
-
|
|
426
|
-
if (ctx.mode === 'strip') {
|
|
427
|
-
data.add(val);
|
|
428
|
-
}
|
|
977
|
+
for (const item of source) {
|
|
978
|
+
data.add(childValidator(item, `${path}[${index}]`, ctx));
|
|
429
979
|
index++;
|
|
430
980
|
}
|
|
431
981
|
return data;
|
|
432
982
|
},
|
|
433
983
|
map: (v, path, ctx, keyValidator, valueValidator, message) => {
|
|
434
984
|
if (!(v instanceof Map)) {
|
|
435
|
-
if (ctx
|
|
985
|
+
if (wantsJsonRevive(ctx) && isPlainObject(v)) {
|
|
436
986
|
v = new Map(Object.entries(v));
|
|
437
987
|
}
|
|
988
|
+
else if (typeof ctx.from === 'function') {
|
|
989
|
+
const converted = fromCustom(ctx, path, v, 'Map');
|
|
990
|
+
if (converted instanceof Map) {
|
|
991
|
+
v = converted;
|
|
992
|
+
}
|
|
993
|
+
else {
|
|
994
|
+
report(ctx, path, 'Type<Map>', v, message);
|
|
995
|
+
return v;
|
|
996
|
+
}
|
|
997
|
+
}
|
|
438
998
|
else {
|
|
439
999
|
report(ctx, path, 'Type<Map>', v, message);
|
|
440
1000
|
return v;
|
|
441
1001
|
}
|
|
442
1002
|
}
|
|
443
|
-
const
|
|
444
|
-
|
|
1003
|
+
const mutate = shouldMutate(ctx);
|
|
1004
|
+
const source = [...v.entries()];
|
|
1005
|
+
if (mutate) {
|
|
1006
|
+
v.clear();
|
|
1007
|
+
}
|
|
1008
|
+
const data = mutate ? v : new Map();
|
|
1009
|
+
for (const [key, val] of source) {
|
|
445
1010
|
const validatedKey = keyValidator(key, `${path}.key(${JSON.stringify(key)})`, ctx);
|
|
446
1011
|
const validatedVal = valueValidator(val, `${path}[${JSON.stringify(key)}]`, ctx);
|
|
447
|
-
|
|
448
|
-
data.set(validatedKey, validatedVal);
|
|
449
|
-
}
|
|
1012
|
+
data.set(validatedKey, validatedVal);
|
|
450
1013
|
}
|
|
451
1014
|
return data;
|
|
452
1015
|
}
|
|
453
1016
|
};
|
|
1017
|
+
function tokenizePath(path) {
|
|
1018
|
+
const cleanPath = path.startsWith('.') ? path.substring(1) : path;
|
|
1019
|
+
if (!cleanPath) {
|
|
1020
|
+
return [];
|
|
1021
|
+
}
|
|
1022
|
+
const segments = [];
|
|
1023
|
+
let buf = '';
|
|
1024
|
+
for (let i = 0; i < cleanPath.length; i++) {
|
|
1025
|
+
const ch = cleanPath[i];
|
|
1026
|
+
if (ch === '.') {
|
|
1027
|
+
if (buf) {
|
|
1028
|
+
segments.push(buf);
|
|
1029
|
+
buf = '';
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
else if (ch === '[') {
|
|
1033
|
+
if (buf) {
|
|
1034
|
+
segments.push(buf);
|
|
1035
|
+
buf = '';
|
|
1036
|
+
}
|
|
1037
|
+
const end = cleanPath.indexOf(']', i);
|
|
1038
|
+
if (end === -1) {
|
|
1039
|
+
buf += cleanPath.substring(i);
|
|
1040
|
+
break;
|
|
1041
|
+
}
|
|
1042
|
+
segments.push(cleanPath.substring(i, end + 1));
|
|
1043
|
+
i = end;
|
|
1044
|
+
}
|
|
1045
|
+
else {
|
|
1046
|
+
buf += ch;
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1049
|
+
if (buf) {
|
|
1050
|
+
segments.push(buf);
|
|
1051
|
+
}
|
|
1052
|
+
return segments;
|
|
1053
|
+
}
|
|
1054
|
+
function joinPathSegments(segments) {
|
|
1055
|
+
let result = '';
|
|
1056
|
+
for (const seg of segments) {
|
|
1057
|
+
if (seg.startsWith('[')) {
|
|
1058
|
+
result += seg;
|
|
1059
|
+
}
|
|
1060
|
+
else if (result) {
|
|
1061
|
+
result += '.' + seg;
|
|
1062
|
+
}
|
|
1063
|
+
else {
|
|
1064
|
+
result = seg;
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
return result;
|
|
1068
|
+
}
|
|
454
1069
|
function resolvePath(currentPath, targetPath) {
|
|
455
1070
|
if (!targetPath.startsWith('.')) {
|
|
456
1071
|
return targetPath;
|
|
@@ -458,39 +1073,33 @@ function resolvePath(currentPath, targetPath) {
|
|
|
458
1073
|
const dotsMatch = targetPath.match(/^\.+/);
|
|
459
1074
|
const dots = dotsMatch ? dotsMatch[0].length : 0;
|
|
460
1075
|
const targetClean = targetPath.substring(dots);
|
|
461
|
-
const
|
|
462
|
-
const
|
|
463
|
-
const baseParts = currentParts.slice(0, currentParts.length - dots);
|
|
1076
|
+
const currentParts = tokenizePath(currentPath);
|
|
1077
|
+
const baseParts = currentParts.slice(0, Math.max(0, currentParts.length - dots));
|
|
464
1078
|
if (targetClean) {
|
|
465
|
-
baseParts.push(targetClean);
|
|
1079
|
+
baseParts.push(...tokenizePath(targetClean));
|
|
466
1080
|
}
|
|
467
|
-
return baseParts
|
|
1081
|
+
return joinPathSegments(baseParts);
|
|
468
1082
|
}
|
|
469
1083
|
function getValueAtPath(obj, path) {
|
|
470
1084
|
if (!obj || typeof obj !== 'object') {
|
|
471
1085
|
return undefined;
|
|
472
1086
|
}
|
|
473
|
-
const
|
|
474
|
-
if (
|
|
1087
|
+
const parts = tokenizePath(path);
|
|
1088
|
+
if (parts.length === 0) {
|
|
475
1089
|
return obj;
|
|
476
1090
|
}
|
|
477
|
-
const parts = cleanPath.split('.');
|
|
478
1091
|
let current = obj;
|
|
479
1092
|
for (const part of parts) {
|
|
480
1093
|
if (current === null || current === undefined || typeof current !== 'object') {
|
|
481
1094
|
return undefined;
|
|
482
1095
|
}
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
current = current[baseKey];
|
|
486
|
-
for (let i = 1; i < matches.length; i++) {
|
|
487
|
-
if (current === null || current === undefined || typeof current !== 'object') {
|
|
488
|
-
return undefined;
|
|
489
|
-
}
|
|
490
|
-
const idxStr = matches[i].replace(']', '');
|
|
491
|
-
const idx = parseInt(idxStr, 10);
|
|
1096
|
+
if (part.startsWith('[') && part.endsWith(']')) {
|
|
1097
|
+
const idx = parseInt(part.slice(1, -1), 10);
|
|
492
1098
|
current = current[idx];
|
|
493
1099
|
}
|
|
1100
|
+
else {
|
|
1101
|
+
current = current[part];
|
|
1102
|
+
}
|
|
494
1103
|
}
|
|
495
1104
|
return current;
|
|
496
1105
|
}
|
|
@@ -536,18 +1145,20 @@ export class MetadataStoreClass {
|
|
|
536
1145
|
is(validator, value, options) {
|
|
537
1146
|
const opt = options;
|
|
538
1147
|
const mode = typeof opt === 'string' ? opt : (opt?.mode || 'strict');
|
|
539
|
-
|
|
1148
|
+
// Type predicates require the value already match T — never coerce.
|
|
540
1149
|
const wrapArrays = typeof opt === 'object' ? opt?.wrapArrays : undefined;
|
|
541
|
-
const
|
|
1150
|
+
const mutate = typeof opt === 'object' ? opt?.mutate === true : false;
|
|
1151
|
+
const ctx = { success: true, errors: [], mode, wrapArrays, mutate, root: value };
|
|
542
1152
|
validator(value, '', ctx);
|
|
543
1153
|
return ctx.success;
|
|
544
1154
|
}
|
|
545
1155
|
assert(validator, value, options) {
|
|
546
1156
|
const opt = options;
|
|
547
1157
|
const mode = typeof opt === 'string' ? opt : (opt?.mode || 'strict');
|
|
548
|
-
const
|
|
1158
|
+
const from = typeof opt === 'object' ? opt?.from : undefined;
|
|
549
1159
|
const wrapArrays = typeof opt === 'object' ? opt?.wrapArrays : undefined;
|
|
550
|
-
const
|
|
1160
|
+
const mutate = typeof opt === 'object' ? opt?.mutate === true : false;
|
|
1161
|
+
const ctx = { success: true, errors: [], mode, from, wrapArrays, mutate, root: value };
|
|
551
1162
|
const res = validator(value, '', ctx);
|
|
552
1163
|
if (!ctx.success) {
|
|
553
1164
|
if (typeof opt === 'object' && opt?.errorFactory) {
|
|
@@ -557,26 +1168,48 @@ export class MetadataStoreClass {
|
|
|
557
1168
|
}
|
|
558
1169
|
return res;
|
|
559
1170
|
}
|
|
1171
|
+
assertGuard(validator, value, options) {
|
|
1172
|
+
const opt = options;
|
|
1173
|
+
const mode = typeof opt === 'string' ? opt : (opt?.mode || 'strict');
|
|
1174
|
+
// Assertion predicates require the value already match T — never coerce.
|
|
1175
|
+
const wrapArrays = typeof opt === 'object' ? opt?.wrapArrays : undefined;
|
|
1176
|
+
const mutate = typeof opt === 'object' ? opt?.mutate === true : false;
|
|
1177
|
+
const ctx = { success: true, errors: [], mode, wrapArrays, mutate, root: value };
|
|
1178
|
+
validator(value, '', ctx);
|
|
1179
|
+
if (!ctx.success) {
|
|
1180
|
+
if (typeof opt === 'object' && opt?.errorFactory) {
|
|
1181
|
+
throw opt.errorFactory(ctx.errors);
|
|
1182
|
+
}
|
|
1183
|
+
throw new Error('Validation Error: ' + ctx.errors.map(e => e.path ? `${e.path}: ${e.error}` : e.error).join(', '));
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
560
1186
|
validate(validator, value, options) {
|
|
561
1187
|
const opt = options;
|
|
562
1188
|
const mode = typeof opt === 'string' ? opt : (opt?.mode || 'strict');
|
|
563
|
-
const
|
|
1189
|
+
const from = typeof opt === 'object' ? opt?.from : undefined;
|
|
564
1190
|
const wrapArrays = typeof opt === 'object' ? opt?.wrapArrays : undefined;
|
|
565
|
-
const
|
|
1191
|
+
const mutate = typeof opt === 'object' ? opt?.mutate === true : false;
|
|
1192
|
+
const ctx = { success: true, errors: [], mode, from, wrapArrays, mutate, root: value };
|
|
566
1193
|
const res = validator(value, '', ctx);
|
|
567
1194
|
return { success: ctx.success, errors: ctx.errors, data: res };
|
|
568
1195
|
}
|
|
569
1196
|
}
|
|
570
1197
|
export function groupErrorsByPath(errors) {
|
|
571
1198
|
const grouped = {};
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
grouped[err.path]
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
grouped[err.path].errors.
|
|
1199
|
+
const visit = (list) => {
|
|
1200
|
+
for (const err of list) {
|
|
1201
|
+
if (!grouped[err.path]) {
|
|
1202
|
+
grouped[err.path] = { value: err.value, errors: [] };
|
|
1203
|
+
}
|
|
1204
|
+
if (!grouped[err.path].errors.includes(err.error)) {
|
|
1205
|
+
grouped[err.path].errors.push(err.error);
|
|
1206
|
+
}
|
|
1207
|
+
if (err.issues?.length) {
|
|
1208
|
+
visit(err.issues);
|
|
1209
|
+
}
|
|
578
1210
|
}
|
|
579
|
-
}
|
|
1211
|
+
};
|
|
1212
|
+
visit(errors);
|
|
580
1213
|
return grouped;
|
|
581
1214
|
}
|
|
582
1215
|
export const MetadataStore = new MetadataStoreClass();
|
|
@@ -608,6 +1241,67 @@ export function compileSchema(schema) {
|
|
|
608
1241
|
compiledDefs.set(refPath, proxy);
|
|
609
1242
|
return proxy;
|
|
610
1243
|
}
|
|
1244
|
+
if (subSchema['x-typescript-type'] === 'Date') {
|
|
1245
|
+
return (v, path, ctx) => validators.date(v, path, ctx);
|
|
1246
|
+
}
|
|
1247
|
+
if (subSchema['x-typescript-type'] === 'RegExp') {
|
|
1248
|
+
return (v, path, ctx) => validators.regexp(v, path, ctx);
|
|
1249
|
+
}
|
|
1250
|
+
if (subSchema['x-typescript-type'] === 'bigint') {
|
|
1251
|
+
return (v, path, ctx) => validators.bigint(v, path, ctx);
|
|
1252
|
+
}
|
|
1253
|
+
if (subSchema['x-typescript-type'] === 'undefined') {
|
|
1254
|
+
return (v, path, ctx) => validators.undefined(v, path, ctx);
|
|
1255
|
+
}
|
|
1256
|
+
if (subSchema['x-typescript-type'] === 'Set') {
|
|
1257
|
+
const child = build(subSchema.items || {});
|
|
1258
|
+
return (v, path, ctx) => validators.set(v, path, ctx, child);
|
|
1259
|
+
}
|
|
1260
|
+
if (subSchema['x-typescript-type'] === 'Map') {
|
|
1261
|
+
const keyCheck = build(subSchema.key || { type: 'string' });
|
|
1262
|
+
const valueCheck = build(subSchema.value || {});
|
|
1263
|
+
return (v, path, ctx) => validators.map(v, path, ctx, keyCheck, valueCheck);
|
|
1264
|
+
}
|
|
1265
|
+
if (subSchema['x-typescript-type'] === 'Promise') {
|
|
1266
|
+
return (v, path, ctx) => {
|
|
1267
|
+
if (!(v instanceof Promise)) {
|
|
1268
|
+
report(ctx, path, 'Type<Promise>', v);
|
|
1269
|
+
}
|
|
1270
|
+
return v;
|
|
1271
|
+
};
|
|
1272
|
+
}
|
|
1273
|
+
if (typeof subSchema['x-typescript-type'] === 'string' &&
|
|
1274
|
+
['Uint8Array', 'Uint16Array', 'Uint32Array', 'Int8Array', 'Int16Array', 'Int32Array', 'Float32Array', 'Float64Array', 'ArrayBuffer', 'SharedArrayBuffer', 'DataView', 'Buffer'].includes(subSchema['x-typescript-type'])) {
|
|
1275
|
+
const typeName = subSchema['x-typescript-type'];
|
|
1276
|
+
return (v, path, ctx) => {
|
|
1277
|
+
const ctor = globalThis[typeName];
|
|
1278
|
+
if (!ctor || !(v instanceof ctor)) {
|
|
1279
|
+
report(ctx, path, `Type<${typeName}>`, v);
|
|
1280
|
+
}
|
|
1281
|
+
return v;
|
|
1282
|
+
};
|
|
1283
|
+
}
|
|
1284
|
+
if (subSchema.allOf) {
|
|
1285
|
+
const checks = subSchema.allOf.map((s) => build(s));
|
|
1286
|
+
return (v, path, ctx) => {
|
|
1287
|
+
const prevMode = ctx.mode;
|
|
1288
|
+
if (ctx.mode !== 'strip') {
|
|
1289
|
+
ctx.mode = 'relaxed';
|
|
1290
|
+
}
|
|
1291
|
+
let data = validators.objectShell(v, ctx);
|
|
1292
|
+
for (const check of checks) {
|
|
1293
|
+
const val = check(v, path, ctx);
|
|
1294
|
+
if (isPlainObject(val) && isPlainObject(data)) {
|
|
1295
|
+
Object.assign(data, val);
|
|
1296
|
+
}
|
|
1297
|
+
else {
|
|
1298
|
+
data = val;
|
|
1299
|
+
}
|
|
1300
|
+
}
|
|
1301
|
+
ctx.mode = prevMode;
|
|
1302
|
+
return data;
|
|
1303
|
+
};
|
|
1304
|
+
}
|
|
611
1305
|
if (subSchema.type === 'string') {
|
|
612
1306
|
const minLength = subSchema.minLength;
|
|
613
1307
|
const maxLength = subSchema.maxLength;
|
|
@@ -629,7 +1323,7 @@ export function compileSchema(schema) {
|
|
|
629
1323
|
validators.pattern(v, path, ctx, pattern, patternStr);
|
|
630
1324
|
}
|
|
631
1325
|
if (format !== undefined) {
|
|
632
|
-
validators.format(v, path, ctx, format);
|
|
1326
|
+
v = validators.format(v, path, ctx, format);
|
|
633
1327
|
}
|
|
634
1328
|
return v;
|
|
635
1329
|
};
|
|
@@ -638,6 +1332,8 @@ export function compileSchema(schema) {
|
|
|
638
1332
|
const isInt = subSchema.type === 'integer';
|
|
639
1333
|
const minimum = subSchema.minimum;
|
|
640
1334
|
const maximum = subSchema.maximum;
|
|
1335
|
+
const exclusiveMinimum = subSchema.exclusiveMinimum;
|
|
1336
|
+
const exclusiveMaximum = subSchema.exclusiveMaximum;
|
|
641
1337
|
const multipleOf = subSchema.multipleOf;
|
|
642
1338
|
return (v, path, ctx) => {
|
|
643
1339
|
v = validators.number(v, path, ctx);
|
|
@@ -653,6 +1349,12 @@ export function compileSchema(schema) {
|
|
|
653
1349
|
if (maximum !== undefined) {
|
|
654
1350
|
validators.maximum(v, path, ctx, maximum);
|
|
655
1351
|
}
|
|
1352
|
+
if (exclusiveMinimum !== undefined) {
|
|
1353
|
+
validators.exclusiveMinimum(v, path, ctx, exclusiveMinimum);
|
|
1354
|
+
}
|
|
1355
|
+
if (exclusiveMaximum !== undefined) {
|
|
1356
|
+
validators.exclusiveMaximum(v, path, ctx, exclusiveMaximum);
|
|
1357
|
+
}
|
|
656
1358
|
if (multipleOf !== undefined) {
|
|
657
1359
|
validators.multipleOf(v, path, ctx, multipleOf);
|
|
658
1360
|
}
|
|
@@ -704,31 +1406,25 @@ export function compileSchema(schema) {
|
|
|
704
1406
|
const check = build(s);
|
|
705
1407
|
return [key, isOptional, check];
|
|
706
1408
|
});
|
|
707
|
-
const
|
|
1409
|
+
const knownKeys = Object.keys(subSchema.properties || {});
|
|
1410
|
+
const additional = 'additionalProperties' in subSchema
|
|
1411
|
+
? subSchema.additionalProperties
|
|
1412
|
+
: false;
|
|
1413
|
+
const strictKeys = additional === false ? knownKeys : undefined;
|
|
1414
|
+
const additionalCheck = additional && typeof additional === 'object' ? build(additional) : undefined;
|
|
708
1415
|
return (v, path, ctx) => {
|
|
709
|
-
|
|
1416
|
+
const obj = validators.object(v, path, ctx, strictKeys, 'Object');
|
|
1417
|
+
if (obj === false) {
|
|
710
1418
|
return v;
|
|
711
1419
|
}
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
else {
|
|
720
|
-
for (let i = 0; i < keys.length; i++) {
|
|
721
|
-
if (!allowedKeys.includes(keys[i])) {
|
|
722
|
-
hasAdditional = true;
|
|
723
|
-
break;
|
|
724
|
-
}
|
|
725
|
-
}
|
|
726
|
-
}
|
|
727
|
-
if (hasAdditional) {
|
|
728
|
-
data = {};
|
|
729
|
-
}
|
|
1420
|
+
const data = validators.objectShell(obj, ctx);
|
|
1421
|
+
validators.props(obj, data, path, ctx, propVals);
|
|
1422
|
+
if (additionalCheck) {
|
|
1423
|
+
validators.additionalProps(obj, data, path, ctx, knownKeys, additionalCheck);
|
|
1424
|
+
}
|
|
1425
|
+
else if (additional === false) {
|
|
1426
|
+
validators.stripExtras(data, ctx, knownKeys);
|
|
730
1427
|
}
|
|
731
|
-
validators.props(v, data, path, ctx, propVals);
|
|
732
1428
|
return data;
|
|
733
1429
|
};
|
|
734
1430
|
}
|
|
@@ -746,24 +1442,31 @@ export function compileSchema(schema) {
|
|
|
746
1442
|
return build(schema);
|
|
747
1443
|
}
|
|
748
1444
|
export function toZodIssues(errors) {
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
.
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
1445
|
+
const issues = [];
|
|
1446
|
+
const visit = (list) => {
|
|
1447
|
+
for (const err of list) {
|
|
1448
|
+
const zodPath = err.path
|
|
1449
|
+
.split(/\.|\[|\]/)
|
|
1450
|
+
.filter(Boolean)
|
|
1451
|
+
.map((segment) => {
|
|
1452
|
+
if (isNaN(Number(segment))) {
|
|
1453
|
+
return segment;
|
|
1454
|
+
}
|
|
1455
|
+
return Number(segment);
|
|
1456
|
+
});
|
|
1457
|
+
issues.push({
|
|
1458
|
+
code: 'custom',
|
|
1459
|
+
path: zodPath,
|
|
1460
|
+
message: err.error,
|
|
1461
|
+
received: err.value
|
|
1462
|
+
});
|
|
1463
|
+
if (err.issues?.length) {
|
|
1464
|
+
visit(err.issues);
|
|
1465
|
+
}
|
|
1466
|
+
}
|
|
1467
|
+
};
|
|
1468
|
+
visit(errors);
|
|
1469
|
+
return issues;
|
|
767
1470
|
}
|
|
768
1471
|
export class ZodLikeError extends Error {
|
|
769
1472
|
issues;
|