Skip to content

Commit

Permalink
feat(errors): allow for custom source and meta properties in a er…
Browse files Browse the repository at this point in the history
…ror response

Signed-off-by: Jeremy Trufier <jeremy@trufier.com>
  • Loading branch information
Tronix117 committed Nov 15, 2017
1 parent 8c305cb commit c250d4c
Show file tree
Hide file tree
Showing 3 changed files with 107 additions and 12 deletions.
39 changes: 39 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -439,6 +439,45 @@ module.exports = function (MyModel) {
}
```

## Custom Errors
Generic errors respond with a 500, but sometime you want to have a better control over the error that is returned to the client, taking advantages of fields provided by JSONApi.

**It is recommanded to extend the base Error constructor, before throwing errors, ex: BadRequestError**

`meta` and `source` fields needs to be objects.

#### example
```js
module.exports = function (MyModel) {
MyModel.find = function () {
var err = new Error('April 1st, 1998');

err.status = 418;
err.name = 'I\'m a teapot';
err.source = { model: 'Post', method: 'find' };
err.detail = 'April 1st, 1998';
err.code = 'i\'m a teapot';
err.meta = { rfc: 'RFC2324' };

throw err
}
}

// This will be returned as :
// {
// errors: [
// {
// status: 418,
// meta: { rfc: 'RFC2324' },
// code: 'i\'m a teapot',
// detail: 'April 1st, 1998',
// title: 'I\'m a teapot',
// source: { model: 'Post', method: 'find' }
// }
// ]
// }
```

##### function parameters

- `options` All config options set for the deserialization process.
Expand Down
35 changes: 26 additions & 9 deletions lib/errors.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
var debug
var errorStackInResponse
var statusCodes = require('http-status-codes')
var _ = require('lodash')

module.exports = function (app, options) {
debug = options.debug
Expand Down Expand Up @@ -51,7 +52,7 @@ function JSONAPIErrorHandler (err, req, res, next) {
err.details.messages[key][0],
err.details.codes[key][0],
err.name,
key
{ pointer: 'data/attributes/' + key }
)
})
} else if (err.message) {
Expand Down Expand Up @@ -81,8 +82,24 @@ function JSONAPIErrorHandler (err, req, res, next) {
err.name = 'BadRequest'
}

var errorSource = err.source && typeof err.source === 'object'
? err.source
: {}
if (errorStackInResponse) {
// We do not want to mutate err.source, so we clone it first
errorSource = _.clone(errorSource)
errorSource.stack = err.stack
}

errors.push(
buildErrorResponse(statusCode, err.message, err.code, err.name, null, err.stack)
buildErrorResponse(
statusCode,
err.message,
err.code,
err.name,
errorSource,
err.meta
)
)
} else {
debug(
Expand Down Expand Up @@ -113,28 +130,28 @@ function JSONAPIErrorHandler (err, req, res, next) {
* @param {String} errorDetail error message for the user, human readable
* @param {String} errorCode internal system error code
* @param {String} errorName error title for the user, human readable
* @param {String} propertyName for validation errors, name of property validation refers to
* @param {String} errorStack Some debbuging informations
* @param {String} errorSource Some informations about the source of the issue
* @param {String} errorMeta Some custom metas informations to give to the error response
* @return {Object}
*/
function buildErrorResponse (
httpStatusCode,
errorDetail,
errorCode,
errorName,
propertyName,
errorStack
errorSource,
errorMeta
) {
var out = {
status: httpStatusCode || statusCodes.INTERNAL_SERVER_ERROR,
source: propertyName ? { pointer: 'data/attributes/' + propertyName } : {},
source: errorSource || {},
title: errorName || '',
code: errorCode || '',
detail: errorDetail || ''
}

if (errorStackInResponse && errorStack) {
out.source.stack = errorStack
if (errorMeta && typeof errorMeta === 'object') {
out.meta = errorMeta
}

return out
Expand Down
45 changes: 42 additions & 3 deletions test/errors.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ describe('disabling loopback-component-jsonapi error handler', function () {
request(app).get('/posts/100').end(function (err, res) {
expect(err).to.equal(null)
expect(res.body).to.have.keys('error')
expect(res.body.error).to.have.keys('name', 'message', 'statusCode', 'code')
expect(res.body.error).to.contain.keys('name', 'message', 'statusCode')
done()
})
})
Expand Down Expand Up @@ -180,7 +180,16 @@ describe('loopback json api errors', function () {
})
})

describe('loopback json api errors with `errorStackInResponse` enabled', function () {
describe('loopback json api errors with advanced reporting', function () {
var errorMetaMock = {
status: 418,
meta: { rfc: 'RFC2324' },
code: "i'm a teapot",
detail: 'April 1st, 1998',
title: "I'm a teapot",
source: { model: 'Post', method: 'find' }
}

beforeEach(function () {
app = loopback()
app.set('legacyExplorer', false)
Expand All @@ -190,13 +199,43 @@ describe('loopback json api errors with `errorStackInResponse` enabled', functio
title: String,
content: String
})

Post.find = function () {
var err = new Error(errorMetaMock.detail)
err.name = errorMetaMock.title
err.meta = errorMetaMock.meta
err.source = errorMetaMock.source
err.statusCode = errorMetaMock.status
err.code = errorMetaMock.code
throw err
}

app.model(Post)
app.use(loopback.rest())
JSONAPIComponent(app, { restApiRoot: '', errorStackInResponse: true })
})

it(
'POST /models should return a more specific 422 status code on the error object if type key is not present',
'should return the given meta and source in the error response when an Error with a meta and source object is thrown',
function (done) {
request(app)
.get('/posts')
.set('Content-Type', 'application/json')
.end(function (err, res) {
expect(err).to.equal(null)
expect(res.body).to.have.keys('errors')
expect(res.body.errors.length).to.equal(1)

expect(_.omit(res.body.errors[0], 'source.stack')).to.deep.equal(
errorMetaMock
)
done()
})
}
)

it(
'should return the corresponding stack in error when `errorStackInResponse` enabled',
function (done) {
request(app)
.post('/posts')
Expand Down

0 comments on commit c250d4c

Please sign in to comment.