UNPKG

@serenity-js/core

Version:

The core Serenity/JS framework, providing the Screenplay Pattern interfaces, as well as the test reporting and integration infrastructure

108 lines (87 loc) 3.67 kB
import type { JSONObject, JSONValue } from 'tiny-types'; import { ensure, isDefined, isFunction } from 'tiny-types'; /** * @group Errors */ export class ErrorSerialiser { private static readonly recognisedErrors: Array<new (...args: any[]) => Error> = [ // Built-in JavaScript errors Error, EvalError, RangeError, ReferenceError, SyntaxError, TypeError, URIError, ]; static serialise(error: Error): string { if (this.isSerialisable(error)) { return JSON.stringify(error.toJSON(), undefined, 0); } const name = error && error.constructor && error.constructor.name ? error.constructor.name : error.name; const serialisedError = Object.getOwnPropertyNames(error).reduce((serialised, key) => { serialised[key] = error[key] return serialised; }, { name }) as SerialisedError; return JSON.stringify(serialisedError, undefined, 0); } static registerErrorTypes(...types: Array<new (...args: any[]) => Error>): void { types.forEach(type => { ErrorSerialiser.recognisedErrors.push( ensure(`Error type ${ type }`, type as any, isDefined(), isFunction()) ); }); } static deserialise(serialised?: string | JSONObject): Error | undefined { if (serialised === null || serialised === undefined) { return undefined; } const serialisedError = typeof serialised === 'string' ? JSON.parse(serialised) as SerialisedError : serialised; const constructor = ErrorSerialiser.recognisedErrors.find(errorType => errorType.name === serialisedError.name) || Error; if (this.isDeserialisable(constructor)) { return constructor.fromJSON(serialisedError) as Error; } const deserialised = Object.create(constructor.prototype); for (const property in serialisedError) { if (Object.prototype.hasOwnProperty.call(serialisedError, property)) { deserialised[property] = serialisedError[property]; } } return deserialised; } private static isSerialisable(value: any): value is { toJSON: () => JSONValue } { return value && typeof (value as any).toJSON === 'function'; } private static isDeserialisable<T>(type: new (...args: any[]) => T): type is typeof type & { fromJSON: (o: JSONValue) => T } { return type && typeof (type as any).fromJSON === 'function'; } static deserialiseFromStackTrace(stack: string): Error { const stackTracePattern = /^([^\s:]*Error).*?(?::\s)?(.*?)\n(^ +at.*)$/ms; if (! stackTracePattern.test(stack)) { return new Error(String(stack)); } const [, name, message, callStack_ ] = stack.match(stackTracePattern); return ErrorSerialiser.deserialise({ name, message: message.trim(), stack }); } } interface SerialisedError extends JSONObject { /** * Name of the constructor function used to instantiate * the original [`Error`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error) object. */ name: string; /** * Message of the original [`Error`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error) object */ message: string; /** * Stack trace of the original [`Error`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error) object */ stack: string; }