src/errors/RuntimeError.ts

/**
 * @desc
 *  Base class for custom errors that may occur during execution of a test scenario.
 *
 * @example <caption>Custom Error definition</caption>
 * import { RuntimeError } from '@serenity-js/core';
 *
 * export class CustomError extends RuntimeError {
 *   constructor(message: string, cause?: Error) {
 *       super(CustomError, message, cause);
 *   }
 * }
 *
 * @example <caption>Sync error handling</caption>
 * try {
 *     operationThatMightThrowAnError();
 * } catch(error) {
 *     // catch and re-throw
 *     throw new CustomError('operationThatMightThrowAnError has failed', error);
 * }
 *
 * @example <caption>Async error handling</caption>
 * operationThatMightRejectAPromise().catch(error => {
 *     // catch and re-throw
 *     throw new CustomError('operationThatMightThrowAnError has failed', error);
 * });
 *
 * @extends {Error}
 */
export abstract class RuntimeError extends Error {

    /**
     * @param {Function} type - Constructor function used to instantiate a subclass of a RuntimeError
     * @param {string} message - Human-readable description of the error
     * @param {Error} [cause] - The root cause of this {@link RuntimeError}, if any
     */
    protected constructor(
        type: new (...args: any[]) => RuntimeError,
        message: string,
        public readonly cause?: Error,
    ) {
        super(message);
        Object.setPrototypeOf(this, type.prototype);
        this.name = this.constructor.name;

        Error.captureStackTrace(this, type);

        if (!! cause) {
            this.stack = `${ this.stack }\nCaused by: ${ cause.stack }`;
        }
    }

    /**
     * @desc
     *  Human-readable description
     */
    toString() {
        return `${ this.constructor.name }: ${ this.message }`;
    }
}