@metamask/superstruct
Version:
A simple and composable way to validate data in JavaScript (and TypeScript).
1 lines • 12.8 kB
Source Map (JSON)
{"version":3,"file":"struct.cjs","sourceRoot":"","sources":["../src/struct.ts"],"names":[],"mappings":";;;AACA,0CAAyC;AAEzC,0CAAsE;AAWtE;;;;GAIG;AACH,MAAa,MAAM;IAmBjB,YAAY,KAAiC;QAC3C,MAAM,EACJ,IAAI,EACJ,MAAM,EACN,SAAS,EACT,OAAO,EACP,OAAO,GAAG,CAAC,KAAc,EAAE,EAAE,CAAC,KAAK,EACnC,OAAO,GAAG,QAAQ,CAAC;YACjB,UAAU;QACZ,CAAC,GACF,GAAG,KAAK,CAAC;QAEV,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;QACvB,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;QAEvB,IAAI,SAAS,EAAE;YACb,IAAI,CAAC,SAAS,GAAG,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE;gBAClC,MAAM,MAAM,GAAG,SAAS,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;gBACzC,OAAO,IAAA,qBAAU,EAAC,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC;YAClD,CAAC,CAAC;SACH;aAAM;YACL,IAAI,CAAC,SAAS,GAAG,GAAG,EAAE,CAAC,EAAE,CAAC;SAC3B;QAED,IAAI,OAAO,EAAE;YACX,IAAI,CAAC,OAAO,GAAG,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE;gBAChC,MAAM,MAAM,GAAG,OAAO,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;gBACvC,OAAO,IAAA,qBAAU,EAAC,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC;YAClD,CAAC,CAAC;SACH;aAAM;YACL,IAAI,CAAC,OAAO,GAAG,GAAG,EAAE,CAAC,EAAE,CAAC;SACzB;IACH,CAAC;IAED;;OAEG;IAEH,MAAM,CAAC,KAAc,EAAE,OAAgB;QACrC,OAAO,MAAM,CAAC,KAAK,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC;IACtC,CAAC;IAED;;OAEG;IAEH,MAAM,CAAC,KAAc,EAAE,OAAgB;QACrC,OAAO,MAAM,CAAC,KAAK,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC;IACtC,CAAC;IAED;;OAEG;IAEH,EAAE,CAAC,KAAc;QACf,OAAO,EAAE,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;IACzB,CAAC;IAED;;;OAGG;IAEH,IAAI,CAAC,KAAc,EAAE,OAAgB;QACnC,OAAO,IAAI,CAAC,KAAK,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC;IACpC,CAAC;IAED;;;;;;;OAOG;IAEH,QAAQ,CACN,KAAc,EACd,UAGI,EAAE;QAEN,OAAO,QAAQ,CAAC,KAAK,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC;IACxC,CAAC;CACF;AA1GD,wBA0GC;AAED,qFAAqF;AACrF,MAAM,kBAAkB,GAAG,gBAAgB,CAAC;AAE5C;;;GAGG;AACH,MAAa,mBAGX,SAAQ,MAAoB;IAQ5B,YAAY,KAAiC;QAC3C,KAAK,CAAC;YACJ,GAAG,KAAK;YACR,IAAI,EAAE,kBAAkB,KAAK,CAAC,IAAI,EAAE;SACrC,CAAC,CAAC;QACH,IAAI,CAAC,KAAK,GAAG,kBAAkB,CAAC;IAClC,CAAC;IAED,MAAM,CAAC,eAAe,CAAC,KAAc;QACnC,OAAO,CACL,IAAA,mBAAQ,EAAC,KAAK,CAAC,IAAI,OAAO,IAAI,KAAK,IAAI,KAAK,CAAC,KAAK,KAAK,kBAAkB,CAC1E,CAAC;IACJ,CAAC;CACF;AAxBD,kDAwBC;AAED;;;;;;GAMG;AACH,SAAgB,MAAM,CACpB,KAAc,EACd,MAA4B,EAC5B,OAAgB;IAEhB,MAAM,MAAM,GAAG,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC;IAEpD,IAAI,MAAM,CAAC,CAAC,CAAC,EAAE;QACb,MAAM,MAAM,CAAC,CAAC,CAAC,CAAC;KACjB;AACH,CAAC;AAVD,wBAUC;AAED;;;;;;;GAOG;AACH,SAAgB,MAAM,CACpB,KAAc,EACd,MAA4B,EAC5B,OAAgB;IAEhB,MAAM,MAAM,GAAG,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC;IAElE,IAAI,MAAM,CAAC,CAAC,CAAC,EAAE;QACb,MAAM,MAAM,CAAC,CAAC,CAAC,CAAC;KACjB;SAAM;QACL,OAAO,MAAM,CAAC,CAAC,CAAC,CAAC;KAClB;AACH,CAAC;AAZD,wBAYC;AAED;;;;;;;GAOG;AACH,SAAgB,IAAI,CAClB,KAAc,EACd,MAA4B,EAC5B,OAAgB;IAEhB,MAAM,MAAM,GAAG,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC;IAE9E,IAAI,MAAM,CAAC,CAAC,CAAC,EAAE;QACb,MAAM,MAAM,CAAC,CAAC,CAAC,CAAC;KACjB;SAAM;QACL,OAAO,MAAM,CAAC,CAAC,CAAC,CAAC;KAClB;AACH,CAAC;AAZD,oBAYC;AAED;;;;;;GAMG;AACH,SAAgB,EAAE,CAChB,KAAc,EACd,MAA4B;IAE5B,MAAM,MAAM,GAAG,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;IACvC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;AACpB,CAAC;AAND,gBAMC;AAED;;;;;;;;;;;GAWG;AACH,SAAgB,QAAQ,CACtB,KAAc,EACd,MAA4B,EAC5B,UAII,EAAE;IAEN,MAAM,MAAM,GAAG,IAAA,cAAG,EAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC;IAC3C,MAAM,KAAK,GAAG,IAAA,wBAAa,EAAC,MAAM,CAGjC,CAAC;IAEF,IAAI,KAAK,CAAC,CAAC,CAAC,EAAE;QACZ,MAAM,KAAK,GAAG,IAAI,sBAAW,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,QAAQ,CAAC;YAC/C,KAAK,MAAM,UAAU,IAAI,MAAM,EAAE;gBAC/B,IAAI,UAAU,CAAC,CAAC,CAAC,EAAE;oBACjB,MAAM,UAAU,CAAC,CAAC,CAAC,CAAC;iBACrB;aACF;QACH,CAAC,CAAC,CAAC;QAEH,OAAO,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC;KAC3B;IAED,MAAM,cAAc,GAAG,KAAK,CAAC,CAAC,CAAS,CAAC;IACxC,OAAO,CAAC,SAAS,EAAE,cAAc,CAAC,CAAC;AACrC,CAAC;AA7BD,4BA6BC","sourcesContent":["import type { Failure } from './error.js';\nimport { StructError } from './error.js';\nimport type { StructSchema } from './utils.js';\nimport { isObject, toFailures, shiftIterator, run } from './utils.js';\n\ntype StructParams<Type, Schema> = {\n type: string;\n schema: Schema;\n coercer?: Coercer | undefined;\n validator?: Validator | undefined;\n refiner?: Refiner<Type> | undefined;\n entries?: Struct<Type, Schema>['entries'] | undefined;\n};\n\n/**\n * `Struct` objects encapsulate the validation logic for a specific type of\n * values. Once constructed, you use the `assert`, `is` or `validate` helpers to\n * validate unknown input data against the struct.\n */\nexport class Struct<Type = unknown, Schema = unknown> {\n // eslint-disable-next-line @typescript-eslint/naming-convention\n readonly TYPE!: Type;\n\n type: string;\n\n schema: Schema;\n\n coercer: (value: unknown, context: Context) => unknown;\n\n validator: (value: unknown, context: Context) => Iterable<Failure>;\n\n refiner: (value: Type, context: Context) => Iterable<Failure>;\n\n entries: (\n value: unknown,\n context: Context,\n ) => Iterable<[string | number, unknown, Struct<any> | Struct<never>]>;\n\n constructor(props: StructParams<Type, Schema>) {\n const {\n type,\n schema,\n validator,\n refiner,\n coercer = (value: unknown) => value,\n entries = function* () {\n /* noop */\n },\n } = props;\n\n this.type = type;\n this.schema = schema;\n this.entries = entries;\n this.coercer = coercer;\n\n if (validator) {\n this.validator = (value, context) => {\n const result = validator(value, context);\n return toFailures(result, context, this, value);\n };\n } else {\n this.validator = () => [];\n }\n\n if (refiner) {\n this.refiner = (value, context) => {\n const result = refiner(value, context);\n return toFailures(result, context, this, value);\n };\n } else {\n this.refiner = () => [];\n }\n }\n\n /**\n * Assert that a value passes the struct's validation, throwing if it doesn't.\n */\n\n assert(value: unknown, message?: string): asserts value is Type {\n return assert(value, this, message);\n }\n\n /**\n * Create a value with the struct's coercion logic, then validate it.\n */\n\n create(value: unknown, message?: string): Type {\n return create(value, this, message);\n }\n\n /**\n * Check if a value passes the struct's validation.\n */\n\n is(value: unknown): value is Type {\n return is(value, this);\n }\n\n /**\n * Mask a value, coercing and validating it, but returning only the subset of\n * properties defined by the struct's schema.\n */\n\n mask(value: unknown, message?: string): Type {\n return mask(value, this, message);\n }\n\n /**\n * Validate a value with the struct's validation logic, returning a tuple\n * representing the result.\n *\n * You may optionally pass `true` for the `withCoercion` argument to coerce\n * the value before attempting to validate it. If you do, the result will\n * contain the coerced result when successful.\n */\n\n validate(\n value: unknown,\n options: {\n coerce?: boolean;\n message?: string;\n } = {},\n ): [StructError, undefined] | [undefined, Type] {\n return validate(value, this, options);\n }\n}\n\n// String instead of a Symbol in case of multiple different versions of this library.\nconst ExactOptionalBrand = 'EXACT_OPTIONAL';\n\n/**\n * An `ExactOptionalStruct` is a `Struct` that is used to create exactly optional\n * properties of `object()` structs.\n */\nexport class ExactOptionalStruct<\n Type = unknown,\n Schema = unknown,\n> extends Struct<Type, Schema> {\n // ESLint wants us to make this #-private, but we need it to be accessible by\n // other versions of this library at runtime. If it were #-private, the\n // implementation would break if multiple instances of this library were\n // loaded at runtime.\n // eslint-disable-next-line no-restricted-syntax\n readonly brand: typeof ExactOptionalBrand;\n\n constructor(props: StructParams<Type, Schema>) {\n super({\n ...props,\n type: `exact optional ${props.type}`,\n });\n this.brand = ExactOptionalBrand;\n }\n\n static isExactOptional(value: unknown): value is ExactOptionalStruct {\n return (\n isObject(value) && 'brand' in value && value.brand === ExactOptionalBrand\n );\n }\n}\n\n/**\n * Assert that a value passes a struct, throwing if it doesn't.\n *\n * @param value - The value to validate.\n * @param struct - The struct to validate against.\n * @param message - An optional message to include in the error.\n */\nexport function assert<Type, Schema>(\n value: unknown,\n struct: Struct<Type, Schema>,\n message?: string,\n): asserts value is Type {\n const result = validate(value, struct, { message });\n\n if (result[0]) {\n throw result[0];\n }\n}\n\n/**\n * Create a value with the coercion logic of struct and validate it.\n *\n * @param value - The value to coerce and validate.\n * @param struct - The struct to validate against.\n * @param message - An optional message to include in the error.\n * @returns The coerced and validated value.\n */\nexport function create<Type, Schema>(\n value: unknown,\n struct: Struct<Type, Schema>,\n message?: string,\n): Type {\n const result = validate(value, struct, { coerce: true, message });\n\n if (result[0]) {\n throw result[0];\n } else {\n return result[1];\n }\n}\n\n/**\n * Mask a value, returning only the subset of properties defined by a struct.\n *\n * @param value - The value to mask.\n * @param struct - The struct to mask against.\n * @param message - An optional message to include in the error.\n * @returns The masked value.\n */\nexport function mask<Type, Schema>(\n value: unknown,\n struct: Struct<Type, Schema>,\n message?: string,\n): Type {\n const result = validate(value, struct, { coerce: true, mask: true, message });\n\n if (result[0]) {\n throw result[0];\n } else {\n return result[1];\n }\n}\n\n/**\n * Check if a value passes a struct.\n *\n * @param value - The value to validate.\n * @param struct - The struct to validate against.\n * @returns `true` if the value passes the struct, `false` otherwise.\n */\nexport function is<Type, Schema>(\n value: unknown,\n struct: Struct<Type, Schema>,\n): value is Type {\n const result = validate(value, struct);\n return !result[0];\n}\n\n/**\n * Validate a value against a struct, returning an error if invalid, or the\n * value (with potential coercion) if valid.\n *\n * @param value - The value to validate.\n * @param struct - The struct to validate against.\n * @param options - Optional settings.\n * @param options.coerce - Whether to coerce the value before validating it.\n * @param options.mask - Whether to mask the value before validating it.\n * @param options.message - An optional message to include in the error.\n * @returns A tuple containing the error (if invalid) and the validated value.\n */\nexport function validate<Type, Schema>(\n value: unknown,\n struct: Struct<Type, Schema>,\n options: {\n coerce?: boolean | undefined;\n mask?: boolean | undefined;\n message?: string | undefined;\n } = {},\n): [StructError, undefined] | [undefined, Type] {\n const tuples = run(value, struct, options);\n const tuple = shiftIterator(tuples) as [\n Failure | undefined,\n Type | undefined,\n ];\n\n if (tuple[0]) {\n const error = new StructError(tuple[0], function* () {\n for (const innerTuple of tuples) {\n if (innerTuple[0]) {\n yield innerTuple[0];\n }\n }\n });\n\n return [error, undefined];\n }\n\n const validatedValue = tuple[1] as Type;\n return [undefined, validatedValue];\n}\n\n/**\n * A `Context` contains information about the current location of the\n * validation inside the initial input value.\n */\n\nexport type Context = {\n branch: any[];\n path: any[];\n};\n\n/**\n * A type utility to extract the type from a `Struct` class.\n */\n\nexport type Infer<StructType extends Struct<any, any>> = StructType['TYPE'];\n\n/**\n * A type utility to describe that a struct represents a TypeScript type.\n */\n\nexport type Describe<Type> = Struct<Type, StructSchema<Type>>;\n\n/**\n * A `Result` is returned from validation functions.\n */\n\nexport type Result =\n | boolean\n | string\n | Partial<Failure>\n | Iterable<boolean | string | Partial<Failure>>;\n\n/**\n * A `Coercer` takes an unknown value and optionally coerces it.\n */\n\nexport type Coercer<Type = unknown> = (\n value: Type,\n context: Context,\n) => unknown;\n\n/**\n * A `Validator` takes an unknown value and validates it.\n */\n\nexport type Validator = (value: unknown, context: Context) => Result;\n\n/**\n * A `Refiner` takes a value of a known type and validates it against a further\n * constraint.\n */\n\nexport type Refiner<Type> = (value: Type, context: Context) => Result;\n"]}