diff --git a/typeid/typeid-js/src/typeid.ts b/typeid/typeid-js/src/typeid.ts index 67144d07..df9e796a 100644 --- a/typeid/typeid-js/src/typeid.ts +++ b/typeid/typeid-js/src/typeid.ts @@ -7,6 +7,7 @@ import { getType, fromString, } from "./unboxed/typeid"; +import { TypeIDConversionError } from "./unboxed/error"; export class TypeID { constructor(private prefix: T, private suffix: string = "") { @@ -27,9 +28,7 @@ export class TypeID { public asType(prefix: U): TypeID { const self = this as unknown as TypeID; if (self.prefix !== prefix) { - throw new Error( - `Cannot convert TypeID of type ${self.prefix} to type ${prefix}` - ); + throw new TypeIDConversionError(self.prefix, prefix); } return self; } diff --git a/typeid/typeid-js/src/unboxed/error.ts b/typeid/typeid-js/src/unboxed/error.ts new file mode 100644 index 00000000..36b073cd --- /dev/null +++ b/typeid/typeid-js/src/unboxed/error.ts @@ -0,0 +1,41 @@ +export class InvalidPrefixError extends Error { + constructor(prefix: string) { + super(`Invalid prefix "${prefix}". Must be at most 63 ASCII letters [a-z_]`); + this.name = "InvalidPrefixError"; + } +} + +export class PrefixMismatchError extends Error { + constructor(expected: string, actual: string) { + super(`Invalid TypeId. Prefix mismatch. Expected ${expected}, got ${actual}`); + this.name = "PrefixMismatchError"; + } +} + +export class EmptyPrefixError extends Error { + constructor(typeId: string) { + super(`Invalid TypeId. Prefix cannot be empty when there's a separator: ${typeId}`); + this.name = "EmptyPrefixError"; + } +} + +export class InvalidSuffixLengthError extends Error { + constructor(length: number) { + super(`Invalid length. Suffix should have 26 characters, got ${length}`); + this.name = "InvalidSuffixLengthError"; + } +} + +export class InvalidSuffixCharacterError extends Error { + constructor(firstChar: string) { + super(`Invalid suffix. First character "${firstChar}" must be in the range [0-7]`); + this.name = "InvalidSuffixCharacterError"; + } +} + +export class TypeIDConversionError extends Error { + constructor(actualPrefix: string, expectedPrefix: string) { + super(`Cannot convert TypeID of type ${actualPrefix} to type ${expectedPrefix}`); + this.name = "TypeIDConversionError"; + } +} diff --git a/typeid/typeid-js/src/unboxed/typeid.ts b/typeid/typeid-js/src/unboxed/typeid.ts index c77a0c27..3f4e1bc6 100644 --- a/typeid/typeid-js/src/unboxed/typeid.ts +++ b/typeid/typeid-js/src/unboxed/typeid.ts @@ -2,6 +2,13 @@ import { stringify, v7 } from "uuid"; import { parseUUID } from "../parse_uuid"; import { encode, decode } from "../base32"; import { isValidPrefix } from "../prefix"; +import { + EmptyPrefixError, + InvalidPrefixError, + InvalidSuffixCharacterError, + InvalidSuffixLengthError, + PrefixMismatchError, +} from "./error"; export type TypeId = string & { __type: T }; @@ -10,7 +17,7 @@ export function typeidUnboxed( suffix: string = "" ): TypeId { if (!isValidPrefix(prefix)) { - throw new Error("Invalid prefix. Must be at most 63 ascii letters [a-z_]"); + throw new InvalidPrefixError(prefix); } let finalSuffix: string; @@ -23,15 +30,11 @@ export function typeidUnboxed( } if (finalSuffix.length !== 26) { - throw new Error( - `Invalid length. Suffix should have 26 characters, got ${finalSuffix.length}` - ); + throw new InvalidSuffixLengthError(finalSuffix.length); } if (finalSuffix[0] > "7") { - throw new Error( - "Invalid suffix. First character must be in the range [0-7]" - ); + throw new InvalidSuffixCharacterError(finalSuffix[0]); } // Validate the suffix by decoding it. If it's invalid, an error will be thrown. @@ -73,20 +76,16 @@ export function fromString( s = typeId.substring(underscoreIndex + 1); if (!p) { - throw new Error( - `Invalid TypeId. Prefix cannot be empty when there's a separator: ${typeId}` - ); + throw new EmptyPrefixError(typeId); } } if (!s) { - throw new Error(`Invalid TypeId. Suffix cannot be empty`); + throw new InvalidSuffixLengthError(0); } if (prefix && p !== prefix) { - throw new Error( - `Invalid TypeId. Prefix mismatch. Expected ${prefix}, got ${p}` - ); + throw new PrefixMismatchError(prefix, p); } return typeidUnboxed(p, s); diff --git a/typeid/typeid-js/test/typeid.test.ts b/typeid/typeid-js/test/typeid.test.ts index c18b5b2d..f7b31436 100644 --- a/typeid/typeid-js/test/typeid.test.ts +++ b/typeid/typeid-js/test/typeid.test.ts @@ -3,6 +3,12 @@ import { describe, expect, it } from "@jest/globals"; import { typeid, TypeID } from "../src/typeid"; import validJson from "./valid"; import invalidJson from "./invalid"; +import { + InvalidPrefixError, + InvalidSuffixCharacterError, + InvalidSuffixLengthError, + PrefixMismatchError, +} from "../src/unboxed/error"; describe("TypeID", () => { describe("constructor", () => { @@ -28,15 +34,11 @@ describe("TypeID", () => { it("should throw an error if prefix is not lowercase", () => { expect(() => { typeid("TEST", "00041061050r3gg28a1c60t3gf"); - }).toThrowError( - "Invalid prefix. Must be at most 63 ascii letters [a-z_]" - ); + }).toThrowError(new InvalidPrefixError("TEST")); expect(() => { typeid(" ", "00041061050r3gg28a1c60t3gf"); - }).toThrowError( - "Invalid prefix. Must be at most 63 ascii letters [a-z_]" - ); + }).toThrowError(new InvalidPrefixError(" ")); }); it("should throw an error if suffix length is not 26", () => { @@ -111,29 +113,25 @@ describe("TypeID", () => { expect(() => { TypeID.fromString(invalidStr); - }).toThrowError( - new Error(`Invalid suffix. First character must be in the range [0-7]`) - ); + }).toThrowError(new InvalidSuffixCharacterError("u")); }); it("should throw an error with wrong prefix", () => { const str = "prefix_00041061050r3gg28a1c60t3gf"; expect(() => { TypeID.fromString(str, "wrong"); - }).toThrowError( - new Error(`Invalid TypeId. Prefix mismatch. Expected wrong, got prefix`) - ); + }).toThrowError(new PrefixMismatchError("wrong", "prefix")); }); it("should throw an error for empty TypeId string", () => { const invalidStr = ""; expect(() => { TypeID.fromString(invalidStr); - }).toThrowError(new Error(`Invalid TypeId. Suffix cannot be empty`)); + }).toThrowError(new InvalidSuffixLengthError(0)); }); it("should throw an error for TypeId string with empty suffix", () => { const invalidStr = "prefix_"; expect(() => { TypeID.fromString(invalidStr); - }).toThrowError(new Error(`Invalid TypeId. Suffix cannot be empty`)); + }).toThrowError(new InvalidSuffixLengthError(0)); }); }); diff --git a/typeid/typeid-js/test/typeid_unboxed.test.ts b/typeid/typeid-js/test/typeid_unboxed.test.ts index 43e727ac..a342ddc3 100644 --- a/typeid/typeid-js/test/typeid_unboxed.test.ts +++ b/typeid/typeid-js/test/typeid_unboxed.test.ts @@ -12,6 +12,11 @@ import { } from "../src/unboxed/typeid"; import validJson from "./valid"; import invalidJson from "./invalid"; +import { + InvalidPrefixError, + InvalidSuffixCharacterError, + InvalidSuffixLengthError, +} from "../src/unboxed/error"; describe("TypeId Functions", () => { describe("typeidUnboxed", () => { @@ -38,15 +43,11 @@ describe("TypeId Functions", () => { it("should throw an error if prefix is not lowercase", () => { expect(() => { typeidUnboxed("TEST", "00041061050r3gg28a1c60t3gf"); - }).toThrowError( - "Invalid prefix. Must be at most 63 ascii letters [a-z_]" - ); + }).toThrowError(new InvalidPrefixError("TEST")); expect(() => { typeidUnboxed(" ", "00041061050r3gg28a1c60t3gf"); - }).toThrowError( - "Invalid prefix. Must be at most 63 ascii letters [a-z_]" - ); + }).toThrowError(new InvalidPrefixError(" ")); }); it("should throw an error if suffix length is not 26", () => { @@ -80,9 +81,7 @@ describe("TypeId Functions", () => { expect(() => { fromString(invalidStr); - }).toThrowError( - new Error(`Invalid suffix. First character must be in the range [0-7]`) - ); + }).toThrowError(new InvalidSuffixCharacterError("u")); }); it("should throw an error for empty TypeId string", () => { @@ -90,7 +89,7 @@ describe("TypeId Functions", () => { expect(() => { fromString(invalidStr); - }).toThrowError(new Error(`Invalid TypeId. Suffix cannot be empty`)); + }).toThrowError(new InvalidSuffixLengthError(0)); }); it("should throw an error for TypeId string with empty suffix", () => { @@ -98,7 +97,7 @@ describe("TypeId Functions", () => { expect(() => { fromString(invalidStr); - }).toThrowError(new Error(`Invalid TypeId. Suffix cannot be empty`)); + }).toThrowError(new InvalidSuffixLengthError(0)); }); });