diff --git a/src/utils/errors/argument-error.ts b/src/utils/errors/argument-error.ts new file mode 100644 index 0000000..cf004a1 --- /dev/null +++ b/src/utils/errors/argument-error.ts @@ -0,0 +1,79 @@ +/** + * Represents an error that is thrown when one of the arguments provided to a method is not valid. + */ +export class ArgumentError extends Error { + /** + * The default message to use when no message is provided. + */ + private static readonly DEFAULT_ARGUMENT_ERROR_MESSAGE = "Value does not fall within the expected range."; + + /** + * The message to use when an object was empty. + */ + private static readonly EMPTY_ARGUMENT_ERROR_MESSAGE = "The value cannot be null, undefined, or empty."; + + /** + * The pattern used to format the parameter name into the error message. + * + * @param paramName - The name of the parameter causing the error. + * + * @returns A formatted error message that includes the parameter name. + */ + private static readonly PARAM_NAME_MESSAGE_PATTERN = (paramName?: string) => paramName ? ` (Parameter '${paramName}')` : ""; + + /** + * The name of the parameter that caused the error. + */ + private readonly _paramName?: string; + + /** + * Initializes a new instance of the {@link ArgumentError} class. + * + * @param paramName - The name of the parameter that caused the error. + * @param message - The error message to display. + * @param options - Optional settings for the error object. + */ + constructor(paramName?: string, message?: string, options?: ErrorOptions) { + super(ArgumentError.formatErrorMessage(message, paramName), options); + + this.name = "ArgumentError"; + this._paramName = paramName; + } + + /** + * Gets the name of the parameter that caused the error. + * + * @returns The name of the parameter that caused the error, or `undefined` if no name was provided. + */ + get paramName(): string | undefined { + return this._paramName; + } + + /** + * Throws an {@link ArgumentError} if the specified argument is `null`, `undefined`, or empty. + * + * @param argument - The argument to check. + * @param paramName - The name of the parameter being checked. + * + * @throws An {@link ArgumentError} if the specified argument is `null`, `undefined`, or empty. + */ + static throwIfNullOrEmpty(argument?: { length: number }, paramName?: string): void | never { + if (argument === undefined || argument === null || argument.length === 0) { + throw new ArgumentError(paramName, ArgumentError.EMPTY_ARGUMENT_ERROR_MESSAGE); + } + } + + /** + * Formats the error message to include any specified parameter name. + * + * @param message - The error message to format. + * @param paramName - The name of the parameter that caused the error. + * + * @returns The formatted error message. + */ + private static formatErrorMessage(message?: string, paramName?: string) { + message ??= ArgumentError.DEFAULT_ARGUMENT_ERROR_MESSAGE; + message += ArgumentError.PARAM_NAME_MESSAGE_PATTERN(paramName); + return message; + } +} diff --git a/tests/unit/utils/errors.ts/argument-error.spec.ts b/tests/unit/utils/errors.ts/argument-error.spec.ts new file mode 100644 index 0000000..8bfa70f --- /dev/null +++ b/tests/unit/utils/errors.ts/argument-error.spec.ts @@ -0,0 +1,45 @@ +import { ArgumentError } from "@/utils/errors/argument-error"; + +describe("ArgumentError", () => { + describe("constructor", () => { + test("creates an instance with the given parameter name and message", () => { + const error = new ArgumentError("param1", "test message"); + + expect(error).toBeInstanceOf(ArgumentError); + expect(error.name).toBe("ArgumentError"); + expect(error.message).toBe("test message (Parameter 'param1')"); + expect(error.paramName).toBe("param1"); + }); + + test("creates an instance with a default message if no message is provided", () => { + const error = new ArgumentError("param1"); + + expect(error.message).toBe("Value does not fall within the expected range. (Parameter 'param1')"); + }); + + test("creates an instance with no parameter name if none is provided", () => { + const error = new ArgumentError(undefined, "test message"); + + expect(error.message).toBe("test message"); + expect(error.paramName).toBeUndefined(); + }); + }); + + describe("throwIfNullOrEmpty", () => { + test("throws an ArgumentError with a specified parameter name if the argument is null", () => { + expect(() => ArgumentError.throwIfNullOrEmpty(null, "param1")).toThrowError(new ArgumentError("param1", "The value cannot be null, undefined, or empty.")); + }); + + test("throws an ArgumentError with a specified parameter name if the argument is undefined", () => { + expect(() => ArgumentError.throwIfNullOrEmpty(undefined, "param1")).toThrowError(new ArgumentError("param1", "The value cannot be null, undefined, or empty.")); + }); + + test("throws an ArgumentError with a specified parameter name if the argument is empty", () => { + expect(() => ArgumentError.throwIfNullOrEmpty("", "param1")).toThrowError(new ArgumentError("param1", "The value cannot be null, undefined, or empty.")); + }); + + test("does not throw if the argument is not null, undefined, or empty", () => { + expect(() => ArgumentError.throwIfNullOrEmpty("not empty", "param1")).not.toThrow(); + }); + }); +});