From 59455bdcc9c7e9872aab31eb57d940c149267865 Mon Sep 17 00:00:00 2001 From: Gil Pedersen Date: Mon, 30 Oct 2023 12:35:20 +0100 Subject: [PATCH 1/2] Custom error cloning to fix node v21 issue --- lib/clone.js | 14 ++++++++++++++ test/clone.js | 28 ++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/lib/clone.js b/lib/clone.js index e64defb8..eec1be5a 100755 --- a/lib/clone.js +++ b/lib/clone.js @@ -88,6 +88,12 @@ module.exports = internals.clone = function (obj, options = {}, _seen = null) { continue; } + if (baseProto === Types.error && + (key === 'message' || key === 'stack')) { + + continue; // Already a part of the base object + } + const descriptor = Object.getOwnPropertyDescriptor(obj, key); if (descriptor) { if (descriptor.get || @@ -162,6 +168,14 @@ internals.base = function (obj, baseProto, options) { return newObj; } + else if (baseProto === Types.error) { + const err = structuredClone(obj); // Needed to copy internal stack state + if (proto !== baseProto) { + Object.setPrototypeOf(err, proto); // Fix prototype + } + + return err; + } if (internals.needsProtoHack.has(baseProto)) { const newObj = new proto.constructor(); diff --git a/test/clone.js b/test/clone.js index 20754e97..aec94815 100755 --- a/test/clone.js +++ b/test/clone.js @@ -680,6 +680,34 @@ describe('clone()', () => { expect(b.get(nestedObj)).to.equal(a.get(nestedObj)); }); + it('clones Error', () => { + + class CustomError extends Error { + name = 'CustomError'; + } + + const a = new CustomError('bad'); + a.test = Symbol('test'); + + const b = Hoek.clone(a); + + expect(b).to.equal(a); + expect(b).to.not.shallow.equal(a); + expect(b).to.be.instanceOf(CustomError); + expect(b.stack).to.equal(a.stack); // Explicitly validate the .stack getters + }); + + it('cloned Error handles late stack update', () => { + + const a = new Error('bad'); + const b = Hoek.clone(a); + + a.stack = 'late update'; + + expect(b).to.equal(a); + expect(b.stack).to.not.equal(a.stack); + }); + it('ignores symbols', () => { const sym = Symbol(); From 377c19f8806e29a5045b43f096da8229d77eab03 Mon Sep 17 00:00:00 2001 From: Gil Pedersen Date: Mon, 30 Oct 2023 13:37:58 +0100 Subject: [PATCH 2/2] Handle non-string error.message --- lib/clone.js | 2 +- test/clone.js | 27 ++++++++++++++++++++++++++- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/lib/clone.js b/lib/clone.js index eec1be5a..37599447 100755 --- a/lib/clone.js +++ b/lib/clone.js @@ -89,7 +89,7 @@ module.exports = internals.clone = function (obj, options = {}, _seen = null) { } if (baseProto === Types.error && - (key === 'message' || key === 'stack')) { + key === 'stack') { continue; // Already a part of the base object } diff --git a/test/clone.js b/test/clone.js index aec94815..d74a849c 100755 --- a/test/clone.js +++ b/test/clone.js @@ -694,7 +694,32 @@ describe('clone()', () => { expect(b).to.equal(a); expect(b).to.not.shallow.equal(a); expect(b).to.be.instanceOf(CustomError); - expect(b.stack).to.equal(a.stack); // Explicitly validate the .stack getters + expect(b.stack).to.equal(a.stack); // Explicitly validate the .stack getters + }); + + it('clones Error with cause', () => { + + const a = new TypeError('bad', { cause: new Error('embedded') }); + const b = Hoek.clone(a); + + expect(b).to.equal(a); + expect(b).to.not.shallow.equal(a); + expect(b).to.be.instanceOf(TypeError); + expect(b.stack).to.equal(a.stack); // Explicitly validate the .stack getters + expect(b.cause.stack).to.equal(a.cause.stack); // Explicitly validate the .stack getters + }); + + it('clones Error with error message', () => { + + const a = new Error(); + a.message = new Error('message'); + + const b = Hoek.clone(a); + + //expect(b).to.equal(a); // deepEqual() always compares message using === + expect(b.message).to.equal(a.message); + expect(b.message).to.not.shallow.equal(a.message); + expect(b.stack).to.equal(a.stack); }); it('cloned Error handles late stack update', () => {