UNPKG

serverless-spy

Version:

CDK-based library for writing elegant integration tests on AWS serverless architecture and an additional web console to monitor events in real time.

210 lines (176 loc) 4.21 kB
import errorConstructors from './error-constructors.js'; export class NonError extends Error { name = 'NonError'; constructor(message) { super(NonError._prepareSuperMessage(message)); } static _prepareSuperMessage(message) { try { return JSON.stringify(message); } catch { return String(message); } } } const commonProperties = [ { property: 'name', enumerable: false, }, { property: 'message', enumerable: false, }, { property: 'stack', enumerable: false, }, { property: 'code', enumerable: true, }, { property: 'cause', enumerable: false, }, ]; const toJsonWasCalled = new WeakSet(); const toJSON = from => { toJsonWasCalled.add(from); const json = from.toJSON(); toJsonWasCalled.delete(from); return json; }; const getErrorConstructor = name => errorConstructors.get(name) ?? Error; // eslint-disable-next-line complexity const destroyCircular = ({ from, seen, to, forceEnumerable, maxDepth, depth, useToJSON, serialize, }) => { if (!to) { if (Array.isArray(from)) { to = []; } else if (!serialize && isErrorLike(from)) { const Error = getErrorConstructor(from.name); to = new Error(); } else { to = {}; } } seen.push(from); if (depth >= maxDepth) { return to; } if (useToJSON && typeof from.toJSON === 'function' && !toJsonWasCalled.has(from)) { return toJSON(from); } const continueDestroyCircular = value => destroyCircular({ from: value, seen: [...seen], forceEnumerable, maxDepth, depth, useToJSON, serialize, }); for (const [key, value] of Object.entries(from)) { if (value && value instanceof Uint8Array && value.constructor.name === 'Buffer') { to[key] = '[object Buffer]'; continue; } // TODO: Use `stream.isReadable()` when targeting Node.js 18. if (value !== null && typeof value === 'object' && typeof value.pipe === 'function') { to[key] = '[object Stream]'; continue; } if (typeof value === 'function') { continue; } if (!value || typeof value !== 'object') { // Gracefully handle non-configurable errors like `DOMException`. try { to[key] = value; } catch {} continue; } if (!seen.includes(from[key])) { depth++; to[key] = continueDestroyCircular(from[key]); continue; } to[key] = '[Circular]'; } for (const {property, enumerable} of commonProperties) { if (typeof from[property] !== 'undefined' && from[property] !== null) { Object.defineProperty(to, property, { value: isErrorLike(from[property]) ? continueDestroyCircular(from[property]) : from[property], enumerable: forceEnumerable ? true : enumerable, configurable: true, writable: true, }); } } return to; }; export function serializeError(value, options = {}) { const { maxDepth = Number.POSITIVE_INFINITY, useToJSON = true, } = options; if (typeof value === 'object' && value !== null) { return destroyCircular({ from: value, seen: [], forceEnumerable: true, maxDepth, depth: 0, useToJSON, serialize: true, }); } // People sometimes throw things besides Error objects… if (typeof value === 'function') { // `JSON.stringify()` discards functions. We do too, unless a function is thrown directly. // We intentionally use `||` because `.name` is an empty string for anonymous functions. return `[Function: ${value.name || 'anonymous'}]`; } return value; } export function deserializeError(value, options = {}) { const {maxDepth = Number.POSITIVE_INFINITY} = options; if (value instanceof Error) { return value; } if (isMinimumViableSerializedError(value)) { const Error = getErrorConstructor(value.name); return destroyCircular({ from: value, seen: [], to: new Error(), maxDepth, depth: 0, serialize: false, }); } return new NonError(value); } export function isErrorLike(value) { return Boolean(value) && typeof value === 'object' && 'name' in value && 'message' in value && 'stack' in value; } function isMinimumViableSerializedError(value) { return Boolean(value) && typeof value === 'object' && 'message' in value && !Array.isArray(value); } export {default as errorConstructors} from './error-constructors.js';