UNPKG

object-shape-tester

Version:
371 lines (370 loc) 9.69 kB
import { check } from '@augment-vir/assert'; import { isShapeDefinitionKey, isShapeSpecifierKey } from './shape-keys.js'; /** * Checks if the given input is a {@link isShapeDefinition}. * * @category Util */ export function isShapeDefinition(input) { return check.hasKey(input, isShapeDefinitionKey); } /** * ======================================== * * Specifier Symbols * * ======================================== */ /** * Values used to mark the outputs of each sub-shape function (like {@link or}). * * @category Internal */ export var ShapeSpecifierType; (function (ShapeSpecifierType) { ShapeSpecifierType["And"] = "and"; ShapeSpecifierType["Class"] = "class"; ShapeSpecifierType["Enum"] = "enum"; ShapeSpecifierType["Exact"] = "exact"; ShapeSpecifierType["IndexedKeys"] = "indexed-keys"; ShapeSpecifierType["Or"] = "or"; ShapeSpecifierType["Unknown"] = "unknown"; ShapeSpecifierType["NumericRange"] = "numeric-range"; ShapeSpecifierType["Optional"] = "optional"; ShapeSpecifierType["Tuple"] = "tuple"; })(ShapeSpecifierType || (ShapeSpecifierType = {})); /** * ======================================== * * Shape Functions * * ======================================== */ /** * Create a shape part that combines all of its inputs together with an intersection or "and". * * @category Shape Part * @example * * ```ts * import {and, defineShape} from 'object-shape-tester'; * * const myShape = defineShape({ * a: and({q: ''}, {r: -1}, {s: true}), * }); * * // `myShape.runtimeType` is `{a: {q: string, r: number, s: boolean}}` * ``` */ export function and(...parts) { return specifier(parts, ShapeSpecifierType.And); } /** * Define a shape part that requires an instance of the given constructor. * * @category Shape Part * @example * * ```ts * import {classShape, defineShape} from 'object-shape-tester'; * * const myShape = defineShape({ * a: classShape(RegExp), * }); * * // `myShape.runtimeType` is `{a: RegExp}` * ``` */ export function classShape(...parts) { return specifier(parts, ShapeSpecifierType.Class); } /** * Define a shape part that requires an enum value. * * @category Shape Part * @example * * ```ts * import {enumShape, defineShape} from 'object-shape-tester'; * * enum MyEnum { * A = 'a', * B = 'b', * } * * const myShape = defineShape({ * a: enumShape(MyEnum), * }); * * // `myShape.runtimeType` is `{a: MyEnum}` * ``` */ export function enumShape(...parts) { return specifier(parts, ShapeSpecifierType.Enum); } /** * Define a shape part that requires _exactly_ the value given. * * @category Shape Part * @example * * ```ts * import {exact, defineShape} from 'object-shape-tester'; * * const myShape = defineShape({ * a: or(exact('hi'), exact('bye')), * }); * * // `myShape.runtimeType` is `{a: 'hi' | 'bye'}` * ``` */ export function exact(...parts) { return specifier(parts, ShapeSpecifierType.Exact); } /** * Define a shape part that's an object with a specific set of keys and values. * * @category Shape Part * @example * * ```ts * import {exact, defineShape, indexedKeys} from 'object-shape-tester'; * * const myShape = defineShape({ * a: indexedKeys({ * keys: or(exact('hi'), exact('bye')), * values: { * helloThere: 0, * }, * required: false, * }), * }); * * // `myShape.runtimeType` is `{a: Partial<Record<'hi' | 'bye', {helloThere: number}>>}` * ``` */ export function indexedKeys(...parts) { return specifier(parts, ShapeSpecifierType.IndexedKeys); } /** * Define a shape part requires a tuple. * * @category Shape Part * @example * * ```ts * import {exact, defineShape, tupleShape} from 'object-shape-tester'; * * const myShape = defineShape({ * a: tupleShape('a', -1, exact('hi')), * }); * * // `myShape.runtimeType` is `[string, number, 'hi']` * ``` */ export function tupleShape(...parts) { return specifier(parts, ShapeSpecifierType.Tuple); } /** * Define a shape part that's a union of all its inputs. * * @category Shape Part * @example * * ```ts * import {or, defineShape} from 'object-shape-tester'; * * const myShape = defineShape({ * a: or('', -1), * }); * * // `myShape.runtimeType` is `{a: string | number}` * ``` */ export function or(...parts) { return specifier(parts, ShapeSpecifierType.Or); } /** * Define a shape part that resolves simply to `unknown`. * * @category Shape Part * @example * * ```ts * import {unknownShape, defineShape} from 'object-shape-tester'; * * const myShape = defineShape({ * a: unknownShape, * }); * * // `myShape.runtimeType` is `{a: unknown}` * ``` */ export function unknownShape(defaultValue) { return specifier([defaultValue], ShapeSpecifierType.Unknown); } /** * Define a shape part that requires numbers to be within a specific range, inclusive. * * @category Shape Part * @example * * ```ts * import {numericRange, defineShape} from 'object-shape-tester'; * * const myShape = defineShape({ * // This will simply produce a type of `number` but will validate runtime values against the range. * a: numericRange(1, 10), * }); * // `myShape.runtimeType` is just `{a: number}` * * const myShape2 = defineShape({ * // If you want type safety, you must specify the allowed numbers manually * a: numericRange<1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10>(1, 10), * }); * // `myShape2.runtimeType` is `{a: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10}` * ``` */ export function numericRange(min, max) { return specifier([ min, max, ], ShapeSpecifierType.NumericRange); } /** * Define a shape part that is optional. This only makes sense as a property in an object. * * @category Shape Part * @example * * ```ts * import {optional, defineShape} from 'object-shape-tester'; * * const myShape = defineShape({ * a: optional(-1), * }); * * // `myShape.runtimeType` is `{a?: number}` * ``` */ export function optional(part) { return specifier([part], ShapeSpecifierType.Optional); } /** * ======================================== * * Shape Specifier Type Guards * * ======================================== */ /** * Checks if the input is an {@link and} shape specifier for internal type guarding purposes. * * @category Internal */ export function isAndShapeSpecifier(maybeSpecifier) { return specifierHasSymbol(maybeSpecifier, ShapeSpecifierType.And); } /** * Checks if the input is a {@link classShape} shape specifier for internal type guarding purposes. * * @category Internal */ export function isClassShapeSpecifier(maybeSpecifier) { return specifierHasSymbol(maybeSpecifier, ShapeSpecifierType.Class); } /** * Checks if the input is an {@link enumShape} shape specifier for internal type guarding purposes. * * @category Internal */ export function isEnumShapeSpecifier(maybeSpecifier) { return specifierHasSymbol(maybeSpecifier, ShapeSpecifierType.Enum); } /** * Checks if the input is an {@link exact} shape specifier for internal type guarding purposes. * * @category Internal */ export function isExactShapeSpecifier(maybeSpecifier) { return specifierHasSymbol(maybeSpecifier, ShapeSpecifierType.Exact); } /** * Checks if the input is an {@link indexedKeys} shape specifier for internal type guarding purposes. * * @category Internal */ export function isIndexedKeysSpecifier(maybeSpecifier) { return specifierHasSymbol(maybeSpecifier, ShapeSpecifierType.IndexedKeys); } /** * Checks if the input is an {@link tupleShape} shape specifier for internal type guarding purposes. * * @category Internal */ export function isTupleShapeSpecifier(maybeSpecifier) { return specifierHasSymbol(maybeSpecifier, ShapeSpecifierType.Tuple); } /** * Checks if the input is an {@link or} shape specifier for internal type guarding purposes. * * @category Internal */ export function isOrShapeSpecifier(maybeSpecifier) { return specifierHasSymbol(maybeSpecifier, ShapeSpecifierType.Or); } /** * Checks if the input is an {@link unknownShape} shape specifier for internal type guarding * purposes. * * @category Internal */ export function isUnknownShapeSpecifier(maybeSpecifier) { return specifierHasSymbol(maybeSpecifier, ShapeSpecifierType.Unknown); } /** * Checks if the input is a {@link numericRange} shape specifier for internal type guarding purposes. * * @category Internal */ export function isNumericRangeShapeSpecifier(maybeSpecifier) { return specifierHasSymbol(maybeSpecifier, ShapeSpecifierType.NumericRange); } /** * Checks if the input is a {@link optional} shape specifier for internal type guarding purposes. * * @category Internal */ export function isOptionalShapeSpecifier(maybeSpecifier) { return specifierHasSymbol(maybeSpecifier, ShapeSpecifierType.Optional); } /** * ======================================== * * Specifier Utilities * * ======================================== */ function specifierHasSymbol(maybeSpecifier, symbol) { const specifier = getShapeSpecifier(maybeSpecifier); return !!specifier && specifier.specifierType === symbol; } function specifier(parts, specifierType) { return { [isShapeSpecifierKey]: true, specifierType, parts, }; } /** * If the input is a shape specifier, return it type guarded as such. * * @category Internal * @returns `undefined` if the input is not */ export function getShapeSpecifier(input) { if (!check.isObject(input) || !check.hasKey(input, isShapeSpecifierKey)) { return undefined; } return input; }