Implemented enum descriptor instantiation logic

This commit is contained in:
Kir_Antipov 2023-01-19 15:51:18 +00:00
parent 41d4de590e
commit 1afbf2483e
2 changed files with 84 additions and 0 deletions

View file

@ -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<T> {
*/
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<TypeOfResult, EnumDescriptor<unknown>>([
["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<T extends TypeOfResult>(type: T): EnumDescriptor<NamedType<T>> | undefined {
return KNOWN_ENUM_DESCRIPTORS.get(type) as EnumDescriptor<NamedType<T>>;
}
/**
* 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<T>(values: readonly T[]): EnumDescriptor<T> | 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<T>;
if (!descriptor) {
throw new Error(`'${underlyingType}' is not an acceptable enum type.`);
}
return descriptor;
}

View file

@ -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");
});
});