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