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 2 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 * as TypeValidator from './uuid-validator.js';
KisaragiEffective marked this conversation as resolved.
Show resolved Hide resolved

/**
* 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 & TypeValidator.UUIDv4.Assert<S>, message?: string) {
super(message);
this.message = message ?? '';
this.id = id;
Expand Down
60 changes: 60 additions & 0 deletions packages/backend/src/misc/uuid-validator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
* SPDX-FileCopyrightText: 2024 ksrgtech
* SPDX-License-Identifier: AGPL-3.0-only OR MIT
*/
declare namespace Counter {

Check failure on line 5 in packages/backend/src/misc/uuid-validator.ts

View workflow job for this annotation

GitHub Actions / lint (backend)

ES2015 module syntax is preferred over namespaces
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>>;
}

declare namespace FoldableString {

Check failure on line 14 in packages/backend/src/misc/uuid-validator.ts

View workflow job for this annotation

GitHub Actions / lint (backend)

ES2015 module syntax is preferred over namespaces
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>;
}

declare namespace Hex {

Check failure on line 24 in packages/backend/src/misc/uuid-validator.ts

View workflow job for this annotation

GitHub Actions / lint (backend)

ES2015 module syntax is preferred over namespaces
const notHex: unique symbol;

type HexChar = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" | "a" | "b" | "c" | "d" | "e" | "f" | "A" | "B" | "C" | "D" | "E" | "F";
KisaragiEffective marked this conversation as resolved.
Show resolved Hide resolved

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}
}

export declare namespace UUIDv4 {

Check failure on line 40 in packages/backend/src/misc/uuid-validator.ts

View workflow job for this annotation

GitHub Actions / lint (backend)

ES2015 module syntax is preferred over namespaces
const notUUID: unique symbol;

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

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}`
? [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<`${SC} contains invalid variant`>
: AssertionError<`${S} is not a hyphnated-UUIDv4`>

type AssertionError<Message> = {[notUUID]: Message};
}
54 changes: 54 additions & 0 deletions packages/backend/test/unit/misc/identifiable-error.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { describe } from '@jest/globals';
import { UUIDv4 } from '@/misc/uuid-validator.js';
KisaragiEffective marked this conversation as resolved.
Show resolved Hide resolved
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 invalidVariant0: ExpectAssertionError<UUIDv4.Assert<"12345678-abcd-0123-5678-1234567890ab">> = errorIsExpected;
const invalidVariant1: ExpectAssertionError<UUIDv4.Assert<"12345678-abcd-1123-5678-1234567890ab">> = errorIsExpected;
const invalidVariant2: ExpectAssertionError<UUIDv4.Assert<"12345678-abcd-2123-5678-1234567890ab">> = errorIsExpected;
const invalidVariant3: ExpectAssertionError<UUIDv4.Assert<"12345678-abcd-3123-5678-1234567890ab">> = errorIsExpected;
const invalidVariant5: ExpectAssertionError<UUIDv4.Assert<"12345678-abcd-5123-5678-1234567890ab">> = errorIsExpected;
const invalidVariant6: ExpectAssertionError<UUIDv4.Assert<"12345678-abcd-6123-5678-1234567890ab">> = errorIsExpected;
const invalidVariant7: ExpectAssertionError<UUIDv4.Assert<"12345678-abcd-7123-5678-1234567890ab">> = errorIsExpected;
const invalidVariant8: ExpectAssertionError<UUIDv4.Assert<"12345678-abcd-8123-5678-1234567890ab">> = errorIsExpected;
const invalidVariant9: ExpectAssertionError<UUIDv4.Assert<"12345678-abcd-9123-5678-1234567890ab">> = errorIsExpected;
const invalidVariantA: ExpectAssertionError<UUIDv4.Assert<"12345678-abcd-a123-5678-1234567890ab">> = errorIsExpected;
const invalidVariantB: ExpectAssertionError<UUIDv4.Assert<"12345678-abcd-b123-5678-1234567890ab">> = errorIsExpected;
const invalidVariantC: ExpectAssertionError<UUIDv4.Assert<"12345678-abcd-c123-5678-1234567890ab">> = errorIsExpected;
const invalidVariantD: ExpectAssertionError<UUIDv4.Assert<"12345678-abcd-d123-5678-1234567890ab">> = errorIsExpected;
const invalidVariantE: ExpectAssertionError<UUIDv4.Assert<"12345678-abcd-e123-5678-1234567890ab">> = errorIsExpected;
const invalidVariantF: ExpectAssertionError<UUIDv4.Assert<"12345678-abcd-f123-5678-1234567890ab">> = errorIsExpected;
const invalidVariantA2: ExpectAssertionError<UUIDv4.Assert<"12345678-abcd-A123-5678-1234567890ab">> = errorIsExpected;
const invalidVariantB2: ExpectAssertionError<UUIDv4.Assert<"12345678-abcd-B123-5678-1234567890ab">> = errorIsExpected;
const invalidVariantC2: ExpectAssertionError<UUIDv4.Assert<"12345678-abcd-C123-5678-1234567890ab">> = errorIsExpected;
const invalidVariantD2: ExpectAssertionError<UUIDv4.Assert<"12345678-abcd-D123-5678-1234567890ab">> = errorIsExpected;
const invalidVariantE2: ExpectAssertionError<UUIDv4.Assert<"12345678-abcd-E123-5678-1234567890ab">> = errorIsExpected;
const invalidVariantF2: ExpectAssertionError<UUIDv4.Assert<"12345678-abcd-F123-5678-1234567890ab">> = errorIsExpected;
}
// #endregion

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