Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 | 2x 2x 2x 3x 3x 3x 5x 1x 2x 3x 3x 3x 2x 14x 3x 6x 1x 1x 3x 3x 1x 1x 1x 1x 1x 2x 3x 3x 2x 2x 2x 1x 2x 1x | import {
array,
assert,
boolean,
define,
number,
object,
optional,
string,
StructError,
} from '@metamask/superstruct';
import type { Struct } from '@metamask/superstruct';
import type { JsonSchema } from './schema.ts';
type ObjectJsonSchema = Extract<JsonSchema, { type: 'object' }>;
/**
* Object schema where unknown property names are allowed (when `additionalProperties` is not `false`).
* Known properties are validated; required keys must be present.
*
* @param schema - JSON Schema with `type: 'object'`, `properties`, and optional `required`.
* @returns A Superstruct validator for plain objects matching the loose object rules.
*/
function looseObjectStruct(schema: ObjectJsonSchema): Struct<unknown> {
const { properties } = schema;
const required = new Set(schema.required ?? Object.keys(properties));
return define('JsonSchemaObject', (value) => {
Iif (typeof value !== 'object' || value === null || Array.isArray(value)) {
return 'Expected a plain object';
}
const obj = value as Record<string, unknown>;
for (const key of required) {
if (!(key in obj)) {
return `Missing required property "${key}"`;
}
}
for (const [key, subSchema] of Object.entries(properties)) {
Iif (!(key in obj)) {
continue;
}
try {
assert(obj[key], jsonSchemaToStruct(subSchema));
} catch (caught) {
if (caught instanceof StructError) {
return `At ${key}: ${caught.message}`;
}
throw caught;
}
}
return true;
}) as Struct<unknown>;
}
/**
* Build a Superstruct {@link Struct} from our {@link JsonSchema} subset (primitives,
* arrays, objects). Used to reuse Superstruct validation for values described by
* discoverable exo / capability {@link MethodSchema} argument shapes.
*
* @param schema - JSON Schema value (must include `type`).
* @returns A struct that validates the same shapes as the prior hand-rolled checks.
*/
export function jsonSchemaToStruct(schema: JsonSchema): Struct<unknown> {
switch (schema.type) {
case 'string':
return string() as Struct<unknown>;
case 'number':
return number() as Struct<unknown>;
case 'boolean':
return boolean() as Struct<unknown>;
case 'array':
return array(jsonSchemaToStruct(schema.items)) as Struct<unknown>;
case 'object': {
const { properties } = schema;
if (schema.additionalProperties === false) {
const required = new Set(schema.required ?? Object.keys(properties));
const shape = Object.fromEntries(
Object.entries(properties).map(([key, subSchema]) => {
const fieldStruct = jsonSchemaToStruct(subSchema);
return [
key,
required.has(key) ? fieldStruct : optional(fieldStruct),
];
}),
);
return object(shape) as Struct<unknown>;
}
return looseObjectStruct(schema);
}
default: {
const _never: never = schema;
throw new TypeError(`Unsupported JSON schema: ${String(_never)}`);
}
}
}
/**
* Build a Superstruct object struct for a method/capability `args` map
* (name → per-argument {@link JsonSchema}). All listed arguments are required.
*
* @param args - Same shape as {@link MethodSchema.args}.
* @returns A struct that validates a plain object with one field per declared argument.
*/
export function methodArgsToStruct(
args: Record<string, JsonSchema>,
): Struct<Record<string, unknown>> {
const entries = Object.entries(args);
if (entries.length === 0) {
return define('EmptyCapabilityArgs', (value) => {
Iif (typeof value !== 'object' || value === null || Array.isArray(value)) {
return 'Expected a plain object';
}
return true;
}) as Struct<Record<string, unknown>>;
}
const shape = Object.fromEntries(
entries.map(([name, jsonSchema]) => [name, jsonSchemaToStruct(jsonSchema)]),
);
return object(shape) as Struct<Record<string, unknown>>;
}
|