Skip to content

Commit

Permalink
Expect a message property at a minimum to be interpreted as an Error (
Browse files Browse the repository at this point in the history
  • Loading branch information
fregante committed May 12, 2022
1 parent 0af9fe8 commit 0720121
Show file tree
Hide file tree
Showing 4 changed files with 54 additions and 14 deletions.
4 changes: 3 additions & 1 deletion index.d.ts
Expand Up @@ -113,11 +113,13 @@ export function serializeError<ErrorType>(error: ErrorType, options?: Options):
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.
- Objects that have at least a `message` property are interpreted as errors.
- All other values are wrapped in a `NonError` error.
- Custom properties are preserved.
- Non-enumerable properties are kept non-enumerable (name, message, stack, cause).
- Enumerable properties are kept enumerable (all properties besides the non-enumerable ones).
- Circular references are handled.
- Native error constructors are preserved (TypeError, DOMException, etc) and more can be added.
@example
```
Expand Down
11 changes: 9 additions & 2 deletions index.js
Expand Up @@ -172,7 +172,7 @@ export function deserializeError(value, options = {}) {
return value;
}

if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
if (isMinimumViableSerializedError(value)) {
const Error = getErrorConstructor(value.name);
return destroyCircular({
from: value,
Expand All @@ -188,11 +188,18 @@ export function deserializeError(value, options = {}) {
}

export function isErrorLike(value) {
return value
return Boolean(value)
&& typeof value === 'object'
&& 'name' in value
&& 'message' in value
&& 'stack' in value;
}

function isMinimumViableSerializedError(value) {
return Boolean(value)
&& typeof value === 'object'
&& 'message' in value
&& !Array.isArray(value);
}

export {default as errorConstructors} from './error-constructors.js';
19 changes: 12 additions & 7 deletions readme.md
Expand Up @@ -64,14 +64,12 @@ import {MyCustomError} from './errors.js'
errorConstructors.set('MyCustomError', MyCustomError)
```

**Warning:** Only simple and standard error constructors are supported, like `new MyCustomError(name)`. If your error constructor **requires** a second parameter or does not accept a string as first parameter, adding it to this map **will** break the deserialization.
**Warning:** Only simple and standard error constructors are supported, like `new MyCustomError(message)`. If your error constructor **requires** a second parameter or does not accept a string as first parameter, adding it to this map **will** break the deserialization.

## API

### serializeError(value, options?)

Type: `Error | unknown`

Serialize an `Error` object into a plain object.

- Non-error values are passed through.
Expand All @@ -83,7 +81,11 @@ Serialize an `Error` object into a plain object.
- If the input object has a `.toJSON()` method, then it's called instead of serializing the object's properties.
- It's up to `.toJSON()` implementation to handle circular references and enumerability of the properties.

`.toJSON` examples:
### value

Type: `Error | unknown`

### toJSON implementation examples

```js
import {serializeError} from 'serialize-error';
Expand Down Expand Up @@ -118,18 +120,21 @@ serializeError(error);

### deserializeError(value, options?)

Type: `{[key: string]: unknown} | unknown`

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.
- Objects that have at least a `message` property are interpreted as errors.
- All other values are wrapped in a `NonError` error.
- Custom properties are preserved.
- Non-enumerable properties are kept non-enumerable (name, message, stack, cause).
- Enumerable properties are kept enumerable (all properties besides the non-enumerable ones).
- Circular references are handled.
- [Native error constructors](./error-constructors.js) are preserved (TypeError, DOMException, etc) and [more can be added.](#error-constructors)

### value

Type: `{message: string} | unknown`

### options

Type: `object`
Expand Down
34 changes: 30 additions & 4 deletions test.js
Expand Up @@ -192,10 +192,23 @@ test('should deserialize array', t => {
deserializeNonError(t, [1]);
});

test('should deserialize empty object', t => {
deserializeNonError(t, {});
});

test('should ignore Error instance', t => {
const originalError = new Error('test');
const deserialized = deserializeError(originalError);
t.is(deserialized, originalError);
});

test('should deserialize error', t => {
const deserialized = deserializeError(new Error('test'));
const deserialized = deserializeError({
message: 'Stuff happened',
});
t.true(deserialized instanceof Error);
t.is(deserialized.message, 'test');
t.is(deserialized.name, 'Error');
t.is(deserialized.message, 'Stuff happened');
});

test('should deserialize and preserve existing properties', t => {
Expand Down Expand Up @@ -247,7 +260,13 @@ for (const property of ['cause', 'any']) {
message: 'source error message',
stack: 'at <anonymous>:3:14',
name: 'name',
code: 'code',
code: 'the apple',
[property]: {
message: 'original error message',
stack: 'at <anonymous>:16:9',
name: 'name',
code: 'the snake',
},
},
};

Expand All @@ -256,7 +275,14 @@ for (const property of ['cause', 'any']) {
t.is(nested.message, 'source error message');
t.is(nested.stack, 'at <anonymous>:3:14');
t.is(nested.name, 'name');
t.is(nested.code, 'code');
t.is(nested.code, 'the apple');

const {[property]: deepNested} = nested;
t.true(deepNested instanceof Error);
t.is(deepNested.message, 'original error message');
t.is(deepNested.stack, 'at <anonymous>:16:9');
t.is(deepNested.name, 'name');
t.is(deepNested.code, 'the snake');
});
}

Expand Down

0 comments on commit 0720121

Please sign in to comment.