@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
JavaScript
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);
}
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 };