From 2edbd8c5112d4c584c4c20dbbc80579a8a771fa5 Mon Sep 17 00:00:00 2001 From: DArcy Rush Date: Tue, 12 Nov 2024 14:08:37 +0100 Subject: [PATCH] feat: Support serializing primitives within Error causes (#158) --- lib/err-helpers.js | 30 ++++++++++++++++-------------- lib/err-with-cause.js | 2 +- test/err-with-cause.test.js | 16 ++++++++++++++++ test/err.test.js | 19 ++++++++++++++++++- 4 files changed, 51 insertions(+), 16 deletions(-) diff --git a/lib/err-helpers.js b/lib/err-helpers.js index efdec2c..e9c7d75 100644 --- a/lib/err-helpers.js +++ b/lib/err-helpers.js @@ -29,9 +29,7 @@ const getErrorCause = (err) => { ? causeResult : undefined } else { - return isErrorLike(cause) - ? cause - : undefined + return cause } } @@ -44,8 +42,6 @@ const getErrorCause = (err) => { * @returns {string} */ const _stackWithCauses = (err, seen) => { - if (!isErrorLike(err)) return '' - const stack = err.stack || '' // Ensure we don't go circular or crazily deep @@ -56,8 +52,12 @@ const _stackWithCauses = (err, seen) => { const cause = getErrorCause(err) if (cause) { - seen.add(err) - return (stack + '\ncaused by: ' + _stackWithCauses(cause, seen)) + if (isErrorLike(cause)) { + seen.add(err) + return (stack + '\ncaused by: ' + _stackWithCauses(cause, seen)) + } else { + return (stack + '\ncaused by: ' + String(cause)) + } } else { return stack } @@ -79,8 +79,6 @@ const stackWithCauses = (err) => _stackWithCauses(err, new Set()) * @returns {string} */ const _messageWithCauses = (err, seen, skip) => { - if (!isErrorLike(err)) return '' - const message = skip ? '' : (err.message || '') // Ensure we don't go circular or crazily deep @@ -91,14 +89,18 @@ const _messageWithCauses = (err, seen, skip) => { const cause = getErrorCause(err) if (cause) { - seen.add(err) - // @ts-ignore const skipIfVErrorStyleCause = typeof err.cause === 'function' - return (message + - (skipIfVErrorStyleCause ? '' : ': ') + - _messageWithCauses(cause, seen, skipIfVErrorStyleCause)) + if (isErrorLike(cause)) { + seen.add(err) + + return (message + + (skipIfVErrorStyleCause ? '' : ': ') + + _messageWithCauses(cause, seen, skipIfVErrorStyleCause)) + } else { + return (message + (skipIfVErrorStyleCause ? '' : ': ') + String(cause)) + } } else { return message } diff --git a/lib/err-with-cause.js b/lib/err-with-cause.js index 29939e0..09593f3 100644 --- a/lib/err-with-cause.js +++ b/lib/err-with-cause.js @@ -25,7 +25,7 @@ function errWithCauseSerializer (err) { _err.aggregateErrors = err.errors.map(err => errWithCauseSerializer(err)) } - if (isErrorLike(err.cause) && !Object.prototype.hasOwnProperty.call(err.cause, seen)) { + if (!!err.cause && !Object.prototype.hasOwnProperty.call(err.cause, seen)) { _err.cause = errWithCauseSerializer(err.cause) } diff --git a/test/err-with-cause.test.js b/test/err-with-cause.test.js index 15f356a..72cd70b 100644 --- a/test/err-with-cause.test.js +++ b/test/err-with-cause.test.js @@ -75,6 +75,22 @@ test('keeps non-error cause', () => { assert.strictEqual(serialized.cause, 'abc') }) +test('keeps non-error cause from constructor', () => { + for (const cause of [ + 'string', + 42, + ['an', 'array'], + { an: 'object' }, + Symbol('symbol') + ]) { + const err = Error('foo', { cause }) + const serialized = serializer(err) + assert.strictEqual(serialized.type, 'Error') + assert.strictEqual(serialized.message, 'foo') + assert.strictEqual(serialized.cause, cause) + } +}) + test('prevents infinite recursion', () => { const err = Error('foo') err.inner = err diff --git a/test/err.test.js b/test/err.test.js index 0aecb07..eef1e03 100644 --- a/test/err.test.js +++ b/test/err.test.js @@ -62,6 +62,23 @@ test('serializes error causes', () => { } }) +test('serializes non Error error cause from constructor', () => { + for (const cause of [ + 'string', + 42, + // ["an", "array"], + // { an: "object" }, + Symbol('symbol') + ]) { + const err = Error('foo', { cause }) + const serialized = serializer(err) + assert.strictEqual(serialized.type, 'Error') + assert.strictEqual(serialized.message, 'foo: ' + String(cause)) + assert.match(serialized.stack, /err\.test\.js:/) + assert.match(serialized.stack, /Error: foo/) + } +}) + test('serializes error causes with VError support', function (t) { // Fake VError-style setup const err = Error('foo: bar') @@ -85,7 +102,7 @@ test('keeps non-error cause', () => { err.cause = 'abc' const serialized = serializer(err) assert.strictEqual(serialized.type, 'Error') - assert.strictEqual(serialized.message, 'foo') + assert.strictEqual(serialized.message, 'foo: abc') assert.strictEqual(serialized.cause, 'abc') })