Skip to content

Commit

Permalink
Add error serialization (#59)
Browse files Browse the repository at this point in the history
  • Loading branch information
danivek authored Dec 6, 2018
1 parent eaaa46f commit 65d23fc
Show file tree
Hide file tree
Showing 3 changed files with 207 additions and 0 deletions.
28 changes: 28 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -382,6 +382,34 @@ Serializer.deserializeAsync('article', data)
}
```

### serializeError

Serializes any error into a JSON API error document.

Input data can be:
- An instance of Error or an array of instance of Error.
- 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).

```javascript
const error = new Error('An error occured');
error.status = 500;

Serializer.serializeError(error);
```

The result will be:

```JSON
{
"errors": [
{
"status": "500",
"detail": "An error occured"
}
]
}
```

## Custom schemas

It is possible to define multiple custom schemas for a resource type :
Expand Down
81 changes: 81 additions & 0 deletions lib/JSONAPISerializer.js
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,45 @@ module.exports = class JSONAPISerializer {
return validated.value;
}

/**
* Validate a JSONAPI error object
*
* @method JSONAPISerializer#validateError
* @private
* @param {Object} err a JSONAPI error object
* @return {Object}
*/
validateError(err) {
const errorSchema = joi.object({
id: joi.string(),
links: joi.object({
about: joi.alternatives([
joi.string(),
joi.object({
href: joi.string(),
meta: joi.object(),
})]),
}),
status: joi.string(),
code: joi.string(),
title: joi.string(),
detail: joi.string(),
source: joi.object({
pointer: joi.string(),
parameter: joi.string(),
}),
meta: joi.object(),
}).required();

const validated = joi.validate(err, errorSchema, { convert: true });

if (validated.error) {
throw new Error(validated.error);
}

return validated.value;
}

/**
* Register a resource with its type, schema name, and configuration options.
*
Expand Down Expand Up @@ -352,6 +391,48 @@ module.exports = class JSONAPISerializer {
});
}

/**
* Serialize any error into a JSON API error document.
* Input data can be:
* - An Error or an array of Error.
* - A JSON API error object or an array of JSON API error object.
*
* @see {@link http://jsonapi.org/format/#errors}
* @method JSONAPISerializer#serializeError
* @param {Error|Error[]|Object|Object[]} error an Error, an array of Error, a JSON API error object, an array of JSON API error object
* @return {Promise} resolves with serialized error.
*/
serializeError(error) {
function convertToError(err) {
let serializedError;
if (err instanceof Error) {
const status = err.status || err.statusCode;

serializedError = {
status: status && status.toString(),
code: err.code,
detail: err.message,
};
} else {
serializedError = this.validateError(err);
}

return serializedError;
}

const convertError = convertToError.bind(this);

if (Array.isArray(error)) {
return {
errors: error.map(err => convertError(err)),
};
}

return {
errors: [convertError(error)],
};
}

/**
* Deserialize a single JSON API resource.
* Input data must be a simple object.
Expand Down
98 changes: 98 additions & 0 deletions test/unit/JSONAPISerializer.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1848,4 +1848,102 @@ describe('JSONAPISerializer', function() {
done();
});
});

describe('serializeError', function() {
const Serializer = new JSONAPISerializer();

it('should return serialized error with an instance of Error', function(done) {
const error = new Error('An error occured');
error.status = 500;
error.code = 'ERROR';

const serializedError = Serializer.serializeError(error);

console.log(JSON.stringify(serializedError));

expect(serializedError).to.have.property('errors').to.be.instanceof(Array).to.have.lengthOf(1);
expect(serializedError.errors[0]).to.have.property('status').to.eql('500');
expect(serializedError.errors[0]).to.have.property('code').to.eql('ERROR');
expect(serializedError.errors[0]).to.have.property('detail').to.eql('An error occured');

done();
});

it('should return serialized errors with an array of Error', function(done) {
const errors = [new Error('First Error'), new Error('Second Error')];

const serializedErrors = Serializer.serializeError(errors);

expect(serializedErrors).to.have.property('errors').to.be.instanceof(Array).to.have.lengthOf(2);
expect(serializedErrors.errors[0]).to.have.property('detail').to.eql('First Error');
expect(serializedErrors.errors[1]).to.have.property('detail').to.eql('Second Error');

done();
});

it('should return a validation error when serializing error with a malformed JSON API error object', function(done) {
const jsonapiError = {
status: '422',
source: 'malformed attribute'
};

expect(function() {
Serializer.serializeError(jsonapiError);
}).to.throw(Error);

done();
});

it('should return serialized error with a JSON API error object', function(done) {
const jsonapiError = {
status: '422',
source: { pointer: '/data/attributes/error' },
title: 'Error',
detail: 'An error occured'
};

const serializedError = Serializer.serializeError(jsonapiError);

expect(serializedError).to.have.property('errors').to.be.instanceof(Array).to.have.lengthOf(1);
expect(serializedError.errors[0]).to.deep.eql({
status: '422',
source: { pointer: '/data/attributes/error' },
title: 'Error',
detail: 'An error occured'
});

done();
});

it('should return serialized error with an array of JSON API error object', function(done) {
const jsonapiErrors = [{
status: '422',
source: { pointer: '/data/attributes/first-error' },
title: 'First Error',
detail: 'First Error'
}, {
status: '422',
source: { pointer: '/data/attributes/second-error' },
title: 'Second Error',
detail: 'Second Error'
}];

const serializedError = Serializer.serializeError(jsonapiErrors);

expect(serializedError).to.have.property('errors').to.be.instanceof(Array).to.have.lengthOf(2);
expect(serializedError.errors).to.deep.eql([{
status: '422',
source: { pointer: '/data/attributes/first-error' },
title: 'First Error',
detail: 'First Error'
}, {
status: '422',
source: { pointer: '/data/attributes/second-error' },
title: 'Second Error',
detail: 'Second Error'
}]);

done();
});
})
});

0 comments on commit 65d23fc

Please sign in to comment.