Skip to content
This repository has been archived by the owner on Nov 15, 2024. It is now read-only.

refactor: force UUID on IdentifiableError constructor #52

Merged
merged 19 commits into from
Aug 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions packages/backend/src/misc/identifiable-error.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,16 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/

import type { UUIDv4 } from './uuid-validator.js';

/**
* ID付きエラー
*/
export class IdentifiableError extends Error {
export class IdentifiableError<const S extends string> extends Error {
public message: string;
public id: string;
public id: S;

constructor(id: string, message?: string) {
constructor(id: S & UUIDv4.Assert<S>, message?: string) {
super(message);
this.message = message ?? '';
this.id = id;
Expand Down
68 changes: 68 additions & 0 deletions packages/backend/src/misc/uuid-validator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
* SPDX-FileCopyrightText: 2024 ksrgtech
* SPDX-License-Identifier: AGPL-3.0-only OR MIT
*/
// eslint-disable-next-line @typescript-eslint/no-namespace
declare module Counter {
type Atom = [];
type Sequence = Atom[];
type Increase<S extends Sequence> = [...S, Atom];
type Current<S extends Sequence> = S["length"];

type Make<I extends number, C extends Sequence = []> = Current<C> extends I ? C : Make<I, Increase<C>>;
}

// eslint-disable-next-line @typescript-eslint/no-namespace
declare module FoldableString {
type Chars<S extends string, Acc extends string[] = []> = string extends S ? never : S extends `${infer A}${infer B}` ? Chars<B, [...Acc, A]> : Acc

type LengthS<S extends string, Acc extends Counter.Sequence = Counter.Make<0>> = string extends S
? number
: S extends `${infer A}${infer B}`
? LengthS<B, Counter.Increase<Acc>>
: Counter.Current<Acc>;
}

// eslint-disable-next-line @typescript-eslint/no-namespace
declare module Hex {
const notHex: unique symbol;

type HexChar = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" | "a" | "b" | "c" | "d" | "e" | "f";

type Assert0<S extends string, Counter extends Counter.Sequence> = FoldableString.Chars<S>[Counter.Current<Counter>] extends HexChar
? Counter.Current<Counter.Increase<Counter>> extends FoldableString.LengthS<S>
? S
: Assert0<S, Counter.Increase<Counter>>
: AssertionError<`${S}[${Counter.Current<Counter>}] is not hex!`>;

type Assert<S extends string> = Assert0<S, Counter.Make<0>>;

type AssertionError<Message> = {[notHex]: Message}
}

// eslint-disable-next-line @typescript-eslint/no-namespace
export declare module UUIDv4 {
const notUUID: unique symbol;

type AssertHex<S extends string> = Hex.Assert<S>;
type LengthS<S extends string> = FoldableString.LengthS<S>;

type AcceptedVariant = '8' | '9' | 'a' | 'b';

export type Assert<S extends string> = string extends S
// not a literal, skipping every assertion
? S
: S extends `${infer SA}-${infer SB}-${infer SC}-${infer SD}-${infer SE}`
? SC extends `4${string}`
? SD extends `${AcceptedVariant}${string}`
? [LengthS<SA>, LengthS<SB>, LengthS<SC>, LengthS<SD>, LengthS<SE>] extends [8, 4, 4, 4, 12]
? [AssertHex<SA>, AssertHex<SB>, AssertHex<SC>, AssertHex<SD>, AssertHex<SE>] extends [string, string, string, string, string]
? S
: AssertionError<[AssertHex<SA>, AssertHex<SB>, AssertHex<SC>, AssertHex<SD>, AssertHex<SE>]>
: AssertionError<`component lengths are required to be [8, 4, 4, 4, 12], but are [${LengthS<SA>}, ${LengthS<SB>}, ${LengthS<SC>}, ${LengthS<SD>}, ${LengthS<SE>}]`>
: AssertionError<`${SD} contains invalid, or reserved variant`>
: AssertionError<`${SC} contains invalid version`>
: AssertionError<`${S} is not a hyphnated-UUIDv4`>

type AssertionError<Message> = {[notUUID]: Message};
}
94 changes: 94 additions & 0 deletions packages/backend/test/unit/misc/identifiable-error.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/*
* SPDX-FileCopyrightText: 2024 ksrgtech
* SPDX-License-Identifier: AGPL-3.0-only or MIT
*/
import { describe } from '@jest/globals';
import type { UUIDv4 } from '@/misc/uuid-validator.js';
import { IdentifiableError } from '@/misc/identifiable-error.js';

describe('identifiable-error', () => {
test('type check', () => {
const errorIsExpected = Symbol();
type ExpectAssertionError<A> = A extends object ? typeof errorIsExpected : never;
// #region invalid UUID type test
{
const empty: ExpectAssertionError<UUIDv4.Assert<"">> = errorIsExpected;
const tooFewComponents1: ExpectAssertionError<UUIDv4.Assert<"a">> = errorIsExpected;
const tooFewComponents2: ExpectAssertionError<UUIDv4.Assert<"a-b">> = errorIsExpected;
const tooFewComponents3: ExpectAssertionError<UUIDv4.Assert<"a-b-c">> = errorIsExpected;
const tooFewComponents4: ExpectAssertionError<UUIDv4.Assert<"a-b-c-d">> = errorIsExpected;
const excessComponents: ExpectAssertionError<UUIDv4.Assert<"a-b-c-d-e-f">> = errorIsExpected;
const tooShortComponents: ExpectAssertionError<UUIDv4.Assert<"a-b-c-d-e">> = errorIsExpected;
const tooLongComponents: ExpectAssertionError<UUIDv4.Assert<"aaaa-bbbbbbbb-4ccccccc-dddddddd-eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee">> = errorIsExpected;

const invalidVersion0: ExpectAssertionError<UUIDv4.Assert<"12345678-abcd-0123-90ab-1234567890ab">> = errorIsExpected;
const invalidVersion1: ExpectAssertionError<UUIDv4.Assert<"12345678-abcd-1123-90ab-1234567890ab">> = errorIsExpected;
const invalidVersion2: ExpectAssertionError<UUIDv4.Assert<"12345678-abcd-2123-90ab-1234567890ab">> = errorIsExpected;
const invalidVersion3: ExpectAssertionError<UUIDv4.Assert<"12345678-abcd-3123-90ab-1234567890ab">> = errorIsExpected;
const invalidVersion5: ExpectAssertionError<UUIDv4.Assert<"12345678-abcd-5123-90ab-1234567890ab">> = errorIsExpected;
const invalidVersion6: ExpectAssertionError<UUIDv4.Assert<"12345678-abcd-6123-90ab-1234567890ab">> = errorIsExpected;
const invalidVersion7: ExpectAssertionError<UUIDv4.Assert<"12345678-abcd-7123-90ab-1234567890ab">> = errorIsExpected;
const invalidVersion8: ExpectAssertionError<UUIDv4.Assert<"12345678-abcd-8123-90ab-1234567890ab">> = errorIsExpected;
const invalidVersion9: ExpectAssertionError<UUIDv4.Assert<"12345678-abcd-9123-90ab-1234567890ab">> = errorIsExpected;
const invalidVersionA: ExpectAssertionError<UUIDv4.Assert<"12345678-abcd-a123-90ab-1234567890ab">> = errorIsExpected;
const invalidVersionB: ExpectAssertionError<UUIDv4.Assert<"12345678-abcd-b123-90ab-1234567890ab">> = errorIsExpected;
const invalidVersionC: ExpectAssertionError<UUIDv4.Assert<"12345678-abcd-c123-90ab-1234567890ab">> = errorIsExpected;
const invalidVersionD: ExpectAssertionError<UUIDv4.Assert<"12345678-abcd-d123-90ab-1234567890ab">> = errorIsExpected;
const invalidVersionE: ExpectAssertionError<UUIDv4.Assert<"12345678-abcd-e123-90ab-1234567890ab">> = errorIsExpected;
const invalidVersionF: ExpectAssertionError<UUIDv4.Assert<"12345678-abcd-f123-90ab-1234567890ab">> = errorIsExpected;
const invalidVersionA2: ExpectAssertionError<UUIDv4.Assert<"12345678-abcd-A123-90ab-1234567890ab">> = errorIsExpected;
const invalidVersionB2: ExpectAssertionError<UUIDv4.Assert<"12345678-abcd-B123-90ab-1234567890ab">> = errorIsExpected;
const invalidVersionC2: ExpectAssertionError<UUIDv4.Assert<"12345678-abcd-C123-90ab-1234567890ab">> = errorIsExpected;
const invalidVersionD2: ExpectAssertionError<UUIDv4.Assert<"12345678-abcd-D123-90ab-1234567890ab">> = errorIsExpected;
const invalidVersionE2: ExpectAssertionError<UUIDv4.Assert<"12345678-abcd-E123-90ab-1234567890ab">> = errorIsExpected;
const invalidVersionF2: ExpectAssertionError<UUIDv4.Assert<"12345678-abcd-F123-90ab-1234567890ab">> = errorIsExpected;

const invalidVariant0: ExpectAssertionError<UUIDv4.Assert<"12345678-abcd-4123-00ab-1234567890ab">> = errorIsExpected;
const invalidVariant1: ExpectAssertionError<UUIDv4.Assert<"12345678-abcd-4123-10ab-1234567890ab">> = errorIsExpected;
const invalidVariant2: ExpectAssertionError<UUIDv4.Assert<"12345678-abcd-4123-20ab-1234567890ab">> = errorIsExpected;
const invalidVariant3: ExpectAssertionError<UUIDv4.Assert<"12345678-abcd-4123-30ab-1234567890ab">> = errorIsExpected;
const invalidVariant4: ExpectAssertionError<UUIDv4.Assert<"12345678-abcd-4123-40ab-1234567890ab">> = errorIsExpected;
const invalidVariant5: ExpectAssertionError<UUIDv4.Assert<"12345678-abcd-4123-50ab-1234567890ab">> = errorIsExpected;
const invalidVariant6: ExpectAssertionError<UUIDv4.Assert<"12345678-abcd-4123-60ab-1234567890ab">> = errorIsExpected;
const invalidVariant7: ExpectAssertionError<UUIDv4.Assert<"12345678-abcd-4123-70ab-1234567890ab">> = errorIsExpected;
const invalidVariantC: ExpectAssertionError<UUIDv4.Assert<"12345678-abcd-4123-c0ab-1234567890ab">> = errorIsExpected;
const invalidVariantD: ExpectAssertionError<UUIDv4.Assert<"12345678-abcd-4123-d0ab-1234567890ab">> = errorIsExpected;
const invalidVariantE: ExpectAssertionError<UUIDv4.Assert<"12345678-abcd-4123-e0ab-1234567890ab">> = errorIsExpected;
const invalidVariantF: ExpectAssertionError<UUIDv4.Assert<"12345678-abcd-4123-f0ab-1234567890ab">> = errorIsExpected;
const invalidVariantC2: ExpectAssertionError<UUIDv4.Assert<"12345678-abcd-4123-C0ab-1234567890ab">> = errorIsExpected;
const invalidVariantD2: ExpectAssertionError<UUIDv4.Assert<"12345678-abcd-4123-D0ab-1234567890ab">> = errorIsExpected;
const invalidVariantE2: ExpectAssertionError<UUIDv4.Assert<"12345678-abcd-4123-E0ab-1234567890ab">> = errorIsExpected;
const invalidVariantF2: ExpectAssertionError<UUIDv4.Assert<"12345678-abcd-4123-F0ab-1234567890ab">> = errorIsExpected;
}
// #endregion

// #region positive type test
{
{
const v = "12345678-abcd-4123-890a-1234567890ab";
const a: UUIDv4.Assert<typeof v> = v;
const i = new IdentifiableError<typeof a>(a);
console.assert(i.id == a);
}
{
const v = "12345678-abcd-4123-990a-1234567890ab";
const a: UUIDv4.Assert<typeof v> = v;
const i = new IdentifiableError<typeof a>(a);
console.assert(i.id == a);
}
{
const v = "12345678-abcd-4123-a90a-1234567890ab";
const a: UUIDv4.Assert<typeof v> = v;
const i = new IdentifiableError<typeof a>(a);
console.assert(i.id == a);
}
{
const v = "12345678-abcd-4123-b90a-1234567890ab";
const a: UUIDv4.Assert<typeof v> = v;
const i = new IdentifiableError<typeof a>(a);
console.assert(i.id == a);
}
}
// #endregion
})
});
Loading