Skip to content

Commit c250d4c

Browse files
committed
feat(errors): allow for custom source and meta properties in a error response
Signed-off-by: Jeremy Trufier <[email protected]>
1 parent 8c305cb commit c250d4c

File tree

3 files changed

+107
-12
lines changed

3 files changed

+107
-12
lines changed

README.md

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -439,6 +439,45 @@ module.exports = function (MyModel) {
439439
}
440440
```
441441

442+
## Custom Errors
443+
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.
444+
445+
**It is recommanded to extend the base Error constructor, before throwing errors, ex: BadRequestError**
446+
447+
`meta` and `source` fields needs to be objects.
448+
449+
#### example
450+
```js
451+
module.exports = function (MyModel) {
452+
MyModel.find = function () {
453+
var err = new Error('April 1st, 1998');
454+
455+
err.status = 418;
456+
err.name = 'I\'m a teapot';
457+
err.source = { model: 'Post', method: 'find' };
458+
err.detail = 'April 1st, 1998';
459+
err.code = 'i\'m a teapot';
460+
err.meta = { rfc: 'RFC2324' };
461+
462+
throw err
463+
}
464+
}
465+
466+
// This will be returned as :
467+
// {
468+
// errors: [
469+
// {
470+
// status: 418,
471+
// meta: { rfc: 'RFC2324' },
472+
// code: 'i\'m a teapot',
473+
// detail: 'April 1st, 1998',
474+
// title: 'I\'m a teapot',
475+
// source: { model: 'Post', method: 'find' }
476+
// }
477+
// ]
478+
// }
479+
```
480+
442481
##### function parameters
443482

444483
- `options` All config options set for the deserialization process.

lib/errors.js

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
var debug
44
var errorStackInResponse
55
var statusCodes = require('http-status-codes')
6+
var _ = require('lodash')
67

78
module.exports = function (app, options) {
89
debug = options.debug
@@ -51,7 +52,7 @@ function JSONAPIErrorHandler (err, req, res, next) {
5152
err.details.messages[key][0],
5253
err.details.codes[key][0],
5354
err.name,
54-
key
55+
{ pointer: 'data/attributes/' + key }
5556
)
5657
})
5758
} else if (err.message) {
@@ -81,8 +82,24 @@ function JSONAPIErrorHandler (err, req, res, next) {
8182
err.name = 'BadRequest'
8283
}
8384

85+
var errorSource = err.source && typeof err.source === 'object'
86+
? err.source
87+
: {}
88+
if (errorStackInResponse) {
89+
// We do not want to mutate err.source, so we clone it first
90+
errorSource = _.clone(errorSource)
91+
errorSource.stack = err.stack
92+
}
93+
8494
errors.push(
85-
buildErrorResponse(statusCode, err.message, err.code, err.name, null, err.stack)
95+
buildErrorResponse(
96+
statusCode,
97+
err.message,
98+
err.code,
99+
err.name,
100+
errorSource,
101+
err.meta
102+
)
86103
)
87104
} else {
88105
debug(
@@ -113,28 +130,28 @@ function JSONAPIErrorHandler (err, req, res, next) {
113130
* @param {String} errorDetail error message for the user, human readable
114131
* @param {String} errorCode internal system error code
115132
* @param {String} errorName error title for the user, human readable
116-
* @param {String} propertyName for validation errors, name of property validation refers to
117-
* @param {String} errorStack Some debbuging informations
133+
* @param {String} errorSource Some informations about the source of the issue
134+
* @param {String} errorMeta Some custom metas informations to give to the error response
118135
* @return {Object}
119136
*/
120137
function buildErrorResponse (
121138
httpStatusCode,
122139
errorDetail,
123140
errorCode,
124141
errorName,
125-
propertyName,
126-
errorStack
142+
errorSource,
143+
errorMeta
127144
) {
128145
var out = {
129146
status: httpStatusCode || statusCodes.INTERNAL_SERVER_ERROR,
130-
source: propertyName ? { pointer: 'data/attributes/' + propertyName } : {},
147+
source: errorSource || {},
131148
title: errorName || '',
132149
code: errorCode || '',
133150
detail: errorDetail || ''
134151
}
135152

136-
if (errorStackInResponse && errorStack) {
137-
out.source.stack = errorStack
153+
if (errorMeta && typeof errorMeta === 'object') {
154+
out.meta = errorMeta
138155
}
139156

140157
return out

test/errors.test.js

Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ describe('disabling loopback-component-jsonapi error handler', function () {
2525
request(app).get('/posts/100').end(function (err, res) {
2626
expect(err).to.equal(null)
2727
expect(res.body).to.have.keys('error')
28-
expect(res.body.error).to.have.keys('name', 'message', 'statusCode', 'code')
28+
expect(res.body.error).to.contain.keys('name', 'message', 'statusCode')
2929
done()
3030
})
3131
})
@@ -180,7 +180,16 @@ describe('loopback json api errors', function () {
180180
})
181181
})
182182

183-
describe('loopback json api errors with `errorStackInResponse` enabled', function () {
183+
describe('loopback json api errors with advanced reporting', function () {
184+
var errorMetaMock = {
185+
status: 418,
186+
meta: { rfc: 'RFC2324' },
187+
code: "i'm a teapot",
188+
detail: 'April 1st, 1998',
189+
title: "I'm a teapot",
190+
source: { model: 'Post', method: 'find' }
191+
}
192+
184193
beforeEach(function () {
185194
app = loopback()
186195
app.set('legacyExplorer', false)
@@ -190,13 +199,43 @@ describe('loopback json api errors with `errorStackInResponse` enabled', functio
190199
title: String,
191200
content: String
192201
})
202+
203+
Post.find = function () {
204+
var err = new Error(errorMetaMock.detail)
205+
err.name = errorMetaMock.title
206+
err.meta = errorMetaMock.meta
207+
err.source = errorMetaMock.source
208+
err.statusCode = errorMetaMock.status
209+
err.code = errorMetaMock.code
210+
throw err
211+
}
212+
193213
app.model(Post)
194214
app.use(loopback.rest())
195215
JSONAPIComponent(app, { restApiRoot: '', errorStackInResponse: true })
196216
})
197217

198218
it(
199-
'POST /models should return a more specific 422 status code on the error object if type key is not present',
219+
'should return the given meta and source in the error response when an Error with a meta and source object is thrown',
220+
function (done) {
221+
request(app)
222+
.get('/posts')
223+
.set('Content-Type', 'application/json')
224+
.end(function (err, res) {
225+
expect(err).to.equal(null)
226+
expect(res.body).to.have.keys('errors')
227+
expect(res.body.errors.length).to.equal(1)
228+
229+
expect(_.omit(res.body.errors[0], 'source.stack')).to.deep.equal(
230+
errorMetaMock
231+
)
232+
done()
233+
})
234+
}
235+
)
236+
237+
it(
238+
'should return the corresponding stack in error when `errorStackInResponse` enabled',
200239
function (done) {
201240
request(app)
202241
.post('/posts')

0 commit comments

Comments
 (0)