From b6ff2ebcd3ed474787a1666596999b9702fa8fb6 Mon Sep 17 00:00:00 2001 From: fregante Date: Sat, 4 Jan 2025 18:02:33 +0100 Subject: [PATCH] Add support for `AggregateError` (#103) --- error-constructors.js | 1 + index.js | 21 +++++++++++++++------ test.js | 29 +++++++++++++++++++++++++++++ 3 files changed, 45 insertions(+), 6 deletions(-) diff --git a/error-constructors.js b/error-constructors.js index 82a43d4..99938f0 100644 --- a/error-constructors.js +++ b/error-constructors.js @@ -7,6 +7,7 @@ const list = [ SyntaxError, TypeError, URIError, + AggregateError, // Built-in errors globalThis.DOMException, diff --git a/index.js b/index.js index ccc6a5e..29dd979 100644 --- a/index.js +++ b/index.js @@ -37,6 +37,10 @@ const errorProperties = [ property: 'cause', enumerable: false, }, + { + property: 'errors', + enumerable: false, + }, ]; const toJsonWasCalled = new WeakSet(); @@ -48,7 +52,12 @@ const toJSON = from => { return json; }; -const getErrorConstructor = name => errorConstructors.get(name) ?? Error; +const newError = name => { + const ErrorConstructor = errorConstructors.get(name) ?? Error; + return ErrorConstructor === AggregateError + ? new ErrorConstructor([]) + : new ErrorConstructor(); +}; // eslint-disable-next-line complexity const destroyCircular = ({ @@ -65,8 +74,7 @@ const destroyCircular = ({ if (Array.isArray(from)) { to = []; } else if (!serialize && isErrorLike(from)) { - const Error = getErrorConstructor(from.name); - to = new Error(); + to = newError(from.name); } else { to = {}; } @@ -131,7 +139,9 @@ const destroyCircular = ({ for (const {property, enumerable} of errorProperties) { if (from[property] !== undefined && from[property] !== null) { Object.defineProperty(to, property, { - value: isErrorLike(from[property]) ? continueDestroyCircular(from[property]) : from[property], + value: isErrorLike(from[property]) || Array.isArray(from[property]) + ? continueDestroyCircular(from[property]) + : from[property], enumerable: forceEnumerable ? true : enumerable, configurable: true, writable: true, @@ -179,11 +189,10 @@ export function deserializeError(value, options = {}) { } if (isMinimumViableSerializedError(value)) { - const Error = getErrorConstructor(value.name); return destroyCircular({ from: value, seen: [], - to: new Error(), + to: newError(value.name), maxDepth, depth: 0, serialize: false, diff --git a/test.js b/test.js index 3d413d5..305de49 100644 --- a/test.js +++ b/test.js @@ -165,6 +165,20 @@ test('should serialize the cause property', t => { t.false(serialized.cause.cause instanceof Error); }); +test('should serialize AggregateError', t => { + // eslint-disable-next-line unicorn/error-message -- Testing this eventuality + const error = new AggregateError([new Error('inner error')]); + + const serialized = serializeError(error); + t.is(serialized.message, ''); // Default error message + t.true(Array.isArray(serialized.errors)); + t.like(serialized.errors[0], { + name: 'Error', + message: 'inner error', + }); + t.false(serialized.errors[0] instanceof Error); +}); + test('should handle top-level null values', t => { const serialized = serializeError(null); t.is(serialized, null); @@ -369,6 +383,21 @@ test('should deserialize properties up to `Options.maxDepth` levels deep', t => t.deepEqual(levelThree, error); }); +test('should deserialize AggregateError', t => { + const deserialized = deserializeError({ + name: 'AggregateError', + message: '', + errors: [ + {name: 'Error', message: 'inner error', stack: ''}, + ], + }); + t.true(deserialized instanceof AggregateError); + t.is(deserialized.message, ''); + t.true(Array.isArray(deserialized.errors)); + t.is(deserialized.errors[0].message, 'inner error'); + t.true(deserialized.errors[0] instanceof Error); +}); + test('should ignore invalid error-like objects', t => { const errorLike = { name: 'Error',