From 016bfccb9e1a1a6c8e7706a8366167daca5fecb1 Mon Sep 17 00:00:00 2001 From: Federico Brigante Date: Fri, 6 May 2022 10:25:45 +0800 Subject: [PATCH 1/5] Update `cause` tests --- test.js | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/test.js b/test.js index f8c512d..35528ed 100644 --- a/test.js +++ b/test.js @@ -132,16 +132,29 @@ test('should serialize nested errors', t => { const serialized = serializeError(error); t.is(serialized.message, 'outer error'); - t.is(serialized.innerError.message, 'inner error'); + t.like(serialized.innerError, { + name: 'Error', + message: 'inner error', + }); + t.false(serialized.innerError instanceof Error); }); test('should serialize the cause property', t => { const error = new Error('outer error'); - error.cause = new Error('inner error'); + // TODO: Replace with plain `new Error('outer', {cause: new Error('inner')})` when targeting Node 16.9+ + Object.defineProperty(error, 'cause', { + value: new Error('inner error'), + enumerable: false, + writable: true, + }); const serialized = serializeError(error); t.is(serialized.message, 'outer error'); - t.is(serialized.cause.message, 'inner error'); + t.like(serialized.cause, { + name: 'Error', + message: 'inner error', + }); + t.false(serialized.cause instanceof Error); }); test('should handle top-level null values', t => { From 20ed2e7046cdad56f96afe391a23f8ecbee0c95e Mon Sep 17 00:00:00 2001 From: Federico Brigante Date: Mon, 9 May 2022 12:02:47 +0800 Subject: [PATCH 2/5] Serialize nested Errors --- index.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/index.js b/index.js index 2a74412..4d3d963 100644 --- a/index.js +++ b/index.js @@ -59,6 +59,7 @@ const destroyCircular = ({ maxDepth, depth, useToJSON, + serialize, }) => { const to = to_ ?? (Array.isArray(from) ? [] : {}); @@ -78,11 +79,12 @@ const destroyCircular = ({ from: value, seen: [...seen], - to_: isErrorLike(value) ? new Error() : undefined, + to_: !serialize && isErrorLike(value) ? new Error() : undefined, forceEnumerable, maxDepth, depth, useToJSON, + serialize, }); }; @@ -146,6 +148,7 @@ export function serializeError(value, options = {}) { maxDepth, depth: 0, useToJSON, + serialize: true, }); } @@ -173,6 +176,7 @@ export function deserializeError(value, options = {}) { to_: new Error(), maxDepth, depth: 0, + serialize: false, }); } From 4c1044682e91b2edd9f87b1cfe5089c480c0c530 Mon Sep 17 00:00:00 2001 From: Federico Brigante Date: Mon, 9 May 2022 12:17:09 +0800 Subject: [PATCH 3/5] Move `to` logic to single location --- index.js | 42 +++++++++++++++++++++++------------------- 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/index.js b/index.js index 4d3d963..9e5e7b3 100644 --- a/index.js +++ b/index.js @@ -54,14 +54,23 @@ const getErrorConstructor = name => errorConstructors.get(name) ?? Error; const destroyCircular = ({ from, seen, - to_, + to, forceEnumerable, maxDepth, depth, useToJSON, serialize, }) => { - const to = to_ ?? (Array.isArray(from) ? [] : {}); + 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); @@ -73,20 +82,15 @@ const destroyCircular = ({ return toJSON(from); } - const destroyLocal = value => { - const Error = getErrorConstructor(value.name); - return destroyCircular({ - from: value, - seen: [...seen], - - to_: !serialize && isErrorLike(value) ? new Error() : undefined, - forceEnumerable, - maxDepth, - depth, - useToJSON, - serialize, - }); - }; + const continueDestroyCircular = value => destroyCircular({ + from: value, + seen: [...seen], + forceEnumerable, + maxDepth, + depth, + useToJSON, + serialize, + }); for (const [key, value] of Object.entries(from)) { // eslint-disable-next-line node/prefer-global/buffer @@ -112,7 +116,7 @@ const destroyCircular = ({ if (!seen.includes(from[key])) { depth++; - to[key] = destroyLocal(from[key]); + to[key] = continueDestroyCircular(from[key]); continue; } @@ -123,7 +127,7 @@ const destroyCircular = ({ for (const {property, enumerable} of commonProperties) { if (typeof from[property] !== 'undefined' && from[property] !== null) { Object.defineProperty(to, property, { - value: isErrorLike(from[property]) ? destroyLocal(from[property]) : from[property], + value: isErrorLike(from[property]) ? continueDestroyCircular(from[property]) : from[property], enumerable: forceEnumerable ? true : enumerable, configurable: true, writable: true, @@ -173,7 +177,7 @@ export function deserializeError(value, options = {}) { return destroyCircular({ from: value, seen: [], - to_: new Error(), + to: new Error(), maxDepth, depth: 0, serialize: false, From d4ebd1279d0e2f54269e8391c34be12ff79d121f Mon Sep 17 00:00:00 2001 From: Federico Brigante Date: Wed, 11 May 2022 18:30:45 +0800 Subject: [PATCH 4/5] Lint --- test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test.js b/test.js index 35528ed..03c25b6 100644 --- a/test.js +++ b/test.js @@ -250,7 +250,7 @@ for (const property of ['cause', 'any']) { }); } -test('deserialized name, stack, cause an message should not be enumerable, other props should be', t => { +test('deserialized name, stack, cause and message should not be enumerable, other props should be', t => { const object = { message: 'error message', stack: 'at :1:13', From 2a79cf4617e4bec84b3816357aadc42c0885a61c Mon Sep 17 00:00:00 2001 From: Federico Brigante Date: Wed, 11 May 2022 18:53:33 +0800 Subject: [PATCH 5/5] Test error.cause.cause --- test.js | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/test.js b/test.js index 03c25b6..7b46b8f 100644 --- a/test.js +++ b/test.js @@ -11,6 +11,15 @@ function deserializeNonError(t, value) { t.is(deserialized.message, JSON.stringify(value)); } +// TODO: Replace with plain `new Error('outer', {cause: new Error('inner')})` when targeting Node 16.9+ +function setErrorCause(error, cause) { + Object.defineProperty(error, 'cause', { + value: cause, + enumerable: false, + writable: true, + }); +} + test('main', t => { const serialized = serializeError(new Error('foo')); const properties = Object.keys(serialized); @@ -141,20 +150,21 @@ test('should serialize nested errors', t => { test('should serialize the cause property', t => { const error = new Error('outer error'); - // TODO: Replace with plain `new Error('outer', {cause: new Error('inner')})` when targeting Node 16.9+ - Object.defineProperty(error, 'cause', { - value: new Error('inner error'), - enumerable: false, - writable: true, - }); + setErrorCause(error, new Error('inner error')); + setErrorCause(error.cause, new Error('deeper error')); const serialized = serializeError(error); t.is(serialized.message, 'outer error'); t.like(serialized.cause, { name: 'Error', message: 'inner error', + cause: { + name: 'Error', + message: 'deeper error', + }, }); t.false(serialized.cause instanceof Error); + t.false(serialized.cause.cause instanceof Error); }); test('should handle top-level null values', t => {