Skip to content

Commit 7aafe29

Browse files
feat: improve error serialization (#111)
Improves the serialization of errors to be compliant with the JSON:API spec: * Remove explicit check for `Error` instances and, instead, validate both instances of `Error` and POJOs using `validateError` * Restrict `links` object properties to `about` * Restrict `links.about` object property type to string * Restrict `source` object properties to `pointer` and `parameter` * Restrict `meta` object property type to object * Add function for validating `status` against a list of known HTTP status codes * Restrict `status` object property type to number * Support both `status` and `statusCode` properties * Add tests for new functionality * Update tests for changed functionality * Update README.md with additional usage examples closes #110 closes #111
1 parent 4ddf2d8 commit 7aafe29

File tree

4 files changed

+363
-76
lines changed

4 files changed

+363
-76
lines changed

README.md

+104-6
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ Serializer.register(type, options);
5454
* **deserialize** (optional): Describes the function which should be used to deserialize a related property which is not included in the JSON:API document. It should be:
5555
* A _function_ with one argument `function(data) { ... }`which defines the format to which a relation should be deserialized. By default, the ID of the related object is returned, which would be equal to `function(data) {return data.id}`. See [issue #65](https://github.com/danivek/json-api-serializer/issues/65).
5656
* **convertCase** (optional): Case conversion for serializing data. Value can be : `kebab-case`, `snake_case`, `camelCase`
57-
* **beforeSerialize** (optional): A _function_ with one argument `beforeSerialize(data) => newData` to transform data before serialization.
57+
* **beforeSerialize** (optional): A _function_ with one argument `beforeSerialize(data) => newData` to transform data before serialization.
5858

5959
**Deserialization options:**
6060

@@ -427,12 +427,18 @@ Serializer.deserializeAsync('article', data)
427427
Serializes any error into a JSON API error document.
428428

429429
Input data can be:
430-
- An instance of Error or an array of instance of Error.
431-
- A [JSON API error object](http://jsonapi.org/format/#error-objects) or an array of [JSON API error object](http://jsonapi.org/format/#error-objects).
430+
- An instance of `Error` or an array of `Error` instances.
431+
- A [JSON API error object](http://jsonapi.org/format/#error-objects) or an array of [JSON API error objects](http://jsonapi.org/format/#error-objects).
432+
433+
Using an instance of `Error`:
432434

433435
```javascript
434-
const error = new Error('An error occured');
435-
error.status = 500;
436+
const error = new Error('An error occurred');
437+
error.id = 123
438+
error.links = { about: 'https://example.com/errors/123' }
439+
error.status = 500; // or `statusCode`
440+
error.code = 'xyz'
441+
error.meta = { time: Date.now() }
436442

437443
Serializer.serializeError(error);
438444
```
@@ -443,8 +449,100 @@ The result will be:
443449
{
444450
"errors": [
445451
{
452+
"id": 123,
453+
"links": {
454+
"about": "https://example.com/errors/123"
455+
},
456+
"status": "500",
457+
"code": "xyz",
458+
"title": "Error",
459+
"detail": "An error occurred",
460+
"meta": {
461+
"time": 1593561258853
462+
}
463+
}
464+
]
465+
}
466+
```
467+
468+
Using an instance of a class that inherits from `Error`:
469+
470+
```js
471+
class MyCustomError extends Error {
472+
constructor(message = 'Something went wrong') {
473+
super(message)
474+
this.id = 123
475+
this.links = {
476+
about: 'https://example.com/errors/123'
477+
}
478+
this.status = 500 // or `statusCode`
479+
this.code = 'xyz'
480+
this.meta = {
481+
time: Date.now()
482+
}
483+
}
484+
}
485+
486+
Serializer.serializeError(new MyCustomError());
487+
```
488+
489+
The result will be:
490+
491+
```JSON
492+
{
493+
"errors": [
494+
{
495+
"id": 123,
496+
"links": {
497+
"about": "https://example.com/errors/123"
498+
},
446499
"status": "500",
447-
"detail": "An error occured"
500+
"code": "xyz",
501+
"title": "MyCustomError",
502+
"detail": "Something went wrong",
503+
"meta": {
504+
"time": 1593561258853
505+
}
506+
}
507+
]
508+
}
509+
```
510+
511+
Using a POJO:
512+
513+
```js
514+
Serializer.serializeError({
515+
id: 123,
516+
links: {
517+
about: 'https://example.com/errors/123'
518+
},
519+
status: 500, // or `statusCode`
520+
code: 'xyz',
521+
title: 'UserNotFound',
522+
detail: 'Unable to find a user with the provided ID',
523+
meta: {
524+
time: Date.now()
525+
}
526+
});
527+
```
528+
529+
The result will be:
530+
531+
```JSON
532+
{
533+
"errors": [
534+
{
535+
"id": 123,
536+
"links": {
537+
"about": "https://example.com/errors/123"
538+
},
539+
"status": "500",
540+
"code": "xyz",
541+
"title": "UserNotFound",
542+
"detail": "Unable to find a user with the provided ID",
543+
"meta": {
544+
"time": 1593561258853
545+
}
448546
}
449547
]
450548
}

lib/JSONAPISerializer.js

+5-37
Original file line numberDiff line numberDiff line change
@@ -383,51 +383,19 @@ module.exports = class JSONAPISerializer {
383383
/**
384384
* Serialize any error into a JSON API error document.
385385
* Input data can be:
386-
* - An Error or an array of Error.
387-
* - A JSON API error object or an array of JSON API error object.
386+
* - An `Error` or an array of `Error` instances.
387+
* - A JSON API error object or an array of JSON API error objects.
388388
*
389389
* @see {@link http://jsonapi.org/format/#errors}
390390
* @function JSONAPISerializer#serializeError
391391
* @param {Error|Error[]|object|object[]} error an Error, an array of Error, a JSON API error object, an array of JSON API error object.
392-
* @returns {Promise} resolves with serialized error.
392+
* @returns {object} resolves with serialized error.
393393
*/
394394
serializeError(error) {
395-
/**
396-
* An Error object enhanced with status or/and custom code properties.
397-
*
398-
* @typedef {Error} ErrorWithStatus
399-
* @property {string} [status] status code error
400-
* @property {string} [code] code error
401-
*/
402-
403-
/**
404-
* @private
405-
* @param {Error|ErrorWithStatus|object} err an Error, a JSON API error object or an ErrorWithStatus.
406-
* @returns {object} valid JSON API error.
407-
*/
408-
function convertToError(err) {
409-
let serializedError;
410-
411-
if (err instanceof Error) {
412-
const status = err.status || err.statusCode;
413-
414-
serializedError = {
415-
status: status && status.toString(),
416-
code: err.code,
417-
title: err.title || err.constructor.name,
418-
detail: err.message,
419-
};
420-
} else {
421-
serializedError = validateError(err);
422-
}
423-
424-
return serializedError;
425-
}
426-
427395
return {
428396
errors: Array.isArray(error)
429-
? error.map((err) => convertToError(err))
430-
: [convertToError(error)],
397+
? error.map((err) => validateError(err))
398+
: [validateError(error)]
431399
};
432400
}
433401

0 commit comments

Comments
 (0)