Skip to content

Commit

Permalink
Make error properties non-enumerable (#30)
Browse files Browse the repository at this point in the history
Co-authored-by: Sindre Sorhus <sindresorhus@gmail.com>
  • Loading branch information
Weakky and sindresorhus committed May 10, 2020
1 parent cf7b57c commit cea59fc
Show file tree
Hide file tree
Showing 4 changed files with 52 additions and 11 deletions.
2 changes: 2 additions & 0 deletions index.d.ts
Expand Up @@ -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
Expand Down
23 changes: 16 additions & 7 deletions index.js
Expand Up @@ -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);
Expand All @@ -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_) => {
Expand All @@ -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
});
}
}

Expand Down
2 changes: 2 additions & 0 deletions readme.md
Expand Up @@ -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)
Expand Down
36 changes: 32 additions & 4 deletions test.js
Expand Up @@ -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 => {
Expand Down Expand Up @@ -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 <anonymous>: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));
}
});

0 comments on commit cea59fc

Please sign in to comment.