From cea59fcdba31908ae851e084ec0ae9d07a0351b4 Mon Sep 17 00:00:00 2001 From: Flavian Desverne Date: Sun, 10 May 2020 08:58:52 +0200 Subject: [PATCH] Make error properties non-enumerable (#30) Co-authored-by: Sindre Sorhus --- index.d.ts | 2 ++ index.js | 23 ++++++++++++++++------- readme.md | 2 ++ test.js | 36 ++++++++++++++++++++++++++++++++---- 4 files changed, 52 insertions(+), 11 deletions(-) diff --git a/index.d.ts b/index.d.ts index d9d0d6c..714dc9f 100644 --- a/index.d.ts +++ b/index.d.ts @@ -37,6 +37,8 @@ Deserialize a plain object or any value into an `Error` object. `Error` objects are passed through. Non-error values are wrapped in a `NonError` error. Custom properties are preserved. +Non-enumerable properties are kept non-enumerable (name, message, stack). +Enumerable properties are kept enumerable (all properties besides the non-enumerable ones). Circular references are handled. @example diff --git a/index.js b/index.js index 658411e..06a2916 100644 --- a/index.js +++ b/index.js @@ -3,7 +3,11 @@ class NonError extends Error { constructor(message) { super(NonError._prepareSuperMessage(message)); - this.name = 'NonError'; + Object.defineProperty(this, 'name', { + value: 'NonError', + configurable: true, + writable: true + }); if (Error.captureStackTrace) { Error.captureStackTrace(this, NonError); @@ -20,10 +24,10 @@ class NonError extends Error { } const commonProperties = [ - 'name', - 'message', - 'stack', - 'code' + {property: 'name', enumerable: false}, + {property: 'message', enumerable: false}, + {property: 'stack', enumerable: false}, + {property: 'code', enumerable: true} ]; const destroyCircular = (from, seen, to_) => { @@ -49,9 +53,14 @@ const destroyCircular = (from, seen, to_) => { to[key] = '[Circular]'; } - for (const property of commonProperties) { + for (const {property, enumerable} of commonProperties) { if (typeof from[property] === 'string') { - to[property] = from[property]; + Object.defineProperty(to, property, { + value: from[property], + enumerable, + configurable: true, + writable: true + }); } } diff --git a/readme.md b/readme.md index aa45241..d27f004 100644 --- a/readme.md +++ b/readme.md @@ -39,6 +39,8 @@ Serialize an `Error` object into a plain object. Non-error values are passed through. Custom properties are preserved. +Non-enumerable properties are kept non-enumerable (name, message, stack). +Enumerable properties are kept enumerable (all properties besides the non-enumerable ones). Circular references are handled. ### deserializeError(value) diff --git a/test.js b/test.js index 6bb512d..dd0e232 100644 --- a/test.js +++ b/test.js @@ -10,10 +10,9 @@ function deserializeNonError(t, value) { test('main', t => { const serialized = serializeError(new Error('foo')); - const keys = Object.keys(serialized); - t.true(keys.includes('name')); - t.true(keys.includes('stack')); - t.true(keys.includes('message')); + t.true('name' in serialized); + t.true('stack' in serialized); + t.true('message' in serialized); }); test('should destroy circular references', t => { @@ -172,3 +171,32 @@ test('should deserialize plain object', t => { t.is(deserialized.name, 'name'); t.is(deserialized.code, 'code'); }); + +test('name, stack and message should not be enumerable, other props should be', t => { + const object = { + message: 'error message', + stack: 'at :1:13', + name: 'name' + }; + const nonEnumerableProps = Object.keys(object); + + const enumerables = { + code: 'code', + path: './path', + errno: 1, + syscall: 'syscall', + randomProperty: 'random' + }; + const enumerableProps = Object.keys(enumerables); + + const deserialized = deserializeError({...object, ...enumerables}); + const deserializedEnumerableProps = Object.keys(deserialized); + + for (const prop of nonEnumerableProps) { + t.false(deserializedEnumerableProps.includes(prop)); + } + + for (const prop of enumerableProps) { + t.true(deserializedEnumerableProps.includes(prop)); + } +});