Suite of AT Protocol TypeScript libraries built on web standards
1import type { WithOptionalProperties } from "../core/types.ts";
2import { lazyProperty } from "../util/lazy-property.ts";
3import { isPlainObject } from "../data/object.ts";
4import {
5 type Infer,
6 Schema,
7 type ValidationResult,
8 type Validator,
9 type ValidatorContext,
10} from "../validation.ts";
11
12export type ObjectSchemaShape = Record<string, Validator>;
13
14export type ObjectSchemaOutput<Shape extends ObjectSchemaShape> =
15 WithOptionalProperties<
16 {
17 [K in keyof Shape]: Infer<Shape[K]>;
18 }
19 >;
20
21export class ObjectSchema<
22 const Shape extends ObjectSchemaShape = any,
23> extends Schema<ObjectSchemaOutput<Shape>> {
24 constructor(readonly shape: Shape) {
25 super();
26 }
27
28 get validatorsMap(): Map<string, Validator> {
29 const map = new Map(Object.entries(this.shape));
30 return lazyProperty(this, "validatorsMap", map);
31 }
32
33 validateInContext(
34 input: unknown,
35 ctx: ValidatorContext,
36 ): ValidationResult<ObjectSchemaOutput<Shape>> {
37 if (!isPlainObject(input)) {
38 return ctx.issueInvalidType(input, "object");
39 }
40
41 let copy: Record<string, unknown> | undefined;
42
43 for (const [key, propDef] of this.validatorsMap) {
44 const result = ctx.validateChild(input, key, propDef);
45 if (!result.success) {
46 if (!(key in input)) {
47 return ctx.issueRequiredKey(input, key);
48 }
49 return result;
50 }
51
52 if (result.value === undefined && !(key in input)) {
53 continue;
54 }
55
56 if (result.value !== input[key]) {
57 copy ??= { ...input };
58 copy[key] = result.value;
59 }
60 }
61
62 return ctx.success((copy ?? input) as ObjectSchemaOutput<Shape>);
63 }
64}