UNPKG

@metamask/superstruct

Version:

A simple and composable way to validate data in JavaScript (and TypeScript).

1 lines 18.3 kB
{"version":3,"file":"utils.cjs","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Failure } from './error.js';\nimport type {\n Struct,\n Infer,\n Result,\n Context,\n Describe,\n ExactOptionalStruct,\n} from './struct.js';\n\n/**\n * Check if a value is an iterator.\n *\n * @param value - The value to check.\n * @returns Whether the value is an iterator.\n */\nfunction isIterable<Type>(value: unknown): value is Iterable<Type> {\n return isObject(value) && typeof value[Symbol.iterator] === 'function';\n}\n\n/**\n * Check if a value is a plain object.\n *\n * @param value - The value to check.\n * @returns Whether the value is a plain object.\n */\nexport function isObject(\n value: unknown,\n): value is Record<PropertyKey, unknown> {\n return typeof value === 'object' && value !== null;\n}\n\n/**\n * Check if a value is a plain object.\n *\n * @param value - The value to check.\n * @returns Whether the value is a plain object.\n */\nexport function isPlainObject(value: unknown): value is { [key: string]: any } {\n if (Object.prototype.toString.call(value) !== '[object Object]') {\n return false;\n }\n\n const prototype = Object.getPrototypeOf(value);\n return prototype === null || prototype === Object.prototype;\n}\n\n/**\n * Return a value as a printable string.\n *\n * @param value - The value to print.\n * @returns The value as a string.\n */\nexport function print(value: any): string {\n if (typeof value === 'symbol') {\n return value.toString();\n }\n\n // eslint-disable-next-line @typescript-eslint/restrict-template-expressions\n return typeof value === 'string' ? JSON.stringify(value) : `${value}`;\n}\n\n/**\n * Shift (remove and return) the first value from the `input` iterator.\n * Like `Array.prototype.shift()` but for an `Iterator`.\n *\n * @param input - The iterator to shift.\n * @returns The first value of the iterator, or `undefined` if the iterator is\n * empty.\n */\nexport function shiftIterator<Type>(input: Iterator<Type>): Type | undefined {\n const { done, value } = input.next();\n return done ? undefined : value;\n}\n\n/**\n * Convert a single validation result to a failure.\n *\n * @param result - The result to convert.\n * @param context - The context of the validation.\n * @param struct - The struct being validated.\n * @param value - The value being validated.\n * @returns A failure if the result is a failure, or `undefined` if the result\n * is a success.\n */\nexport function toFailure<Type, Schema>(\n result: string | boolean | Partial<Failure>,\n context: Context,\n struct: Struct<Type, Schema>,\n value: any,\n): Failure | undefined {\n if (result === true) {\n return undefined;\n } else if (result === false) {\n // eslint-disable-next-line no-param-reassign\n result = {};\n } else if (typeof result === 'string') {\n // eslint-disable-next-line no-param-reassign\n result = { message: result };\n }\n\n const { path, branch } = context;\n const { type } = struct;\n const {\n refinement,\n message = `Expected a value of type \\`${type}\\`${\n refinement ? ` with refinement \\`${refinement}\\`` : ''\n }, but received: \\`${print(value)}\\``,\n } = result;\n\n return {\n value,\n type,\n refinement,\n key: path[path.length - 1],\n path,\n branch,\n ...result,\n message,\n };\n}\n\n/**\n * Convert a validation result to an iterable of failures.\n *\n * @param result - The result to convert.\n * @param context - The context of the validation.\n * @param struct - The struct being validated.\n * @param value - The value being validated.\n * @yields The failures.\n * @returns An iterable of failures.\n */\nexport function* toFailures<Type, Schema>(\n result: Result,\n context: Context,\n struct: Struct<Type, Schema>,\n value: any,\n): IterableIterator<Failure> {\n if (!isIterable(result)) {\n // eslint-disable-next-line no-param-reassign\n result = [result];\n }\n\n for (const validationResult of result) {\n const failure = toFailure(validationResult, context, struct, value);\n\n if (failure) {\n yield failure;\n }\n }\n}\n\n/**\n * Check a value against a struct, traversing deeply into nested values, and\n * returning an iterator of failures or success.\n *\n * @param value - The value to check.\n * @param struct - The struct to check against.\n * @param options - Optional settings.\n * @param options.path - The path to the value in the input data.\n * @param options.branch - The branch of the value in the input data.\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 * @yields An iterator of failures or success.\n * @returns An iterator of failures or success.\n */\nexport function* run<Type, Schema>(\n value: unknown,\n struct: Struct<Type, Schema>,\n options: {\n path?: any[] | undefined;\n branch?: any[] | undefined;\n coerce?: boolean | undefined;\n mask?: boolean | undefined;\n message?: string | undefined;\n } = {},\n): IterableIterator<[Failure, undefined] | [undefined, Type]> {\n const { path = [], branch = [value], coerce = false, mask = false } = options;\n const context: Context = { path, branch };\n\n if (coerce) {\n // eslint-disable-next-line no-param-reassign\n value = struct.coercer(value, context);\n\n if (\n mask &&\n struct.type !== 'type' &&\n isObject(struct.schema) &&\n isObject(value) &&\n !Array.isArray(value)\n ) {\n for (const key in value) {\n if (struct.schema[key] === undefined) {\n delete value[key];\n }\n }\n }\n }\n\n let status: 'valid' | 'not_refined' | 'not_valid' = 'valid';\n\n for (const failure of struct.validator(value, context)) {\n failure.explanation = options.message;\n status = 'not_valid';\n yield [failure, undefined];\n }\n\n // eslint-disable-next-line prefer-const\n for (let [innerKey, innerValue, innerStruct] of struct.entries(\n value,\n context,\n )) {\n const iterable = run(innerValue, innerStruct as Struct, {\n path: innerKey === undefined ? path : [...path, innerKey],\n branch: innerKey === undefined ? branch : [...branch, innerValue],\n coerce,\n mask,\n message: options.message,\n });\n\n for (const result of iterable) {\n if (result[0]) {\n status =\n result[0].refinement === null || result[0].refinement === undefined\n ? 'not_valid'\n : 'not_refined';\n\n yield [result[0], undefined];\n } else if (coerce) {\n innerValue = result[1];\n\n if (innerKey === undefined) {\n // eslint-disable-next-line no-param-reassign\n value = innerValue;\n } else if (value instanceof Map) {\n value.set(innerKey, innerValue);\n } else if (value instanceof Set) {\n value.add(innerValue);\n } else if (isObject(value)) {\n if (innerValue !== undefined || innerKey in value) {\n value[innerKey] = innerValue;\n }\n }\n }\n }\n }\n\n if (status !== 'not_valid') {\n for (const failure of struct.refiner(value as Type, context)) {\n failure.explanation = options.message;\n status = 'not_refined';\n yield [failure, undefined];\n }\n }\n\n if (status === 'valid') {\n yield [undefined, value as Type];\n }\n}\n\n/**\n * Convert a union of type to an intersection.\n */\nexport type UnionToIntersection<Union> = (\n Union extends any ? (arg: Union) => any : never\n) extends (arg: infer Type) => void\n ? Type\n : never;\n\n/**\n * Assign properties from one type to another, overwriting existing.\n */\nexport type Assign<Type, OtherType> = Simplify<\n OtherType & Omit<Type, keyof OtherType>\n>;\n\n/**\n * A schema for enum structs.\n */\nexport type EnumSchema<Type extends string | number | undefined | null> = {\n [Key in NonNullable<Type>]: Key;\n};\n\n/**\n * Check if a type is a match for another whilst treating overlapping\n * unions as a match.\n */\nexport type IsMatch<Type, OtherType> = Type extends OtherType\n ? OtherType extends Type\n ? Type\n : never\n : never;\n\n/**\n * Check if a type is an exact match.\n */\nexport type IsExactMatch<Type, OtherType> = (<Inner>() => Inner extends Type\n ? 1\n : 2) extends <Inner>() => Inner extends OtherType ? 1 : 2\n ? Type\n : never;\n\n/**\n * Check if a type is a record type.\n */\nexport type IsRecord<Type> = Type extends object\n ? string extends keyof Type\n ? Type\n : never\n : never;\n\n/**\n * Check if a type is a tuple.\n */\nexport type IsTuple<Type> = Type extends [any]\n ? Type\n : Type extends [any, any]\n ? Type\n : Type extends [any, any, any]\n ? Type\n : Type extends [any, any, any, any]\n ? Type\n : Type extends [any, any, any, any, any]\n ? Type\n : never;\n\n/**\n * Check if a type is a union.\n */\nexport type IsUnion<Type, Union extends Type = Type> = (\n Type extends any ? (Union extends Type ? false : true) : false\n) extends false\n ? never\n : Type;\n\n/**\n * A schema for object structs.\n */\nexport type ObjectSchema = Record<string, Struct<any, any>>;\n\n/**\n * Infer a type from an object struct schema.\n */\nexport type ObjectType<Schema extends ObjectSchema> = Simplify<\n // ExactOptionalize first ensures that properties of `exactOptional()` structs\n // are optional, then Optionalize ensures that properties that can have the\n // value `undefined` are optional.\n Optionalize<ExactOptionalize<Schema>>\n>;\n\n/**\n * Make properties of `exactOptional()` structs optional.\n */\nexport type ExactOptionalize<Schema extends ObjectSchema> = {\n [K in keyof OmitExactOptional<Schema>]: Infer<OmitExactOptional<Schema>[K]>;\n} & {\n [K in keyof PickExactOptional<Schema>]?: Infer<PickExactOptional<Schema>[K]>;\n};\n\ntype OmitExactOptional<Schema extends ObjectSchema> = Omit<\n Schema,\n {\n [K in keyof Schema]: Schema[K] extends ExactOptionalStruct<any, any>\n ? Schema[K] extends never\n ? never\n : K\n : never;\n }[keyof Schema]\n>;\n\ntype PickExactOptional<Schema extends ObjectSchema> = Pick<\n Schema,\n {\n [K in keyof Schema]: Schema[K] extends ExactOptionalStruct<any, any>\n ? Schema[K] extends never\n ? never\n : K\n : never;\n }[keyof Schema]\n>;\n\n/**\n * Make properties that can have the value `undefined` optional.\n */\nexport type Optionalize<Schema extends object> = OmitBy<Schema, undefined> &\n Partial<PickBy<Schema, undefined>>;\n\n/**\n * Omit properties from a type that extend from a specific type.\n */\nexport type OmitBy<Type, Value> = Omit<\n Type,\n {\n [Key in keyof Type]: Value extends Extract<Type[Key], Value> ? Key : never;\n }[keyof Type]\n>;\n\n/**\n * Pick properties from a type that extend from a specific type.\n */\nexport type PickBy<Type, Value> = Pick<\n Type,\n {\n [Key in keyof Type]: Value extends Extract<Type[Key], Value> ? Key : never;\n }[keyof Type]\n>;\n\n/**\n * Transform an object schema type to represent a partial.\n */\nexport type PartialObjectSchema<Schema extends ObjectSchema> = {\n [K in keyof Schema]: Struct<Infer<Schema[K]> | undefined>;\n};\n\n/**\n * Simplifies a type definition to its most basic representation.\n */\nexport type Simplify<Type> = Type extends any[] | Date\n ? Type\n : // eslint-disable-next-line @typescript-eslint/ban-types\n { [Key in keyof Type]: Type[Key] } & {};\n\nexport type If<Condition extends boolean, Then, Else> = Condition extends true\n ? Then\n : Else;\n\n/**\n * A schema for any type of struct.\n */\nexport type StructSchema<Type> = [Type] extends [string | undefined | null]\n ? [Type] extends [IsMatch<Type, string | undefined | null>]\n ? null\n : [Type] extends [IsUnion<Type>]\n ? EnumSchema<Type>\n : Type\n : [Type] extends [number | undefined | null]\n ? [Type] extends [IsMatch<Type, number | undefined | null>]\n ? null\n : [Type] extends [IsUnion<Type>]\n ? EnumSchema<Type>\n : Type\n : [Type] extends [boolean]\n ? [Type] extends [IsExactMatch<Type, boolean>]\n ? null\n : Type\n : Type extends\n | bigint\n | symbol\n | undefined\n | null\n // eslint-disable-next-line @typescript-eslint/ban-types\n | Function\n | Date\n | Error\n | RegExp\n | Map<any, any>\n | WeakMap<any, any>\n | Set<any>\n | WeakSet<any>\n | Promise<any>\n ? null\n : Type extends (infer Inner)[]\n ? Type extends IsTuple<Type>\n ? null\n : Struct<Inner>\n : Type extends object\n ? Type extends IsRecord<Type>\n ? null\n : { [K in keyof Type]: Describe<Type[K]> }\n : null;\n\n/**\n * A schema for tuple structs.\n */\nexport type TupleSchema<Type> = { [K in keyof Type]: Struct<Type[K]> };\n\n/**\n * Shorthand type for matching any `Struct`.\n */\nexport type AnyStruct = Struct<any, any>;\n\n/**\n * Infer a tuple of types from a tuple of `Struct`s.\n *\n * This is used to recursively retrieve the type from `union` `intersection` and\n * `tuple` structs.\n */\nexport type InferStructTuple<\n Tuple extends AnyStruct[],\n Length extends number = Tuple['length'],\n> = Length extends Length\n ? number extends Length\n ? Tuple\n : InferTuple<Tuple, Length, []>\n : never;\n\ntype InferTuple<\n Tuple extends AnyStruct[],\n Length extends number,\n Accumulated extends unknown[],\n Index extends number = Accumulated['length'],\n> = Index extends Length\n ? Accumulated\n : InferTuple<Tuple, Length, [...Accumulated, Infer<Tuple[Index]>]>;\n"]}