Skip to content

Commit b2dfda8

Browse files
committed
Add explicitly defined error types
1 parent 5bf77d1 commit b2dfda8

File tree

5 files changed

+78
-42
lines changed

5 files changed

+78
-42
lines changed

typeid/typeid-js/src/typeid.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
getType,
88
fromString,
99
} from "./unboxed/typeid";
10+
import { TypeIDConversionError } from "./unboxed/error";
1011

1112
export class TypeID<const T extends string> {
1213
constructor(private prefix: T, private suffix: string = "") {
@@ -27,9 +28,7 @@ export class TypeID<const T extends string> {
2728
public asType<const U extends string>(prefix: U): TypeID<U> {
2829
const self = this as unknown as TypeID<U>;
2930
if (self.prefix !== prefix) {
30-
throw new Error(
31-
`Cannot convert TypeID of type ${self.prefix} to type ${prefix}`
32-
);
31+
throw new TypeIDConversionError(self.prefix, prefix);
3332
}
3433
return self;
3534
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
export class InvalidPrefixError extends Error {
2+
constructor(prefix: string) {
3+
super(`Invalid prefix "${prefix}". Must be at most 63 ASCII letters [a-z_]`);
4+
this.name = "InvalidPrefixError";
5+
}
6+
}
7+
8+
export class PrefixMismatchError extends Error {
9+
constructor(expected: string, actual: string) {
10+
super(`Invalid TypeId. Prefix mismatch. Expected ${expected}, got ${actual}`);
11+
this.name = "PrefixMismatchError";
12+
}
13+
}
14+
15+
export class EmptyPrefixError extends Error {
16+
constructor(typeId: string) {
17+
super(`Invalid TypeId. Prefix cannot be empty when there's a separator: ${typeId}`);
18+
this.name = "EmptyPrefixError";
19+
}
20+
}
21+
22+
export class InvalidSuffixLengthError extends Error {
23+
constructor(length: number) {
24+
super(`Invalid length. Suffix should have 26 characters, got ${length}`);
25+
this.name = "InvalidSuffixLengthError";
26+
}
27+
}
28+
29+
export class InvalidSuffixCharacterError extends Error {
30+
constructor(firstChar: string) {
31+
super(`Invalid suffix. First character "${firstChar}" must be in the range [0-7]`);
32+
this.name = "InvalidSuffixCharacterError";
33+
}
34+
}
35+
36+
export class TypeIDConversionError extends Error {
37+
constructor(actualPrefix: string, expectedPrefix: string) {
38+
super(`Cannot convert TypeID of type ${actualPrefix} to type ${expectedPrefix}`);
39+
this.name = "TypeIDConversionError";
40+
}
41+
}

typeid/typeid-js/src/unboxed/typeid.ts

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,13 @@ import { stringify, v7 } from "uuid";
22
import { parseUUID } from "../parse_uuid";
33
import { encode, decode } from "../base32";
44
import { isValidPrefix } from "../prefix";
5+
import {
6+
EmptyPrefixError,
7+
InvalidPrefixError,
8+
InvalidSuffixCharacterError,
9+
InvalidSuffixLengthError,
10+
PrefixMismatchError,
11+
} from "./error";
512

613
export type TypeId<T> = string & { __type: T };
714

@@ -10,7 +17,7 @@ export function typeidUnboxed<T extends string>(
1017
suffix: string = ""
1118
): TypeId<T> {
1219
if (!isValidPrefix(prefix)) {
13-
throw new Error("Invalid prefix. Must be at most 63 ascii letters [a-z_]");
20+
throw new InvalidPrefixError(prefix);
1421
}
1522

1623
let finalSuffix: string;
@@ -23,15 +30,11 @@ export function typeidUnboxed<T extends string>(
2330
}
2431

2532
if (finalSuffix.length !== 26) {
26-
throw new Error(
27-
`Invalid length. Suffix should have 26 characters, got ${finalSuffix.length}`
28-
);
33+
throw new InvalidSuffixLengthError(finalSuffix.length);
2934
}
3035

3136
if (finalSuffix[0] > "7") {
32-
throw new Error(
33-
"Invalid suffix. First character must be in the range [0-7]"
34-
);
37+
throw new InvalidSuffixCharacterError(finalSuffix[0]);
3538
}
3639

3740
// Validate the suffix by decoding it. If it's invalid, an error will be thrown.
@@ -73,20 +76,16 @@ export function fromString<T extends string>(
7376
s = typeId.substring(underscoreIndex + 1);
7477

7578
if (!p) {
76-
throw new Error(
77-
`Invalid TypeId. Prefix cannot be empty when there's a separator: ${typeId}`
78-
);
79+
throw new EmptyPrefixError(typeId);
7980
}
8081
}
8182

8283
if (!s) {
83-
throw new Error(`Invalid TypeId. Suffix cannot be empty`);
84+
throw new InvalidSuffixLengthError(0);
8485
}
8586

8687
if (prefix && p !== prefix) {
87-
throw new Error(
88-
`Invalid TypeId. Prefix mismatch. Expected ${prefix}, got ${p}`
89-
);
88+
throw new PrefixMismatchError(prefix, p);
9089
}
9190

9291
return typeidUnboxed(p, s);

typeid/typeid-js/test/typeid.test.ts

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,12 @@ import { describe, expect, it } from "@jest/globals";
33
import { typeid, TypeID } from "../src/typeid";
44
import validJson from "./valid";
55
import invalidJson from "./invalid";
6+
import {
7+
InvalidPrefixError,
8+
InvalidSuffixCharacterError,
9+
InvalidSuffixLengthError,
10+
PrefixMismatchError,
11+
} from "../src/unboxed/error";
612

713
describe("TypeID", () => {
814
describe("constructor", () => {
@@ -28,15 +34,11 @@ describe("TypeID", () => {
2834
it("should throw an error if prefix is not lowercase", () => {
2935
expect(() => {
3036
typeid("TEST", "00041061050r3gg28a1c60t3gf");
31-
}).toThrowError(
32-
"Invalid prefix. Must be at most 63 ascii letters [a-z_]"
33-
);
37+
}).toThrowError(new InvalidPrefixError("TEST"));
3438

3539
expect(() => {
3640
typeid(" ", "00041061050r3gg28a1c60t3gf");
37-
}).toThrowError(
38-
"Invalid prefix. Must be at most 63 ascii letters [a-z_]"
39-
);
41+
}).toThrowError(new InvalidPrefixError(" "));
4042
});
4143

4244
it("should throw an error if suffix length is not 26", () => {
@@ -111,29 +113,25 @@ describe("TypeID", () => {
111113

112114
expect(() => {
113115
TypeID.fromString(invalidStr);
114-
}).toThrowError(
115-
new Error(`Invalid suffix. First character must be in the range [0-7]`)
116-
);
116+
}).toThrowError(new InvalidSuffixCharacterError("u"));
117117
});
118118
it("should throw an error with wrong prefix", () => {
119119
const str = "prefix_00041061050r3gg28a1c60t3gf";
120120
expect(() => {
121121
TypeID.fromString(str, "wrong");
122-
}).toThrowError(
123-
new Error(`Invalid TypeId. Prefix mismatch. Expected wrong, got prefix`)
124-
);
122+
}).toThrowError(new PrefixMismatchError("wrong", "prefix"));
125123
});
126124
it("should throw an error for empty TypeId string", () => {
127125
const invalidStr = "";
128126
expect(() => {
129127
TypeID.fromString(invalidStr);
130-
}).toThrowError(new Error(`Invalid TypeId. Suffix cannot be empty`));
128+
}).toThrowError(new InvalidSuffixLengthError(0));
131129
});
132130
it("should throw an error for TypeId string with empty suffix", () => {
133131
const invalidStr = "prefix_";
134132
expect(() => {
135133
TypeID.fromString(invalidStr);
136-
}).toThrowError(new Error(`Invalid TypeId. Suffix cannot be empty`));
134+
}).toThrowError(new InvalidSuffixLengthError(0));
137135
});
138136
});
139137

typeid/typeid-js/test/typeid_unboxed.test.ts

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@ import {
1212
} from "../src/unboxed/typeid";
1313
import validJson from "./valid";
1414
import invalidJson from "./invalid";
15+
import {
16+
InvalidPrefixError,
17+
InvalidSuffixCharacterError,
18+
InvalidSuffixLengthError,
19+
} from "../src/unboxed/error";
1520

1621
describe("TypeId Functions", () => {
1722
describe("typeidUnboxed", () => {
@@ -38,15 +43,11 @@ describe("TypeId Functions", () => {
3843
it("should throw an error if prefix is not lowercase", () => {
3944
expect(() => {
4045
typeidUnboxed("TEST", "00041061050r3gg28a1c60t3gf");
41-
}).toThrowError(
42-
"Invalid prefix. Must be at most 63 ascii letters [a-z_]"
43-
);
46+
}).toThrowError(new InvalidPrefixError("TEST"));
4447

4548
expect(() => {
4649
typeidUnboxed(" ", "00041061050r3gg28a1c60t3gf");
47-
}).toThrowError(
48-
"Invalid prefix. Must be at most 63 ascii letters [a-z_]"
49-
);
50+
}).toThrowError(new InvalidPrefixError(" "));
5051
});
5152

5253
it("should throw an error if suffix length is not 26", () => {
@@ -80,25 +81,23 @@ describe("TypeId Functions", () => {
8081

8182
expect(() => {
8283
fromString(invalidStr);
83-
}).toThrowError(
84-
new Error(`Invalid suffix. First character must be in the range [0-7]`)
85-
);
84+
}).toThrowError(new InvalidSuffixCharacterError("u"));
8685
});
8786

8887
it("should throw an error for empty TypeId string", () => {
8988
const invalidStr = "";
9089

9190
expect(() => {
9291
fromString(invalidStr);
93-
}).toThrowError(new Error(`Invalid TypeId. Suffix cannot be empty`));
92+
}).toThrowError(new InvalidSuffixLengthError(0));
9493
});
9594

9695
it("should throw an error for TypeId string with empty suffix", () => {
9796
const invalidStr = "prefix_";
9897

9998
expect(() => {
10099
fromString(invalidStr);
101-
}).toThrowError(new Error(`Invalid TypeId. Suffix cannot be empty`));
100+
}).toThrowError(new InvalidSuffixLengthError(0));
102101
});
103102
});
104103

0 commit comments

Comments
 (0)