Skip to content

Commit 65d23fc

Browse files
authored
Add error serialization (#59)
1 parent eaaa46f commit 65d23fc

File tree

3 files changed

+207
-0
lines changed

3 files changed

+207
-0
lines changed

Diff for: README.md

+28
Original file line numberDiff line numberDiff line change
@@ -382,6 +382,34 @@ Serializer.deserializeAsync('article', data)
382382
}
383383
```
384384

385+
### serializeError
386+
387+
Serializes any error into a JSON API error document.
388+
389+
Input data can be:
390+
- An instance of Error or an array of instance of Error.
391+
- 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).
392+
393+
```javascript
394+
const error = new Error('An error occured');
395+
error.status = 500;
396+
397+
Serializer.serializeError(error);
398+
```
399+
400+
The result will be:
401+
402+
```JSON
403+
{
404+
"errors": [
405+
{
406+
"status": "500",
407+
"detail": "An error occured"
408+
}
409+
]
410+
}
411+
```
412+
385413
## Custom schemas
386414

387415
It is possible to define multiple custom schemas for a resource type :

Diff for: lib/JSONAPISerializer.js

+81
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,45 @@ module.exports = class JSONAPISerializer {
106106
return validated.value;
107107
}
108108

109+
/**
110+
* Validate a JSONAPI error object
111+
*
112+
* @method JSONAPISerializer#validateError
113+
* @private
114+
* @param {Object} err a JSONAPI error object
115+
* @return {Object}
116+
*/
117+
validateError(err) {
118+
const errorSchema = joi.object({
119+
id: joi.string(),
120+
links: joi.object({
121+
about: joi.alternatives([
122+
joi.string(),
123+
joi.object({
124+
href: joi.string(),
125+
meta: joi.object(),
126+
})]),
127+
}),
128+
status: joi.string(),
129+
code: joi.string(),
130+
title: joi.string(),
131+
detail: joi.string(),
132+
source: joi.object({
133+
pointer: joi.string(),
134+
parameter: joi.string(),
135+
}),
136+
meta: joi.object(),
137+
}).required();
138+
139+
const validated = joi.validate(err, errorSchema, { convert: true });
140+
141+
if (validated.error) {
142+
throw new Error(validated.error);
143+
}
144+
145+
return validated.value;
146+
}
147+
109148
/**
110149
* Register a resource with its type, schema name, and configuration options.
111150
*
@@ -352,6 +391,48 @@ module.exports = class JSONAPISerializer {
352391
});
353392
}
354393

394+
/**
395+
* Serialize any error into a JSON API error document.
396+
* Input data can be:
397+
* - An Error or an array of Error.
398+
* - A JSON API error object or an array of JSON API error object.
399+
*
400+
* @see {@link http://jsonapi.org/format/#errors}
401+
* @method JSONAPISerializer#serializeError
402+
* @param {Error|Error[]|Object|Object[]} error an Error, an array of Error, a JSON API error object, an array of JSON API error object
403+
* @return {Promise} resolves with serialized error.
404+
*/
405+
serializeError(error) {
406+
function convertToError(err) {
407+
let serializedError;
408+
if (err instanceof Error) {
409+
const status = err.status || err.statusCode;
410+
411+
serializedError = {
412+
status: status && status.toString(),
413+
code: err.code,
414+
detail: err.message,
415+
};
416+
} else {
417+
serializedError = this.validateError(err);
418+
}
419+
420+
return serializedError;
421+
}
422+
423+
const convertError = convertToError.bind(this);
424+
425+
if (Array.isArray(error)) {
426+
return {
427+
errors: error.map(err => convertError(err)),
428+
};
429+
}
430+
431+
return {
432+
errors: [convertError(error)],
433+
};
434+
}
435+
355436
/**
356437
* Deserialize a single JSON API resource.
357438
* Input data must be a simple object.

Diff for: test/unit/JSONAPISerializer.test.js

+98
Original file line numberDiff line numberDiff line change
@@ -1848,4 +1848,102 @@ describe('JSONAPISerializer', function() {
18481848
done();
18491849
});
18501850
});
1851+
1852+
describe('serializeError', function() {
1853+
const Serializer = new JSONAPISerializer();
1854+
1855+
it('should return serialized error with an instance of Error', function(done) {
1856+
const error = new Error('An error occured');
1857+
error.status = 500;
1858+
error.code = 'ERROR';
1859+
1860+
const serializedError = Serializer.serializeError(error);
1861+
1862+
console.log(JSON.stringify(serializedError));
1863+
1864+
expect(serializedError).to.have.property('errors').to.be.instanceof(Array).to.have.lengthOf(1);
1865+
expect(serializedError.errors[0]).to.have.property('status').to.eql('500');
1866+
expect(serializedError.errors[0]).to.have.property('code').to.eql('ERROR');
1867+
expect(serializedError.errors[0]).to.have.property('detail').to.eql('An error occured');
1868+
1869+
done();
1870+
});
1871+
1872+
it('should return serialized errors with an array of Error', function(done) {
1873+
const errors = [new Error('First Error'), new Error('Second Error')];
1874+
1875+
const serializedErrors = Serializer.serializeError(errors);
1876+
1877+
expect(serializedErrors).to.have.property('errors').to.be.instanceof(Array).to.have.lengthOf(2);
1878+
expect(serializedErrors.errors[0]).to.have.property('detail').to.eql('First Error');
1879+
expect(serializedErrors.errors[1]).to.have.property('detail').to.eql('Second Error');
1880+
1881+
done();
1882+
});
1883+
1884+
it('should return a validation error when serializing error with a malformed JSON API error object', function(done) {
1885+
const jsonapiError = {
1886+
status: '422',
1887+
source: 'malformed attribute'
1888+
};
1889+
1890+
expect(function() {
1891+
Serializer.serializeError(jsonapiError);
1892+
}).to.throw(Error);
1893+
1894+
done();
1895+
});
1896+
1897+
it('should return serialized error with a JSON API error object', function(done) {
1898+
const jsonapiError = {
1899+
status: '422',
1900+
source: { pointer: '/data/attributes/error' },
1901+
title: 'Error',
1902+
detail: 'An error occured'
1903+
};
1904+
1905+
const serializedError = Serializer.serializeError(jsonapiError);
1906+
1907+
expect(serializedError).to.have.property('errors').to.be.instanceof(Array).to.have.lengthOf(1);
1908+
expect(serializedError.errors[0]).to.deep.eql({
1909+
status: '422',
1910+
source: { pointer: '/data/attributes/error' },
1911+
title: 'Error',
1912+
detail: 'An error occured'
1913+
});
1914+
1915+
done();
1916+
});
1917+
1918+
it('should return serialized error with an array of JSON API error object', function(done) {
1919+
const jsonapiErrors = [{
1920+
status: '422',
1921+
source: { pointer: '/data/attributes/first-error' },
1922+
title: 'First Error',
1923+
detail: 'First Error'
1924+
}, {
1925+
status: '422',
1926+
source: { pointer: '/data/attributes/second-error' },
1927+
title: 'Second Error',
1928+
detail: 'Second Error'
1929+
}];
1930+
1931+
const serializedError = Serializer.serializeError(jsonapiErrors);
1932+
1933+
expect(serializedError).to.have.property('errors').to.be.instanceof(Array).to.have.lengthOf(2);
1934+
expect(serializedError.errors).to.deep.eql([{
1935+
status: '422',
1936+
source: { pointer: '/data/attributes/first-error' },
1937+
title: 'First Error',
1938+
detail: 'First Error'
1939+
}, {
1940+
status: '422',
1941+
source: { pointer: '/data/attributes/second-error' },
1942+
title: 'Second Error',
1943+
detail: 'Second Error'
1944+
}]);
1945+
1946+
done();
1947+
});
1948+
})
18511949
});

0 commit comments

Comments
 (0)