convex-helpers 0.1.69 → 0.1.70
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -8
- package/package.json +1 -1
- package/validators.d.ts +22 -1
- package/validators.d.ts.map +1 -1
- package/validators.js +185 -1
- package/validators.ts +198 -1
package/README.md
CHANGED
|
@@ -600,20 +600,23 @@ await ctx.runMutation(internal.users.update, {
|
|
|
600
600
|
When using validators for defining database schema or function arguments,
|
|
601
601
|
these validators help:
|
|
602
602
|
|
|
603
|
-
1. Add a
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
an extension of [this article](https://stack.convex.dev/types-cookbook).
|
|
607
|
-
2. Add utilties for `partial`, `pick` and `omit` to match the TypeScript type
|
|
608
|
-
utilities.
|
|
609
|
-
3. Add shorthand for a union of `literals`, a `nullable` field, a `deprecated`
|
|
610
|
-
field, and `brandedString`. To learn more about branded strings see
|
|
603
|
+
1. Add shorthand for a union of `literals`, a `nullable` field, a `deprecated`
|
|
604
|
+
field, a `partial` object, and `brandedString`.
|
|
605
|
+
To learn more about branded strings see
|
|
611
606
|
[this article](https://stack.convex.dev/using-branded-types-in-validators).
|
|
607
|
+
2. A `validate(validator, data)` function validates a value against a validator.
|
|
608
|
+
Warning: this does not validate that the value of v.id is an ID for the given table.
|
|
609
|
+
3. Add utilties for `partial`, `pick` and `omit` to match the TypeScript type
|
|
610
|
+
utilities.
|
|
612
611
|
4. Add a `doc(schema, "tableName")` helper to validate a document with system
|
|
613
612
|
fields included.
|
|
614
613
|
5. Add a `typedV(schema)` helper that is a `v` replacement that also has:
|
|
615
614
|
- `doc("tableName")` that works like `doc` above.
|
|
616
615
|
- `id("tableName")` that is typed to tables in your schema.
|
|
616
|
+
6. Add a `Table` utility that defines a table and keeps references to the fields
|
|
617
|
+
to avoid re-defining validators. To learn more about sharing validators, read
|
|
618
|
+
[this article](https://stack.convex.dev/argument-validation-without-repetition),
|
|
619
|
+
an extension of [this article](https://stack.convex.dev/types-cookbook).
|
|
617
620
|
|
|
618
621
|
Example:
|
|
619
622
|
|
|
@@ -666,6 +669,11 @@ const balanceAndEmail = pick(vv.doc("accounts").fields, ["balance", "email"]);
|
|
|
666
669
|
|
|
667
670
|
// A validator for all the fields except balance.
|
|
668
671
|
const accountWithoutBalance = omit(vv.doc("accounts").fields, ["balance"]);
|
|
672
|
+
|
|
673
|
+
// Validate against a validator. Can optionally throw on error.
|
|
674
|
+
validate(balanceAndEmail, { balance: 123n, email: "test@example.com" });
|
|
675
|
+
// Warning: this only validates that `accountId` is a string.
|
|
676
|
+
validate(vv.id("accounts"), accountId);
|
|
669
677
|
```
|
|
670
678
|
|
|
671
679
|
## Filter
|
package/package.json
CHANGED
package/validators.d.ts
CHANGED
|
@@ -55,7 +55,7 @@ export declare const null_: import("convex/values").VNull<null, "required">;
|
|
|
55
55
|
/** Re-export values from v without having to do v.* */
|
|
56
56
|
export declare const id: <TableName extends string>(tableName: TableName) => import("convex/values").VId<import("convex/values").GenericId<TableName>, "required">, object: <T_2 extends PropertyValidators>(fields: T_2) => VObject<import("convex/server").Expand<{ [Property in { [Property_1 in keyof T_2]: T_2[Property_1]["isOptional"] extends "optional" ? Property_1 : never; }[keyof T_2]]?: Exclude<import("convex/values").Infer<T_2[Property]>, undefined> | undefined; } & { [Property_1 in Exclude<keyof T_2, { [Property in keyof T_2]: T_2[Property]["isOptional"] extends "optional" ? Property : never; }[keyof T_2]>]: import("convex/values").Infer<T_2[Property_1]>; }>, T_2, "required", { [Property_2 in keyof T_2]: Property_2 | `${Property_2 & string}.${T_2[Property_2]["fieldPaths"]}`; }[keyof T_2] & string>, array: <T_1 extends Validator<any, "required", any>>(element: T_1) => import("convex/values").VArray<T_1["type"][], T_1, "required">, bytes: () => import("convex/values").VBytes<ArrayBuffer, "required">, literal: <T extends string | number | bigint | boolean>(literal: T) => import("convex/values").VLiteral<T, "required">, optional: <T_4 extends GenericValidator>(value: T_4) => VOptional<T_4>, union: <T_3 extends Validator<any, "required", any>[]>(...members: T_3) => VUnion<T_3[number]["type"], T_3, "required", T_3[number]["fieldPaths"]>;
|
|
57
57
|
/** ArrayBuffer validator. */
|
|
58
|
-
export declare const arrayBuffer:
|
|
58
|
+
export declare const arrayBuffer: import("convex/values").VBytes<ArrayBuffer, "required">;
|
|
59
59
|
/**
|
|
60
60
|
* Utility to get the validators for fields associated with a table.
|
|
61
61
|
* e.g. for systemFields("users") it would return:
|
|
@@ -215,4 +215,25 @@ export declare const pretend: <T extends GenericValidator>(_typeToImmitate: T) =
|
|
|
215
215
|
* });
|
|
216
216
|
*/
|
|
217
217
|
export declare const pretendRequired: <T extends Validator<any, "required", any>>(optionalType: T) => T;
|
|
218
|
+
export declare class ValidationError extends Error {
|
|
219
|
+
expected: string;
|
|
220
|
+
got: string;
|
|
221
|
+
path?: string | undefined;
|
|
222
|
+
constructor(expected: string, got: string, path?: string | undefined);
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Validate a value against a validator.
|
|
226
|
+
*
|
|
227
|
+
* WARNING: This does not validate that v.id is an ID for the given table.
|
|
228
|
+
* It only validates that the ID is a string. Function `args`, `returns` and
|
|
229
|
+
* schema definitions will validate that the ID is an ID for the given table.
|
|
230
|
+
*
|
|
231
|
+
* @param validator The validator to validate against.
|
|
232
|
+
* @param value The value to validate.
|
|
233
|
+
* @returns Whether the value is valid against the validator.
|
|
234
|
+
*/
|
|
235
|
+
export declare function validate<T extends Validator<any, any, any>>(validator: T, value: unknown, opts?: {
|
|
236
|
+
throw?: boolean;
|
|
237
|
+
pathPrefix?: string;
|
|
238
|
+
}): value is T["type"];
|
|
218
239
|
//# sourceMappingURL=validators.d.ts.map
|
package/validators.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validators.d.ts","sourceRoot":"","sources":["validators.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,gBAAgB,EAChB,UAAU,EACV,kBAAkB,EAClB,OAAO,EACP,SAAS,EACT,OAAO,EACP,MAAM,EACN,SAAS,EAEV,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AACpC,OAAO,EACL,6BAA6B,EAC7B,gBAAgB,EAChB,qBAAqB,EACtB,MAAM,eAAe,CAAC;AAEvB;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,QAAQ,GACnB,CAAC,SAAS,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,EAC5C,CAAC,SAAS,CAAC,EAAE,WAEJ,CAAC,KACT,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,EAAE,GAAG,CAMvB,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,QAAQ,GAAI,CAAC,SAAS,SAAS,CAAC,GAAG,EAAE,UAAU,EAAE,GAAG,CAAC,KAAK,CAAC,iNAClD,CAAC;AAEvB;;;;;;;;GAQG;AACH,eAAO,MAAM,OAAO,GAAI,CAAC,SAAS,kBAAkB,OAAO,CAAC,KAMrD,GACF,CAAC,IAAI,MAAM,CAAC,GAAG,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAElC,CAAC;AAIF,wBAAwB;AACxB,eAAO,MAAM,MAAM,6BAAa,CAAC;AACjC,mEAAmE;AACnE,eAAO,MAAM,MAAM,sDAAc,CAAC;AAClC,mEAAmE;AACnE,eAAO,MAAM,OAAO,sDAAc,CAAC;AACnC,+DAA+D;AAC/D,eAAO,MAAM,OAAO,uDAAc,CAAC;AACnC,yDAAyD;AACzD,eAAO,MAAM,MAAM,oDAAY,CAAC;AAChC,yDAAyD;AACzD,eAAO,MAAM,KAAK,oDAAY,CAAC;AAC/B,uBAAuB;AACvB,eAAO,MAAM,GAAG,uDAAU,CAAC;AAC3B,sEAAsE;AACtE,eAAO,MAAM,KAAK,iDAAW,CAAC;AAC9B,uDAAuD;AACvD,eAAO,MAAQ,EAAE,6IAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"validators.d.ts","sourceRoot":"","sources":["validators.ts"],"names":[],"mappings":"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"}
|
package/validators.js
CHANGED
|
@@ -64,7 +64,7 @@ export const null_ = v.null();
|
|
|
64
64
|
/** Re-export values from v without having to do v.* */
|
|
65
65
|
export const { id, object, array, bytes, literal, optional, union } = v;
|
|
66
66
|
/** ArrayBuffer validator. */
|
|
67
|
-
export const arrayBuffer = bytes;
|
|
67
|
+
export const arrayBuffer = bytes();
|
|
68
68
|
/**
|
|
69
69
|
* Utility to get the validators for fields associated with a table.
|
|
70
70
|
* e.g. for systemFields("users") it would return:
|
|
@@ -223,3 +223,187 @@ export const pretend = (_typeToImmitate) => v.optional(v.any());
|
|
|
223
223
|
* });
|
|
224
224
|
*/
|
|
225
225
|
export const pretendRequired = (optionalType) => v.optional(optionalType);
|
|
226
|
+
export class ValidationError extends Error {
|
|
227
|
+
expected;
|
|
228
|
+
got;
|
|
229
|
+
path;
|
|
230
|
+
constructor(expected, got, path) {
|
|
231
|
+
const message = `Validator error${path ? ` for ${path}` : ""}: Expected \`${expected}\`, got \`${got}\``;
|
|
232
|
+
super(message);
|
|
233
|
+
this.expected = expected;
|
|
234
|
+
this.got = got;
|
|
235
|
+
this.path = path;
|
|
236
|
+
this.name = "ValidationError";
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Validate a value against a validator.
|
|
241
|
+
*
|
|
242
|
+
* WARNING: This does not validate that v.id is an ID for the given table.
|
|
243
|
+
* It only validates that the ID is a string. Function `args`, `returns` and
|
|
244
|
+
* schema definitions will validate that the ID is an ID for the given table.
|
|
245
|
+
*
|
|
246
|
+
* @param validator The validator to validate against.
|
|
247
|
+
* @param value The value to validate.
|
|
248
|
+
* @returns Whether the value is valid against the validator.
|
|
249
|
+
*/
|
|
250
|
+
export function validate(validator, value, opts) {
|
|
251
|
+
let valid = true;
|
|
252
|
+
let expected = validator.kind;
|
|
253
|
+
if (value === undefined) {
|
|
254
|
+
if (validator.isOptional !== "optional") {
|
|
255
|
+
valid = false;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
else {
|
|
259
|
+
switch (validator.kind) {
|
|
260
|
+
case "null": {
|
|
261
|
+
if (value !== null) {
|
|
262
|
+
valid = false;
|
|
263
|
+
}
|
|
264
|
+
break;
|
|
265
|
+
}
|
|
266
|
+
case "float64": {
|
|
267
|
+
if (typeof value !== "number") {
|
|
268
|
+
expected = "number";
|
|
269
|
+
valid = false;
|
|
270
|
+
}
|
|
271
|
+
break;
|
|
272
|
+
}
|
|
273
|
+
case "int64": {
|
|
274
|
+
if (typeof value !== "bigint") {
|
|
275
|
+
expected = "bigint";
|
|
276
|
+
valid = false;
|
|
277
|
+
}
|
|
278
|
+
break;
|
|
279
|
+
}
|
|
280
|
+
case "boolean": {
|
|
281
|
+
if (typeof value !== "boolean") {
|
|
282
|
+
valid = false;
|
|
283
|
+
}
|
|
284
|
+
break;
|
|
285
|
+
}
|
|
286
|
+
case "string": {
|
|
287
|
+
if (typeof value !== "string") {
|
|
288
|
+
valid = false;
|
|
289
|
+
}
|
|
290
|
+
break;
|
|
291
|
+
}
|
|
292
|
+
case "bytes": {
|
|
293
|
+
if (!(value instanceof ArrayBuffer)) {
|
|
294
|
+
valid = false;
|
|
295
|
+
}
|
|
296
|
+
break;
|
|
297
|
+
}
|
|
298
|
+
case "any": {
|
|
299
|
+
break;
|
|
300
|
+
}
|
|
301
|
+
case "literal": {
|
|
302
|
+
if (value !== validator.value) {
|
|
303
|
+
valid = false;
|
|
304
|
+
}
|
|
305
|
+
break;
|
|
306
|
+
}
|
|
307
|
+
case "id": {
|
|
308
|
+
if (typeof value !== "string") {
|
|
309
|
+
valid = false;
|
|
310
|
+
}
|
|
311
|
+
break;
|
|
312
|
+
}
|
|
313
|
+
case "array": {
|
|
314
|
+
if (!Array.isArray(value)) {
|
|
315
|
+
valid = false;
|
|
316
|
+
break;
|
|
317
|
+
}
|
|
318
|
+
for (const [index, v] of value.entries()) {
|
|
319
|
+
const path = `${opts?.pathPrefix ?? ""}[${index}]`;
|
|
320
|
+
valid = validate(validator.element, v, { ...opts, pathPrefix: path });
|
|
321
|
+
if (!valid) {
|
|
322
|
+
expected = validator.element.kind;
|
|
323
|
+
break;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
break;
|
|
327
|
+
}
|
|
328
|
+
case "object": {
|
|
329
|
+
if (typeof value !== "object" || value === null) {
|
|
330
|
+
valid = false;
|
|
331
|
+
break;
|
|
332
|
+
}
|
|
333
|
+
const prototype = Object.getPrototypeOf(value);
|
|
334
|
+
const isSimple = prototype === null ||
|
|
335
|
+
prototype === Object.prototype ||
|
|
336
|
+
// Objects generated from other contexts (e.g. across Node.js `vm` modules) will not satisfy the previous
|
|
337
|
+
// conditions but are still simple objects.
|
|
338
|
+
prototype?.constructor?.name === "Object";
|
|
339
|
+
if (!isSimple) {
|
|
340
|
+
expected =
|
|
341
|
+
prototype?.constructor?.name ?? typeof prototype ?? "object";
|
|
342
|
+
valid = false;
|
|
343
|
+
break;
|
|
344
|
+
}
|
|
345
|
+
for (const [k, fieldValidator] of Object.entries(validator.fields)) {
|
|
346
|
+
valid = validate(fieldValidator, value[k], {
|
|
347
|
+
...opts,
|
|
348
|
+
pathPrefix: appendPath(opts, k),
|
|
349
|
+
});
|
|
350
|
+
if (!valid) {
|
|
351
|
+
break;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
for (const k of Object.keys(value)) {
|
|
355
|
+
if (validator.fields[k] === undefined) {
|
|
356
|
+
if (opts?.throw) {
|
|
357
|
+
throw new ValidationError("nothing", typeof value[k], appendPath(opts, k));
|
|
358
|
+
}
|
|
359
|
+
valid = false;
|
|
360
|
+
break;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
break;
|
|
364
|
+
}
|
|
365
|
+
case "union": {
|
|
366
|
+
valid = false;
|
|
367
|
+
for (const member of validator.members) {
|
|
368
|
+
if (validate(member, value, opts)) {
|
|
369
|
+
valid = true;
|
|
370
|
+
break;
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
break;
|
|
374
|
+
}
|
|
375
|
+
case "record": {
|
|
376
|
+
if (typeof value !== "object" || value === null) {
|
|
377
|
+
valid = false;
|
|
378
|
+
break;
|
|
379
|
+
}
|
|
380
|
+
for (const [k, fieldValue] of Object.entries(value)) {
|
|
381
|
+
valid = validate(validator.key, k, {
|
|
382
|
+
...opts,
|
|
383
|
+
pathPrefix: appendPath(opts, k),
|
|
384
|
+
});
|
|
385
|
+
if (!valid) {
|
|
386
|
+
expected = validator.key.kind;
|
|
387
|
+
break;
|
|
388
|
+
}
|
|
389
|
+
valid = validate(validator.value, fieldValue, {
|
|
390
|
+
...opts,
|
|
391
|
+
pathPrefix: appendPath(opts, k),
|
|
392
|
+
});
|
|
393
|
+
if (!valid) {
|
|
394
|
+
expected = validator.value.kind;
|
|
395
|
+
break;
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
break;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
if (!valid && opts?.throw) {
|
|
403
|
+
throw new ValidationError(expected, typeof value, opts?.pathPrefix);
|
|
404
|
+
}
|
|
405
|
+
return valid;
|
|
406
|
+
}
|
|
407
|
+
function appendPath(opts, path) {
|
|
408
|
+
return opts?.pathPrefix ? `${opts.pathPrefix}.${path}` : path;
|
|
409
|
+
}
|
package/validators.ts
CHANGED
|
@@ -96,7 +96,7 @@ export const null_ = v.null();
|
|
|
96
96
|
/** Re-export values from v without having to do v.* */
|
|
97
97
|
export const { id, object, array, bytes, literal, optional, union } = v;
|
|
98
98
|
/** ArrayBuffer validator. */
|
|
99
|
-
export const arrayBuffer = bytes;
|
|
99
|
+
export const arrayBuffer = bytes();
|
|
100
100
|
|
|
101
101
|
/**
|
|
102
102
|
* Utility to get the validators for fields associated with a table.
|
|
@@ -329,3 +329,200 @@ export const pretend = <T extends GenericValidator>(_typeToImmitate: T): T =>
|
|
|
329
329
|
export const pretendRequired = <T extends Validator<any, "required", any>>(
|
|
330
330
|
optionalType: T,
|
|
331
331
|
): T => v.optional(optionalType) as unknown as T;
|
|
332
|
+
|
|
333
|
+
export class ValidationError extends Error {
|
|
334
|
+
constructor(
|
|
335
|
+
public expected: string,
|
|
336
|
+
public got: string,
|
|
337
|
+
public path?: string,
|
|
338
|
+
) {
|
|
339
|
+
const message = `Validator error${path ? ` for ${path}` : ""}: Expected \`${expected}\`, got \`${got}\``;
|
|
340
|
+
super(message);
|
|
341
|
+
this.name = "ValidationError";
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Validate a value against a validator.
|
|
347
|
+
*
|
|
348
|
+
* WARNING: This does not validate that v.id is an ID for the given table.
|
|
349
|
+
* It only validates that the ID is a string. Function `args`, `returns` and
|
|
350
|
+
* schema definitions will validate that the ID is an ID for the given table.
|
|
351
|
+
*
|
|
352
|
+
* @param validator The validator to validate against.
|
|
353
|
+
* @param value The value to validate.
|
|
354
|
+
* @returns Whether the value is valid against the validator.
|
|
355
|
+
*/
|
|
356
|
+
export function validate<T extends Validator<any, any, any>>(
|
|
357
|
+
validator: T,
|
|
358
|
+
value: unknown,
|
|
359
|
+
opts?: {
|
|
360
|
+
throw?: boolean;
|
|
361
|
+
pathPrefix?: string;
|
|
362
|
+
},
|
|
363
|
+
): value is T["type"] {
|
|
364
|
+
let valid = true;
|
|
365
|
+
let expected: string = validator.kind;
|
|
366
|
+
if (value === undefined) {
|
|
367
|
+
if (validator.isOptional !== "optional") {
|
|
368
|
+
valid = false;
|
|
369
|
+
}
|
|
370
|
+
} else {
|
|
371
|
+
switch (validator.kind) {
|
|
372
|
+
case "null": {
|
|
373
|
+
if (value !== null) {
|
|
374
|
+
valid = false;
|
|
375
|
+
}
|
|
376
|
+
break;
|
|
377
|
+
}
|
|
378
|
+
case "float64": {
|
|
379
|
+
if (typeof value !== "number") {
|
|
380
|
+
expected = "number";
|
|
381
|
+
valid = false;
|
|
382
|
+
}
|
|
383
|
+
break;
|
|
384
|
+
}
|
|
385
|
+
case "int64": {
|
|
386
|
+
if (typeof value !== "bigint") {
|
|
387
|
+
expected = "bigint";
|
|
388
|
+
valid = false;
|
|
389
|
+
}
|
|
390
|
+
break;
|
|
391
|
+
}
|
|
392
|
+
case "boolean": {
|
|
393
|
+
if (typeof value !== "boolean") {
|
|
394
|
+
valid = false;
|
|
395
|
+
}
|
|
396
|
+
break;
|
|
397
|
+
}
|
|
398
|
+
case "string": {
|
|
399
|
+
if (typeof value !== "string") {
|
|
400
|
+
valid = false;
|
|
401
|
+
}
|
|
402
|
+
break;
|
|
403
|
+
}
|
|
404
|
+
case "bytes": {
|
|
405
|
+
if (!(value instanceof ArrayBuffer)) {
|
|
406
|
+
valid = false;
|
|
407
|
+
}
|
|
408
|
+
break;
|
|
409
|
+
}
|
|
410
|
+
case "any": {
|
|
411
|
+
break;
|
|
412
|
+
}
|
|
413
|
+
case "literal": {
|
|
414
|
+
if (value !== validator.value) {
|
|
415
|
+
valid = false;
|
|
416
|
+
}
|
|
417
|
+
break;
|
|
418
|
+
}
|
|
419
|
+
case "id": {
|
|
420
|
+
if (typeof value !== "string") {
|
|
421
|
+
valid = false;
|
|
422
|
+
}
|
|
423
|
+
break;
|
|
424
|
+
}
|
|
425
|
+
case "array": {
|
|
426
|
+
if (!Array.isArray(value)) {
|
|
427
|
+
valid = false;
|
|
428
|
+
break;
|
|
429
|
+
}
|
|
430
|
+
for (const [index, v] of value.entries()) {
|
|
431
|
+
const path = `${opts?.pathPrefix ?? ""}[${index}]`;
|
|
432
|
+
valid = validate(validator.element, v, { ...opts, pathPrefix: path });
|
|
433
|
+
if (!valid) {
|
|
434
|
+
expected = validator.element.kind;
|
|
435
|
+
break;
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
break;
|
|
439
|
+
}
|
|
440
|
+
case "object": {
|
|
441
|
+
if (typeof value !== "object" || value === null) {
|
|
442
|
+
valid = false;
|
|
443
|
+
break;
|
|
444
|
+
}
|
|
445
|
+
const prototype = Object.getPrototypeOf(value);
|
|
446
|
+
const isSimple =
|
|
447
|
+
prototype === null ||
|
|
448
|
+
prototype === Object.prototype ||
|
|
449
|
+
// Objects generated from other contexts (e.g. across Node.js `vm` modules) will not satisfy the previous
|
|
450
|
+
// conditions but are still simple objects.
|
|
451
|
+
prototype?.constructor?.name === "Object";
|
|
452
|
+
|
|
453
|
+
if (!isSimple) {
|
|
454
|
+
expected =
|
|
455
|
+
prototype?.constructor?.name ?? typeof prototype ?? "object";
|
|
456
|
+
valid = false;
|
|
457
|
+
break;
|
|
458
|
+
}
|
|
459
|
+
for (const [k, fieldValidator] of Object.entries(validator.fields)) {
|
|
460
|
+
valid = validate(fieldValidator, (value as any)[k], {
|
|
461
|
+
...opts,
|
|
462
|
+
pathPrefix: appendPath(opts, k),
|
|
463
|
+
});
|
|
464
|
+
if (!valid) {
|
|
465
|
+
break;
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
for (const k of Object.keys(value)) {
|
|
469
|
+
if (validator.fields[k] === undefined) {
|
|
470
|
+
if (opts?.throw) {
|
|
471
|
+
throw new ValidationError(
|
|
472
|
+
"nothing",
|
|
473
|
+
typeof (value as any)[k],
|
|
474
|
+
appendPath(opts, k),
|
|
475
|
+
);
|
|
476
|
+
}
|
|
477
|
+
valid = false;
|
|
478
|
+
break;
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
break;
|
|
482
|
+
}
|
|
483
|
+
case "union": {
|
|
484
|
+
valid = false;
|
|
485
|
+
for (const member of validator.members) {
|
|
486
|
+
if (validate(member, value, opts)) {
|
|
487
|
+
valid = true;
|
|
488
|
+
break;
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
break;
|
|
492
|
+
}
|
|
493
|
+
case "record": {
|
|
494
|
+
if (typeof value !== "object" || value === null) {
|
|
495
|
+
valid = false;
|
|
496
|
+
break;
|
|
497
|
+
}
|
|
498
|
+
for (const [k, fieldValue] of Object.entries(value)) {
|
|
499
|
+
valid = validate(validator.key, k, {
|
|
500
|
+
...opts,
|
|
501
|
+
pathPrefix: appendPath(opts, k),
|
|
502
|
+
});
|
|
503
|
+
if (!valid) {
|
|
504
|
+
expected = validator.key.kind;
|
|
505
|
+
break;
|
|
506
|
+
}
|
|
507
|
+
valid = validate(validator.value, fieldValue, {
|
|
508
|
+
...opts,
|
|
509
|
+
pathPrefix: appendPath(opts, k),
|
|
510
|
+
});
|
|
511
|
+
if (!valid) {
|
|
512
|
+
expected = validator.value.kind;
|
|
513
|
+
break;
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
break;
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
if (!valid && opts?.throw) {
|
|
521
|
+
throw new ValidationError(expected, typeof value, opts?.pathPrefix);
|
|
522
|
+
}
|
|
523
|
+
return valid;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
function appendPath(opts: { pathPrefix?: string } | undefined, path: string) {
|
|
527
|
+
return opts?.pathPrefix ? `${opts.pathPrefix}.${path}` : path;
|
|
528
|
+
}
|