UNPKG

@metamask/snaps-sdk

Version:

A library containing the core functionality for building MetaMask Snaps

182 lines 6.56 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.nonEmptyRecord = exports.selectiveUnion = exports.typedUnion = exports.enumValue = exports.union = exports.literal = void 0; const superstruct_1 = require("@metamask/superstruct"); const utils_1 = require("@metamask/utils"); /** * A wrapper of `superstruct`'s `literal` struct that also defines the name of * the struct as the literal value. * * This is useful for improving the error messages returned by `superstruct`. * For example, instead of returning an error like: * * ``` * Expected the value to satisfy a union of `literal | literal`, but received: \"baz\" * ``` * * This struct will return an error like: * * ``` * Expected the value to satisfy a union of `"foo" | "bar"`, but received: \"baz\" * ``` * * @param value - The literal value. * @returns The `superstruct` struct, which validates that the value is equal * to the literal value. */ function literal(value) { return (0, superstruct_1.define)(JSON.stringify(value), (0, superstruct_1.literal)(value).validator); } exports.literal = literal; /** * A wrapper of `superstruct`'s `union` struct that also defines the schema as * the union of the schemas of the structs. * * This is useful for improving the error messages returned by `superstruct`. * * @param structs - The structs to union. * @param structs."0" - The first struct. * @param structs."1" - The remaining structs. * @returns The `superstruct` struct, which validates that the value satisfies * one of the structs. */ function union([head, ...tail]) { const struct = (0, superstruct_1.union)([head, ...tail]); return new superstruct_1.Struct({ ...struct, schema: [head, ...tail], }); } exports.union = union; /** * Superstruct struct for validating an enum value. This allows using both the * enum string values and the enum itself as values. * * @param constant - The enum to validate against. * @returns The superstruct struct. */ function enumValue(constant) { return literal(constant); } exports.enumValue = enumValue; /** * Create a custom union struct that validates exclusively based on a `type` field. * * This should improve error messaging for unions with many structs in them. * * @param structs - The structs to union. * @returns The `superstruct` struct, which validates that the value satisfies * one of the structs. */ function typedUnion(structs) { const flatStructs = structs .map((struct) => struct.type === 'union' && Array.isArray(struct.schema) ? struct.schema : struct) .flat(Infinity); const types = flatStructs.map(({ schema }) => schema.type.type); const structMap = flatStructs.reduce((accumulator, struct) => { accumulator[JSON.parse(struct.schema.type.type)] = struct; return accumulator; }, {}); return new superstruct_1.Struct({ type: 'union', schema: flatStructs, *entries(value, context) { if (!(0, utils_1.isObject)(value) || !(0, utils_1.hasProperty)(value, 'type') || typeof value.type !== 'string') { return; } const { type } = value; const struct = structMap[type]; if (!struct) { return; } for (const entry of struct.entries(value, context)) { yield entry; } }, coercer(value, context) { if (!(0, utils_1.isObject)(value) || !(0, utils_1.hasProperty)(value, 'type') || typeof value.type !== 'string') { return value; } const { type } = value; const struct = structMap[type]; if (struct) { return struct.coercer(value, context); } return value; }, // At this point we know the value to be an object. *refiner(value, context) { const struct = structMap[value.type]; yield* struct.refiner(value, context); }, validator(value, context) { if (!(0, utils_1.isObject)(value) || !(0, utils_1.hasProperty)(value, 'type') || typeof value.type !== 'string') { return `Expected type to be one of: ${types.join(', ')}, but received: undefined`; } const { type } = value; const struct = structMap[type]; if (struct) { // This only validates the root of the struct, entries does the rest of the work. return struct.validator(value, context); } return `Expected type to be one of: ${types.join(', ')}, but received: "${type}"`; }, }); } exports.typedUnion = typedUnion; /** * Create a custom union struct that uses a `selector` function for choosing * the validation path. * * @param selector - The selector function choosing the struct to validate with. * @returns The `superstruct` struct, which validates that the value satisfies * one of the structs. */ function selectiveUnion(selector) { return new superstruct_1.Struct({ type: 'union', schema: null, *entries(value, context) { const struct = selector(value); for (const entry of struct.entries(value, context)) { yield entry; } }, *refiner(value, context) { const struct = selector(value); yield* struct.refiner(value, context); }, coercer(value, context) { const struct = selector(value); return struct.coercer(value, context); }, validator(value, context) { const struct = selector(value); // This only validates the root of the struct, entries does the rest of the work. return struct.validator(value, context); }, }); } exports.selectiveUnion = selectiveUnion; /** * Refine a struct to be a non-empty record and disallows usage of arrays. * * @param Key - The struct for the record key. * @param Value - The struct for the record value. * @returns The refined struct. */ function nonEmptyRecord(Key, Value) { return (0, superstruct_1.refine)((0, superstruct_1.record)(Key, Value), 'Non-empty record', (value) => { return !Array.isArray(value) && Object.keys(value).length > 0; }); } exports.nonEmptyRecord = nonEmptyRecord; //# sourceMappingURL=structs.cjs.map