Skip to content

Commit 8c305cb

Browse files
committed
feat(error debug) #125 - Return a debug key containing the stack when an unknown error occured if debug is enabled
Signed-off-by: Jeremy Trufier <[email protected]>
1 parent 399e732 commit 8c305cb

File tree

3 files changed

+84
-7
lines changed

3 files changed

+84
-7
lines changed

README.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ Example:
118118
"host": "https://www.mydomain.com",
119119
"enable": true,
120120
"handleErrors": true,
121+
"errorStackInResponse": false,
121122
"exclude": [
122123
{"model": "comment"},
123124
{"methods": "find"},
@@ -201,6 +202,25 @@ out of the box with EmberJS.
201202
- Type: `boolean`
202203
- Default: `true`
203204

205+
### errorStackInResponse
206+
Along handleErrors, When true, this option will send the error stack if available within the error
207+
reponse. It will be stored under the `source.stack` key.
208+
209+
**Be careful, this option should never be enabled in production environment. It can propagate some
210+
sensitive datas.**
211+
212+
#### example
213+
```js
214+
{
215+
...
216+
"errorStackInResponse": NODE_ENV === 'development',
217+
...
218+
}
219+
```
220+
221+
- Type: `boolean`
222+
- Default: `false`
223+
204224
### exclude
205225
Allows blacklisting of models and methods.
206226
Define an array of blacklist objects. Blacklist objects can contain "model" key

lib/errors.js

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
'use strict'
22

33
var debug
4+
var errorStackInResponse
45
var statusCodes = require('http-status-codes')
56

67
module.exports = function (app, options) {
78
debug = options.debug
9+
errorStackInResponse = options.errorStackInResponse
810

911
if (options.handleErrors !== false) {
1012
debug(
@@ -80,7 +82,7 @@ function JSONAPIErrorHandler (err, req, res, next) {
8082
}
8183

8284
errors.push(
83-
buildErrorResponse(statusCode, err.message, err.code, err.name)
85+
buildErrorResponse(statusCode, err.message, err.code, err.name, null, err.stack)
8486
)
8587
} else {
8688
debug(
@@ -112,20 +114,28 @@ function JSONAPIErrorHandler (err, req, res, next) {
112114
* @param {String} errorCode internal system error code
113115
* @param {String} errorName error title for the user, human readable
114116
* @param {String} propertyName for validation errors, name of property validation refers to
117+
* @param {String} errorStack Some debbuging informations
115118
* @return {Object}
116119
*/
117120
function buildErrorResponse (
118121
httpStatusCode,
119122
errorDetail,
120123
errorCode,
121124
errorName,
122-
propertyName
125+
propertyName,
126+
errorStack
123127
) {
124-
return {
128+
var out = {
125129
status: httpStatusCode || statusCodes.INTERNAL_SERVER_ERROR,
126-
source: propertyName ? { pointer: 'data/attributes/' + propertyName } : '',
130+
source: propertyName ? { pointer: 'data/attributes/' + propertyName } : {},
127131
title: errorName || '',
128132
code: errorCode || '',
129133
detail: errorDetail || ''
130134
}
135+
136+
if (errorStackInResponse && errorStack) {
137+
out.source.stack = errorStack
138+
}
139+
140+
return out
131141
}

test/errors.test.js

Lines changed: 50 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
var request = require('supertest')
44
var loopback = require('loopback')
5+
var _ = require('lodash')
56
var expect = require('chai').expect
67
var JSONAPIComponent = require('../')
78
var app
@@ -24,7 +25,7 @@ describe('disabling loopback-component-jsonapi error handler', function () {
2425
request(app).get('/posts/100').end(function (err, res) {
2526
expect(err).to.equal(null)
2627
expect(res.body).to.have.keys('error')
27-
expect(res.body.error).to.have.keys('name', 'message', 'statusCode')
28+
expect(res.body.error).to.have.keys('name', 'message', 'statusCode', 'code')
2829
done()
2930
})
3031
})
@@ -87,7 +88,7 @@ describe('loopback json api errors', function () {
8788
status: 404,
8889
code: 'MODEL_NOT_FOUND',
8990
detail: 'Unknown "post" id "100".',
90-
source: '',
91+
source: {},
9192
title: 'Error'
9293
})
9394
done()
@@ -129,7 +130,7 @@ describe('loopback json api errors', function () {
129130
status: 422,
130131
code: 'presence',
131132
detail: 'JSON API resource object must contain `data.type` property',
132-
source: '',
133+
source: {},
133134
title: 'ValidationError'
134135
})
135136
done()
@@ -178,3 +179,49 @@ describe('loopback json api errors', function () {
178179
)
179180
})
180181
})
182+
183+
describe('loopback json api errors with `errorStackInResponse` enabled', function () {
184+
beforeEach(function () {
185+
app = loopback()
186+
app.set('legacyExplorer', false)
187+
var ds = loopback.createDataSource('memory')
188+
Post = ds.createModel('post', {
189+
id: { type: Number, id: true },
190+
title: String,
191+
content: String
192+
})
193+
app.model(Post)
194+
app.use(loopback.rest())
195+
JSONAPIComponent(app, { restApiRoot: '', errorStackInResponse: true })
196+
})
197+
198+
it(
199+
'POST /models should return a more specific 422 status code on the error object if type key is not present',
200+
function (done) {
201+
request(app)
202+
.post('/posts')
203+
.send({
204+
data: {
205+
attributes: { title: 'my post', content: 'my post content' }
206+
}
207+
})
208+
.set('Content-Type', 'application/json')
209+
.end(function (err, res) {
210+
expect(err).to.equal(null)
211+
expect(res.body).to.have.keys('errors')
212+
expect(res.body.errors.length).to.equal(1)
213+
214+
expect(res.body.errors[0].source).to.haveOwnProperty('stack')
215+
expect(res.body.errors[0].source.stack.length).to.be.above(100)
216+
217+
expect(_.omit(res.body.errors[0], 'source')).to.deep.equal({
218+
status: 422,
219+
code: 'presence',
220+
detail: 'JSON API resource object must contain `data.type` property',
221+
title: 'ValidationError'
222+
})
223+
done()
224+
})
225+
}
226+
)
227+
})

0 commit comments

Comments
 (0)