UNPKG

@storm-stack/errors

Version:

This package includes a base error class and various utility functions for working with errors.

315 lines (314 loc) 8.45 kB
import { Serializable } from "@storm-stack/serialization"; import { EMPTY_STRING, MessageType, NEWLINE_STRING, isError, isFunction, isObject, isSetString } from "@storm-stack/types"; import { ErrorCode } from "./errors.mjs"; import { ErrorType } from "./types.mjs"; import { getDefaultCodeFromType, getDefaultNameFromType } from "./utilities/default-value-helpers.mjs"; export function createStormError({ code, name, type, message, cause, stack, data }) { if (isStormError(cause)) { return cause; } if (cause instanceof Error && cause.name === "StormError") { return cause; } const stormError = new StormError( code ?? ErrorCode.internal_server_error, { name, type, message, cause, stack, data } ); if (cause instanceof Error && cause.stack) { stormError.stack = cause.stack; } return stormError; } export function getCauseFromUnknown(cause, type = ErrorType.EXCEPTION, data) { if (isStormError(cause)) { const result = cause; result.data ??= data; return result; } if (isError(cause)) { return createStormError({ code: getDefaultCodeFromType(type), name: cause.name, message: cause.message, cause, stack: cause.stack, type, data }); } const causeType = typeof cause; if (causeType === "undefined" || causeType === "function" || cause === null) { return new StormError( getDefaultCodeFromType(type), { name: getDefaultNameFromType(type), cause, type, data } ); } if (causeType !== "object") { return new StormError( getDefaultCodeFromType(type), { name: getDefaultNameFromType(type), type, data, message: String(cause) } ); } if (isObject(cause)) { const err = new StormError( getDefaultCodeFromType(type), { name: getDefaultNameFromType(type), type, data } ); for (const key of Object.keys(cause)) { err[key] = cause[key]; } return err; } return new StormError( getDefaultCodeFromType(type), { name: getDefaultNameFromType(type), cause, type, data } ); } export function isStormError(value) { return isError(value) && isSetString(value?.code) && isSetString(value?.message) && isSetString(value?.stack); } @Serializable() class StormError extends Error { __proto__ = Error; /** * The stack trace */ _stack; /** * The inner error */ _cause; /** * The error code */ code; /** * Additional data to be passed with the error */ data; /** * The type of error response message/event */ type = ErrorType.EXCEPTION; /** * Creates a new StormError instance * * @param error - The error to create * @returns The newly created StormError */ static create(error, type = ErrorType.EXCEPTION, data = void 0) { return getCauseFromUnknown(error, type, data); } /** * Creates a new Exception StormError instance * * @param error - The validation details * @param data - The options to use * @returns The newly created StormError */ static createException(error, data = void 0) { return StormError.create(error, ErrorType.EXCEPTION, data); } /** * Creates a new Exception StormError instance * * @param data - The options to use * @returns The newly created StormError */ static createFromData(data = void 0) { return StormError.createException(void 0, data); } /** * Creates a new Validation StormError instance * * @param validationDetails - The validation details * @param options - The options to use * @returns The newly created StormError */ static createValidation(validationDetails, options) { return StormError.create( options, ErrorType.VALIDATION, Array.isArray(validationDetails) ? validationDetails : [validationDetails] ); } /** * Creates a new Not Found StormError instance * * @param recordName - The name of the items returned (or in this case *not returned*) in the search results * @param options - The options to use * @returns The newly created StormError */ static createNotFound(recordName, options) { return StormError.create(options, ErrorType.NOT_FOUND, recordName); } /** * Creates a new Security StormError instance * * @param data - Any relevant data related to the security issue * @param options - The options to use * @returns The newly created StormError */ static createSecurity(data, options) { return StormError.create(options, ErrorType.NOT_FOUND, data); } /** * Creates a new Service Unavailable StormError instance * * @param serviceName - The name of the service that is currently unavailable * @param options - The options to use * @returns The newly created StormError */ static createServiceUnavailable(serviceName, options) { return StormError.create( options, ErrorType.SERVICE_UNAVAILABLE, serviceName ); } /** * Creates a new Action Unsupported StormError instance * * @param action - The action that is unsupported * @param options - The options to use * @returns The newly created StormError */ static createActionUnsupported(action, options) { return StormError.create(options, ErrorType.ACTION_UNSUPPORTED, action); } /** * Creates a new Unknown StormError instance * * @param data - The action that is unsupported * @param options - The options to use * @returns The newly created StormError */ static createUnknown(data, options) { return StormError.create(options, ErrorType.UNKNOWN, data); } constructor(code, options) { super(options.message, { cause: options.cause }); this.code = code; this.message ??= options.message || "An error occurred during processing"; this.name ??= options.name || this.constructor.name; this.data = options.data; if (options.type) { this.type = options.type; } if (options.stack) { this._stack = options.stack; } else if (isFunction(Error.captureStackTrace)) { Error.captureStackTrace(this, this.constructor); } else { this._stack = new Error(options.message).stack; } Object.setPrototypeOf(this, StormError.prototype); } /** * The cause of the error */ get cause() { return this._cause; } /** * The cause of the error */ set cause(_cause) { this._cause = getCauseFromUnknown(_cause, this.type, this.data); } /** * Prints a displayable/formatted stack trace * * @returns The stack trace string */ get stack() { return this._stack ? NEWLINE_STRING + (this._stack || "").split("\n").map((line) => line.trim()).map((line) => { if (line.startsWith(`${this.name}: ${this.message}`)) { return null; } if (line.startsWith("at")) { return ` ${line}`; } return line; }).filter(Boolean).join("\n") : EMPTY_STRING; } /** * Store the stack trace */ set stack(_stack) { this._stack = _stack; } /** * The unformatted stack trace * * @returns The stack trace string */ get originalStack() { return super.stack || this.stack; } /** * Prints the display error message string * * @returns The display error message string */ print() { return this.message ? `${this.name ? this.code ? `${this.name} ` : this.name : EMPTY_STRING} ${this.code ? this.code && this.name ? `(${this.type} - ${this.code})` : `${this.type} - ${this.code}` : this.name ? `(${this.type})` : this.type}: ${this.message}${this.cause ? `${NEWLINE_STRING}Cause: ${isStormError(this.cause) ? this.cause.print() : this.cause}` : EMPTY_STRING}${this.data ? `${NEWLINE_STRING}Data: ${JSON.stringify(this.data, null, 2)}` : EMPTY_STRING}` : EMPTY_STRING; } /** * Prints the error message and stack trace * * @returns The error message and stack trace string */ toString(stacktrace) { return this.print() + (stacktrace === false ? "" : ` ${NEWLINE_STRING}Stack Trace: ${NEWLINE_STRING}${this.stack}`); } /** * Convert the error object into a message details object * * @returns The error message details object */ toMessage() { return { code: this.code, message: this.print(), type: MessageType.ERROR }; } } export { StormError };