From 1afbf2483e96d1ea7e6d4701dc87f097598f1ee1 Mon Sep 17 00:00:00 2001 From: Kir_Antipov Date: Thu, 19 Jan 2023 15:51:18 +0000 Subject: [PATCH] Implemented enum descriptor instantiation logic --- src/utils/enum/descriptors/enum-descriptor.ts | 51 +++++++++++++++++++ .../enum/descriptors/enum-descriptor.spec.ts | 33 ++++++++++++ 2 files changed, 84 insertions(+) create mode 100644 tests/unit/utils/enum/descriptors/enum-descriptor.spec.ts diff --git a/src/utils/enum/descriptors/enum-descriptor.ts b/src/utils/enum/descriptors/enum-descriptor.ts index 7e60f69..c55f5c1 100644 --- a/src/utils/enum/descriptors/enum-descriptor.ts +++ b/src/utils/enum/descriptors/enum-descriptor.ts @@ -1,4 +1,8 @@ import { NamedType, TypeOf, TypeOfResult } from "@/utils/types"; +import { BigIntDescriptor } from "./bigint-descriptor"; +import { BooleanDescriptor } from "./boolean-descriptor"; +import { NumberDescriptor } from "./number-descriptor"; +import { StringDescriptor } from "./string-descriptor"; /** * Interface that defines operations that should be implemented by an underlying type of an `Enum`. @@ -46,3 +50,50 @@ export interface EnumDescriptor { */ removeFlag(value: T, flag: T): T; } + +/** + * A map of known `EnumDescriptor`s, keyed by the string representation of their underlying type. + */ +const KNOWN_ENUM_DESCRIPTORS = new Map>([ + ["bigint", new BigIntDescriptor()], + ["boolean", new BooleanDescriptor()], + ["number", new NumberDescriptor()], + ["string", new StringDescriptor()], +]); + +/** + * Gets the {@link EnumDescriptor} for the provided type name. + * + * @template T - The type of the result to return + * @param type - The name of the type to get the descriptor for + * + * @returns The descriptor for the specified type, or `undefined` if there is no such descriptor. + */ +export function getEnumDescriptorByUnderlyingType(type: T): EnumDescriptor> | undefined { + return KNOWN_ENUM_DESCRIPTORS.get(type) as EnumDescriptor>; +} + +/** + * Infers the descriptor for an enum based on its values. + * + * @template T - Type of the enum. + * + * @param values - The values of the enum. + * + * @returns The inferred descriptor for the enum. + * + * @throws An error if the enum contains objects of different types or an invalid underlying type. + */ +export function inferEnumDescriptorOrThrow(values: readonly T[]): EnumDescriptor | never { + if (!values.every((x, i, self) => i === 0 || typeof x === typeof self[i - 1])) { + throw new Error("The enum must contain objects of the same type."); + } + + const underlyingType = values.length ? typeof values[0] : "number"; + const descriptor = getEnumDescriptorByUnderlyingType(underlyingType) as EnumDescriptor; + if (!descriptor) { + throw new Error(`'${underlyingType}' is not an acceptable enum type.`); + } + + return descriptor; +} diff --git a/tests/unit/utils/enum/descriptors/enum-descriptor.spec.ts b/tests/unit/utils/enum/descriptors/enum-descriptor.spec.ts new file mode 100644 index 0000000..460e289 --- /dev/null +++ b/tests/unit/utils/enum/descriptors/enum-descriptor.spec.ts @@ -0,0 +1,33 @@ +import { getEnumDescriptorByUnderlyingType, inferEnumDescriptorOrThrow } from "@/utils/enum/descriptors/enum-descriptor"; + +describe("getEnumDescriptorByUnderlyingType", () => { + test("returns the correct descriptor for a known type", () => { + const knownTypes = ["number", "bigint", "boolean", "string"] as const; + for (const knowType of knownTypes) { + expect(getEnumDescriptorByUnderlyingType(knowType)?.name).toBe(knowType); + } + }); + + test("returns undefined for an unknown type", () => { + expect(getEnumDescriptorByUnderlyingType("unknownType" as "string")).toBeUndefined(); + }); +}); + +describe("inferEnumDescriptorOrThrow", () => { + test("infers correct descriptor based on enum values", () => { + expect(inferEnumDescriptorOrThrow([1, 2, 3]).name).toBe("number"); + expect(inferEnumDescriptorOrThrow(["a", "b", "c"]).name).toBe("string"); + }); + + test("throws error if enum contains objects of different types", () => { + expect(() => inferEnumDescriptorOrThrow([1, "b", 3])).toThrow("The enum must contain objects of the same type."); + }); + + test("throws error if enum has an invalid underlying type", () => { + expect(() => inferEnumDescriptorOrThrow([{}])).toThrow("'object' is not an acceptable enum type."); + }); + + test("returns a number descriptor for an empty array", () => { + expect(inferEnumDescriptorOrThrow([]).name).toBe("number"); + }); +});