diff --git a/src/lib/__tests__/serialize.spec.ts b/src/lib/__tests__/serialize.spec.ts index fb01463..7bdd90c 100644 --- a/src/lib/__tests__/serialize.spec.ts +++ b/src/lib/__tests__/serialize.spec.ts @@ -1,7 +1,17 @@ import { describe, expect, it } from "vitest"; import { + type ComposedKeySetSerialized, + type IKeyLabel, InvalidKeySetError, + type KeyLabelSetAll, + type KeyLabelSetAllExceptSome, + type KeyLabelSetAllExceptSomeSerialized, + type KeyLabelSetAllSerialized, + type KeyLabelSetNone, + type KeyLabelSetNoneSerialized, + type KeyLabelSetSome, + type KeyLabelSetSomeSerialized, type KeySet, KeySetAll, KeySetAllExceptSome, @@ -15,13 +25,20 @@ import { KeySetTypes, all, allExceptSome, + composedKeySetFrom, + isComposedKeySetSerialized, + isKeyLabelSetSerialized, isKeySetAllExceptSomeSerialized, isKeySetAllSerialized, isKeySetNoneSerialized, isKeySetSerialized, isKeySetSomeSerialized, none, + parseComposedKeySet, + parseKeyLabelSet, parseKeySet, + serializeComposedKeySet, + serializeKeyLabelSet, serializeKeySet, some, } from "../.."; @@ -474,4 +491,165 @@ describe("serialize KeySet", () => { expect(JSON.stringify(keySet)).toEqual(JSON.stringify(expected)); }); }); + + describe("KeyLabelSet", () => { + const allKS: KeyLabelSetAll = all>(); + const allSerialized: KeyLabelSetAllSerialized = { type: KeySetTypes.all }; + + const noneKS: KeyLabelSetNone = none>(); + const noneSerialized: KeyLabelSetNoneSerialized = { type: KeySetTypes.none }; + + const aesKS: KeyLabelSetAllExceptSome = allExceptSome([ + { key: "a", label: "A" }, + { key: "b", label: "B" }, + ]); + const aesSerialized: KeyLabelSetAllExceptSomeSerialized = { + type: KeySetTypes.allExceptSome, + elements: [ + { key: "a", label: "A" }, + { key: "b", label: "B" }, + ], + }; + const someKS: KeyLabelSetSome = some([ + { key: "a", label: "A" }, + { key: "b", label: "B" }, + ]); + const someSerialized: KeyLabelSetSomeSerialized = { + type: KeySetTypes.some, + elements: [ + { key: "a", label: "A" }, + { key: "b", label: "B" }, + ], + }; + + const aesKeySetSerialized = { + type: KeySetTypes.allExceptSome, + elements: [1, 2], + }; + + const someKeySetSerialized = { + type: KeySetTypes.some, + elements: [1, 2], + }; + + describe("isKeyLabelSetSerialized()", () => { + it("isKeyLabelSetSerialized(keyLabelSetSerialized) -> true", () => { + expect(isKeyLabelSetSerialized(aesSerialized)).toBeTruthy(); + expect(isKeyLabelSetSerialized(someSerialized)).toBeTruthy(); + expect(isKeyLabelSetSerialized(allSerialized)).toBeTruthy(); + expect(isKeyLabelSetSerialized(noneSerialized)).toBeTruthy(); + }); + it("isKeyLabelSetSerialized(normalKeySetSerialized some or AES) -> false", () => { + expect(isKeyLabelSetSerialized(aesKeySetSerialized)).toBeFalsy(); + expect(isKeyLabelSetSerialized(someKeySetSerialized)).toBeFalsy(); + }); + it("isKeyLabelSetSerialized(keySet) -> false", () => { + expect(isKeyLabelSetSerialized(allKS)).toBeFalsy(); + expect(isKeyLabelSetSerialized(noneKS)).toBeFalsy(); + expect(isKeyLabelSetSerialized(someKS)).toBeFalsy(); + expect(isKeyLabelSetSerialized(aesKS)).toBeFalsy(); + }); + }); + + describe("serializeKeyLabelSet()", () => { + it("serializeKeyLabelSet(keyLabelSetSerialized) -> return same", () => { + expect(serializeKeyLabelSet(aesSerialized)).toBe(aesSerialized); + expect(serializeKeyLabelSet(someSerialized)).toBe(someSerialized); + expect(serializeKeyLabelSet(allSerialized)).toBe(allSerialized); + expect(serializeKeyLabelSet(noneSerialized)).toBe(noneSerialized); + }); + it("serializeKeyLabelSet(ks) -> serialize", () => { + expect(serializeKeyLabelSet(aesKS)).toEqual(aesSerialized); + expect(serializeKeyLabelSet(someKS)).toEqual(someSerialized); + expect(serializeKeyLabelSet(allKS)).toEqual(allSerialized); + expect(serializeKeyLabelSet(noneKS)).toEqual(noneSerialized); + }); + it("serializeKeyLabelSet(invalid) -> throws", () => { + expect(() => serializeKeyLabelSet("whatever" as unknown as KeySet)).toThrow(); + }); + }); + + describe("parseKeyLabelSet()", () => { + it("parseKeyLabelSet(keyLabelSetSerialized) -> return same", () => { + expect(parseKeyLabelSet(aesSerialized)).toEqual(aesKS); + expect(parseKeyLabelSet(someSerialized)).toEqual(someKS); + expect(parseKeyLabelSet(allSerialized)).toEqual(allKS); + expect(parseKeyLabelSet(noneSerialized)).toEqual(noneKS); + }); + it("parseKeyLabelSet(ks) -> parse", () => { + expect(parseKeyLabelSet(aesKS)).toBe(aesKS); + expect(parseKeyLabelSet(someKS)).toBe(someKS); + expect(parseKeyLabelSet(allKS)).toBe(allKS); + expect(parseKeyLabelSet(noneKS)).toBe(noneKS); + }); + it("parseKeyLabelSet(invalid) -> throws", () => { + expect(() => parseKeyLabelSet("whatever" as unknown as KeySet)).toThrow(); + }); + }); + }); + + describe("composedKeySet", () => { + const keySet1 = allExceptSome([1, 2]); + const keySetSerialized1 = { + type: KeySetTypes.allExceptSome, + elements: [1, 2], + }; + const keySet2 = some([1, 2, 3, 4]); + const keySetSerialized2 = { + type: KeySetTypes.some, + elements: [1, 2, 3, 4], + }; + + const composedSerialized: KeySetSerialized[] = [keySetSerialized1, keySetSerialized2]; + const composedKeySet = composedKeySetFrom([keySet1, keySet2]); + + describe("isComposedKeySetSerialized()", () => { + it("isComposedKeySetSerialized(composedSerialized) -> OK", () => { + expect(isComposedKeySetSerialized(composedSerialized)).toBeTruthy(); + }); + it("isComposedKeySetSerialized([]) -> OK", () => { + expect(isComposedKeySetSerialized([])).toBeTruthy(); + }); + it("isComposedKeySetSerialized(keySet) -> NOPE", () => { + expect(isComposedKeySetSerialized(keySet1)).toBeFalsy(); + }); + it("isComposedKeySetSerialized(keySetSerialized) -> NOPE", () => { + expect(isComposedKeySetSerialized(keySetSerialized1)).toBeFalsy(); + }); + it("isComposedKeySetSerialized(composedKeySet) -> NOPE", () => { + expect(isComposedKeySetSerialized(composedKeySet)).toBeFalsy(); + }); + it("isComposedKeySetSerialized(composedKeySet) -> NOPE", () => { + expect(isComposedKeySetSerialized(keySet1)).toBeFalsy(); + }); + }); + + describe("serializeComposedKeySet()", () => { + it("serializeComposedKeySet(composedSerialized) -> return same", () => { + expect(serializeComposedKeySet(composedSerialized)).toBe(composedSerialized); + }); + it("serializeComposedKeySet(composedKeySet) -> serialize each", () => { + expect(serializeComposedKeySet(composedKeySet)).toEqual(composedSerialized); + }); + it("composedKeySet.serialized() -> serialize each", () => { + expect(composedKeySet.serialized()).toEqual(composedSerialized); + }); + }); + + describe("parseComposedKeySet()", () => { + it("parseComposedKeySet() with key sets", () => { + expect(parseComposedKeySet(composedSerialized)).toEqual(composedKeySet); + }); + it("parseComposedKeySet() with empty list", () => { + expect(parseComposedKeySet([])).toEqual(composedKeySetFrom([])); + }); + it("parseComposedKeySet() with an already composedKeySet", () => { + expect(parseComposedKeySet(composedKeySet)).toBe(composedKeySet); + }); + + it("parseComposedKeySet() with empty list", () => { + expect(() => parseComposedKeySet([{ invalid: "stuff" }] as unknown as ComposedKeySetSerialized)).toThrow(); + }); + }); + }); }); diff --git a/src/lib/key-set.ts b/src/lib/key-set.ts index b57978e..30f13e9 100644 --- a/src/lib/key-set.ts +++ b/src/lib/key-set.ts @@ -1,10 +1,16 @@ import { + ComposedKeyLabelSetSerialized, + ComposedKeySetSerialized, Key, KeyLabelSet, + KeyLabelSetAll, + KeyLabelSetAllExceptSome, KeyLabelSetAllExceptSomeSerialized, KeyLabelSetAllSerialized, + KeyLabelSetNone, KeyLabelSetNoneSerialized, KeyLabelSetSerialized, + KeyLabelSetSome, KeyLabelSetSomeSerialized, KeySet, KeySetAllExceptSomeSerialized, @@ -19,6 +25,7 @@ import { isKeySetAllExceptSome, isKeySetNone, isKeySetSome, + isKeySetType, isValidKey, } from "./key-set/-base"; import { KeySetAll, all, allKeySet } from "./key-set/all"; @@ -46,12 +53,18 @@ import { InvalidEmptySetError } from "./key-set/invalid-empty-set-error"; import { InvalidKeySetError } from "./key-set/invalid-key-set-error"; import { KeySetNone, none, noneKeySet } from "./key-set/none"; import { + isComposedKeySetSerialized, + isKeyLabelSetSerialized, isKeySetAllExceptSomeSerialized, isKeySetAllSerialized, isKeySetNoneSerialized, isKeySetSerialized, isKeySetSomeSerialized, + parseComposedKeySet, + parseKeyLabelSet, parseKeySet, + serializeComposedKeySet, + serializeKeyLabelSet, serializeKeySet, } from "./key-set/serialize"; import { KeySetSome, some, someForced, someKeySet, someKeySetForced } from "./key-set/some"; @@ -77,10 +90,16 @@ export { composedKeySetFrom, // types and classes ComposedKeySet, + ComposedKeySetSerialized, ComposedKeyLabelSet, + ComposedKeyLabelSetSerialized, Key, KeySet, KeyLabelSet, + KeyLabelSetAll, + KeyLabelSetNone, + KeyLabelSetSome, + KeyLabelSetAllExceptSome, KeySetAll, KeySetAllExceptSome, KeySetNone, @@ -104,6 +123,10 @@ export { // serialize functions serializeKeySet, parseKeySet, + parseComposedKeySet, + serializeComposedKeySet, + parseKeyLabelSet, + serializeKeyLabelSet, // set utils setByKeys, sortKeys, @@ -124,6 +147,9 @@ export { isKeySetNoneSerialized, isKeySetSomeSerialized, isKeySetAllExceptSomeSerialized, + isKeySetType, + isComposedKeySetSerialized, + isKeyLabelSetSerialized, // utils isValidKey, isKeyLabel, diff --git a/src/lib/key-set/-base.ts b/src/lib/key-set/-base.ts index 78b33b1..b0dde70 100644 --- a/src/lib/key-set/-base.ts +++ b/src/lib/key-set/-base.ts @@ -14,11 +14,16 @@ export function isValidKey(x: unknown): x is Key { export type KeySet = KeySetAll | KeySetNone | KeySetSome | KeySetAllExceptSome; +export type KeyLabelSetAll = KeySetAll>; +export type KeyLabelSetNone = KeySetNone>; +export type KeyLabelSetSome = KeySetSome>; +export type KeyLabelSetAllExceptSome = KeySetAllExceptSome>; + export type KeyLabelSet = - | KeySetAll> - | KeySetNone> - | KeySetSome> - | KeySetAllExceptSome>; + | KeyLabelSetAll + | KeyLabelSetNone + | KeyLabelSetSome + | KeyLabelSetAllExceptSome; /** * one type for each of the 4 sets @@ -32,6 +37,12 @@ export enum KeySetTypes { export type KeySetTypesEnumValues = "ALL" | "ALL_EXCEPT_SOME" | "NONE" | "SOME"; +export const KEY_SET_TYPES = Object.values(KeySetTypes).sort(); + +export function isKeySetType(x: unknown): x is KeySetTypes { + return KEY_SET_TYPES.includes(x as KeySetTypes); +} + export type KeySetAllSerialized = | { type: KeySetTypes.all } | { type: KeySetTypes.all; elements: EmptyArray }; @@ -82,6 +93,9 @@ export type KeyLabelSetSerialized = | KeyLabelSetSomeSerialized | KeyLabelSetAllExceptSomeSerialized; +export type ComposedKeySetSerialized = Array>; +export type ComposedKeyLabelSetSerialized = Array; + export interface IKeySetClass { /** * returns the KeySetType that defines this class @@ -199,3 +213,12 @@ export function isKeySet(x: unknown): x is KeySet; export function isKeySet(x: unknown): x is KeySet { return isKeySetAll(x) || isKeySetNone(x) || isKeySetSome(x) || isKeySetAllExceptSome(x); } + +export function isKeyLabelSet( + x: KeySet | KeySetSerialized | KeyLabelSet | KeyLabelSetSerialized, +): x is KeyLabelSet; +export function isKeyLabelSet(x: unknown): x is KeyLabelSet; +export function isKeyLabelSet(x: unknown): x is KeyLabelSet { + if (!isKeySet(x)) return false; + return x.elementsList.every(isKeyLabel); +} diff --git a/src/lib/key-set/composed.ts b/src/lib/key-set/composed.ts index 44d9bdd..b9f2118 100644 --- a/src/lib/key-set/composed.ts +++ b/src/lib/key-set/composed.ts @@ -1,7 +1,7 @@ import { uniqWith } from "es-toolkit"; import { sortBy } from "es-toolkit/compat"; import type { IKeyLabel } from "../util/object-utils"; -import type { Key, KeySet } from "./-base"; +import type { ComposedKeySetSerialized, Key, KeySet } from "./-base"; import { INSPECT } from "./-is-node-env"; import { KeySetAll, all } from "./all"; import { KeySetAllExceptSome } from "./all-except-some"; @@ -219,6 +219,14 @@ export class ComposedKeySet { compactIntersect(): ComposedKeySet { return compactWith(this.list, (list) => list.reduce((acc, x) => acc.intersect(x), new KeySetAll())); } + + /** + * returns an array with the serialized version of each of the key sets of this composed key set + * @returns the serialized version of the composed key set + */ + serialized(): ComposedKeySetSerialized { + return this.list.map((x) => x.serialized()); + } } export function isComposedKeySet(x: ComposedKeySet): x is ComposedKeySet; diff --git a/src/lib/key-set/serialize.ts b/src/lib/key-set/serialize.ts index 10c22fb..f43d8e5 100644 --- a/src/lib/key-set/serialize.ts +++ b/src/lib/key-set/serialize.ts @@ -1,6 +1,17 @@ -import { isObject } from "../util/object-utils"; +import { type IKeyLabel, isKeyLabel, isObject } from "../util/object-utils"; import { + type ComposedKeySetSerialized, type Key, + type KeyLabelSet, + type KeyLabelSetAll, + type KeyLabelSetAllExceptSome, + type KeyLabelSetAllExceptSomeSerialized, + type KeyLabelSetAllSerialized, + type KeyLabelSetNone, + type KeyLabelSetNoneSerialized, + type KeyLabelSetSerialized, + type KeyLabelSetSome, + type KeyLabelSetSomeSerialized, type KeySet, type KeySetAllExceptSomeSerialized, type KeySetAllSerialized, @@ -8,10 +19,13 @@ import { type KeySetSerialized, type KeySetSomeSerialized, KeySetTypes, + isKeyLabelSet, isKeySet, + isKeySetType, } from "./-base"; import { type KeySetAll, all } from "./all"; import { type KeySetAllExceptSome, allExceptSome } from "./all-except-some"; +import { type ComposedKeySet, composedKeySetFrom, isComposedKeySet } from "./composed"; import { InvalidKeySetError } from "./invalid-key-set-error"; import { type KeySetNone, none } from "./none"; import { type KeySetSome, some } from "./some"; @@ -20,12 +34,12 @@ import { type KeySetSome, some } from "./some"; * @hidden * @internal */ -function hasShapeOfSerialized(given: unknown): given is { type: string; elements?: Key[] } { +function hasShapeOfSerialized(given: unknown): given is { type: KeySetTypes; elements?: Key[] } { if (!isObject(given)) return false; if (given.elements && !Array.isArray(given.elements)) return false; - return typeof given.type === "string"; + return isKeySetType(given.type); } /** @@ -82,6 +96,44 @@ export function isKeySetAllExceptSomeSerialized(given: unknown): given is KeySet return isKeySetElementsSerialized(given) && given.type === KeySetTypes.allExceptSome; } +export function isKeyLabelSetSerialized(given: unknown): given is KeyLabelSetSerialized { + if (isKeySetElementsSerialized(given)) { + return given.elements.every((x) => isKeyLabel(x)); + } + + return isKeySetAllSerialized(given) || isKeySetNoneSerialized(given); +} + +export function isComposedKeySetSerialized( + x: ComposedKeySetSerialized, +): x is ComposedKeySetSerialized; +export function isComposedKeySetSerialized(x: unknown): x is ComposedKeySetSerialized; +export function isComposedKeySetSerialized(x: unknown): x is ComposedKeySetSerialized { + if (!x || !Array.isArray(x)) return false; + + return x.every((y) => isKeySetSerialized(y)); +} + +export function serializeComposedKeySet( + x: ComposedKeySet | ComposedKeySetSerialized, +): ComposedKeySetSerialized { + if (isComposedKeySetSerialized(x)) return x; + + return x.list.map((y) => serializeKeySet(y)); +} + +export function serializeKeySet( + x: KeyLabelSetAllSerialized | KeyLabelSetAll, +): KeyLabelSetAllSerialized; +export function serializeKeySet( + x: KeyLabelSetNoneSerialized | KeyLabelSetNone, +): KeyLabelSetNoneSerialized; +export function serializeKeySet( + x: KeyLabelSetSomeSerialized | KeyLabelSetSome, +): KeyLabelSetSomeSerialized; +export function serializeKeySet( + x: KeyLabelSetAllExceptSomeSerialized | KeyLabelSetAllExceptSome, +): KeyLabelSetAllExceptSomeSerialized; export function serializeKeySet(x: KeySetAllSerialized | KeySetAll): KeySetAllSerialized; export function serializeKeySet(x: KeySetNoneSerialized | KeySetNone): KeySetNoneSerialized; export function serializeKeySet(x: KeySetSomeSerialized | KeySetSome): KeySetSomeSerialized; @@ -99,6 +151,20 @@ export function serializeKeySet(keySet: KeySet | KeySetSeriali throw new InvalidKeySetError(`keySet expected, given ${JSON.stringify(keySet)}`); } +export const serializeKeyLabelSet = serializeKeySet; + +export function parseComposedKeySet( + x: ComposedKeySetSerialized | ComposedKeySet, +): ComposedKeySet { + if (isComposedKeySet(x)) return x; + + if (!isComposedKeySetSerialized(x)) { + throw new InvalidKeySetError(`composedKeySetSerialized expected, given ${JSON.stringify(x)}`); + } + + return composedKeySetFrom(x.map((y) => parseKeySet(y))); +} + export function parseKeySet(x: KeySetAllSerialized | KeySetAll): KeySetAll; export function parseKeySet(x: KeySetNoneSerialized | KeySetNone): KeySetNone; export function parseKeySet(x: KeySetSomeSerialized | KeySetSome): KeySetSome; @@ -120,3 +186,35 @@ export function parseKeySet(x: KeySetSerialized | KeySet): } return allExceptSome((x as KeySetAllExceptSomeSerialized).elements); } + +export function parseKeyLabelSet( + x: KeyLabelSetAllSerialized | KeyLabelSetAll, +): KeyLabelSetAll; +export function parseKeyLabelSet( + x: KeyLabelSetNoneSerialized | KeyLabelSetNone, +): KeyLabelSetNone; +export function parseKeyLabelSet( + x: KeyLabelSetSomeSerialized | KeyLabelSetSome, +): KeyLabelSetSome; +export function parseKeyLabelSet( + x: KeyLabelSetAllExceptSomeSerialized | KeyLabelSetAllExceptSome, +): KeyLabelSetAllExceptSome; +export function parseKeyLabelSet( + x: KeyLabelSetSerialized | KeyLabelSet, +): KeyLabelSet; +export function parseKeyLabelSet( + x: KeyLabelSetSerialized | KeyLabelSet, +): KeyLabelSet { + if (isKeyLabelSet(x)) return x; + + if (!isKeyLabelSetSerialized(x)) { + throw new InvalidKeySetError(`KeyLabelSetSerialized expected, given ${JSON.stringify(x)}`); + } + + if (x.type === KeySetTypes.all) return all>(); + if (x.type === KeySetTypes.none) return none>(); + if (x.type === KeySetTypes.some) { + return some((x as KeyLabelSetSomeSerialized).elements); + } + return allExceptSome((x as KeyLabelSetAllExceptSomeSerialized).elements); +}