Skip to content

Commit 7c962f4

Browse files
committed
Include the "type" property on all generated errors
closes #122
1 parent a7e7937 commit 7c962f4

File tree

7 files changed

+213
-26
lines changed

7 files changed

+213
-26
lines changed

HISTORY.md

+1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ unreleased
33

44
* Fix JSON strict violation error to match native parse error
55
* Include the `body` property on verify errors
6+
* Include the `type` property on all generated errors
67
* Use `http-errors` to set status code on errors
78
89

README.md

+19-10
Original file line numberDiff line numberDiff line change
@@ -275,8 +275,9 @@ encoding of the request. The parsing can be aborted by throwing an error.
275275
The middlewares provided by this module create errors depending on the error
276276
condition during parsing. The errors will typically have a `status`/`statusCode`
277277
property that contains the suggested HTTP response code, an `expose` property
278-
to determine if the `message` property should be displayed to the client and a
279-
`body` property containing the read body, if available.
278+
to determine if the `message` property should be displayed to the client, a
279+
`type` property to determine the type of error without matching against the
280+
`message`, and a `body` property containing the read body, if available.
280281

281282
The following are the common errors emitted, though any error can come through
282283
for various reasons.
@@ -285,54 +286,62 @@ for various reasons.
285286

286287
This error will occur when the request had a `Content-Encoding` header that
287288
contained an encoding but the "inflation" option was set to `false`. The
288-
`status` property is set to `415`.
289+
`status` property is set to `415`, the `type` property is set to
290+
`'encoding.unsupported'`, and the `charset` property will be set to the
291+
encoding that is unsupported.
289292

290293
### request aborted
291294

292295
This error will occur when the request is aborted by the client before reading
293296
the body has finished. The `received` property will be set to the number of
294297
bytes received before the request was aborted and the `expected` property is
295-
set to the number of expected bytes. The `status` property is set to `400`.
298+
set to the number of expected bytes. The `status` property is set to `400`
299+
and `type` property is set to `'request.aborted'`.
296300

297301
### request entity too large
298302

299303
This error will occur when the request body's size is larger than the "limit"
300304
option. The `limit` property will be set to the byte limit and the `length`
301305
property will be set to the request body's length. The `status` property is
302-
set to `413`.
306+
set to `413` and the `type` property is set to `'entity.too.large'`.
303307

304308
### request size did not match content length
305309

306310
This error will occur when the request's length did not match the length from
307311
the `Content-Length` header. This typically occurs when the request is malformed,
308312
typically when the `Content-Length` header was calculated based on characters
309-
instead of bytes. The `status` property is set to `400`.
313+
instead of bytes. The `status` property is set to `400` and the `type` property
314+
is set to `'request.size.invalid'`.
310315

311316
### stream encoding should not be set
312317

313318
This error will occur when something called the `req.setEncoding` method prior
314319
to this middleware. This module operates directly on bytes only and you cannot
315320
call `req.setEncoding` when using this module. The `status` property is set to
316-
`500`.
321+
`500` and the `type` property is set to `'stream.encoding.set'`.
317322

318323
### too many parameters
319324

320325
This error will occur when the content of the request exceeds the configured
321326
`parameterLimit` for the `urlencoded` parser. The `status` property is set to
322-
`413`.
327+
`413` and the `type` property is set to `'parameters.too.many'`.
323328

324329
### unsupported charset "BOGUS"
325330

326331
This error will occur when the request had a charset parameter in the
327332
`Content-Type` header, but the `iconv-lite` module does not support it OR the
328333
parser does not support it. The charset is contained in the message as well
329-
as in the `charset` property. The `status` property is set to `415`.
334+
as in the `charset` property. The `status` property is set to `415`, the
335+
`type` property is set to `'charset.unsupported'`, and the `charset` property
336+
is set to the charset that is unsupported.
330337

331338
### unsupported content encoding "bogus"
332339

333340
This error will occur when the request had a `Content-Encoding` header that
334341
contained an unsupported encoding. The encoding is contained in the message
335-
as well as in the `encoding` property. The `status` property is set to `415`.
342+
as well as in the `encoding` property. The `status` property is set to `415`,
343+
the `type` property is set to `'encoding.unsupported'`, and the `encoding`
344+
property is set to the encoding that is unsupported.
336345

337346
## Examples
338347

lib/read.js

+14-6
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,8 @@ function read (req, res, next, parse, debug, options) {
6767
// assert charset is supported
6868
if (opts.encoding === null && encoding !== null && !iconv.encodingExists(encoding)) {
6969
return next(createError(415, 'unsupported charset "' + encoding.toUpperCase() + '"', {
70-
charset: encoding.toLowerCase()
70+
charset: encoding.toLowerCase(),
71+
type: 'charset.unsupported'
7172
}))
7273
}
7374

@@ -78,7 +79,8 @@ function read (req, res, next, parse, debug, options) {
7879
// echo back charset
7980
if (err.type === 'encoding.unsupported') {
8081
err = createError(415, 'unsupported charset "' + encoding.toUpperCase() + '"', {
81-
charset: encoding.toLowerCase()
82+
charset: encoding.toLowerCase(),
83+
type: 'charset.unsupported'
8284
})
8385
}
8486

@@ -97,7 +99,8 @@ function read (req, res, next, parse, debug, options) {
9799
verify(req, res, body, encoding)
98100
} catch (err) {
99101
next(createError(403, err, {
100-
body: body
102+
body: body,
103+
type: err.type || 'entity.verify.failed'
101104
}))
102105
return
103106
}
@@ -113,7 +116,8 @@ function read (req, res, next, parse, debug, options) {
113116
req.body = parse(str)
114117
} catch (err) {
115118
next(createError(400, err, {
116-
body: str
119+
body: str,
120+
type: err.type || 'entity.parse.failed'
117121
}))
118122
return
119123
}
@@ -140,7 +144,10 @@ function contentstream (req, debug, inflate) {
140144
debug('content-encoding "%s"', encoding)
141145

142146
if (inflate === false && encoding !== 'identity') {
143-
throw createError(415, 'content encoding unsupported')
147+
throw createError(415, 'content encoding unsupported', {
148+
encoding: encoding,
149+
type: 'encoding.unsupported'
150+
})
144151
}
145152

146153
switch (encoding) {
@@ -160,7 +167,8 @@ function contentstream (req, debug, inflate) {
160167
break
161168
default:
162169
throw createError(415, 'unsupported content encoding "' + encoding + '"', {
163-
encoding: encoding
170+
encoding: encoding,
171+
type: 'encoding.unsupported'
164172
})
165173
}
166174

lib/types/json.js

+42-5
Original file line numberDiff line numberDiff line change
@@ -84,8 +84,14 @@ function json (options) {
8484
}
8585
}
8686

87-
debug('parse json')
88-
return JSON.parse(body, reviver)
87+
try {
88+
debug('parse json')
89+
return JSON.parse(body, reviver)
90+
} catch (e) {
91+
throw normalizeJsonSyntaxError(e, {
92+
stack: e.stack
93+
})
94+
}
8995
}
9096

9197
return function jsonParser (req, res, next) {
@@ -118,7 +124,8 @@ function json (options) {
118124
if (charset.substr(0, 4) !== 'utf-') {
119125
debug('invalid charset')
120126
next(createError(415, 'unsupported charset "' + charset.toUpperCase() + '"', {
121-
charset: charset
127+
charset: charset,
128+
type: 'charset.unsupported'
122129
}))
123130
return
124131
}
@@ -149,8 +156,10 @@ function createStrictSyntaxError (str, char) {
149156
try {
150157
JSON.parse(partial); /* istanbul ignore next */ throw new SyntaxError('strict violation')
151158
} catch (e) {
152-
e.message = e.message.replace('#', char)
153-
return e
159+
return normalizeJsonSyntaxError(e, {
160+
message: e.message.replace('#', char),
161+
stack: e.stack
162+
})
154163
}
155164
}
156165

@@ -181,6 +190,34 @@ function getCharset (req) {
181190
}
182191
}
183192

193+
/**
194+
* Normalize a SyntaxError for JSON.parse.
195+
*
196+
* @param {SyntaxError} error
197+
* @param {object} obj
198+
* @return {SyntaxError}
199+
*/
200+
201+
function normalizeJsonSyntaxError (error, obj) {
202+
var keys = Object.getOwnPropertyNames(error)
203+
204+
for (var i = 0; i < keys.length; i++) {
205+
var key = keys[i]
206+
if (key !== 'stack' && key !== 'message') {
207+
delete error[key]
208+
}
209+
}
210+
211+
var props = Object.keys(obj)
212+
213+
for (var j = 0; j < props.length; j++) {
214+
var prop = props[j]
215+
error[prop] = obj[prop]
216+
}
217+
218+
return error
219+
}
220+
184221
/**
185222
* Get the simple type checker.
186223
*

lib/types/urlencoded.js

+8-3
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,8 @@ function urlencoded (options) {
106106
if (charset !== 'utf-8') {
107107
debug('invalid charset')
108108
next(createError(415, 'unsupported charset "' + charset.toUpperCase() + '"', {
109-
charset: charset
109+
charset: charset,
110+
type: 'charset.unsupported'
110111
}))
111112
return
112113
}
@@ -147,7 +148,9 @@ function extendedparser (options) {
147148

148149
if (paramCount === undefined) {
149150
debug('too many parameters')
150-
throw createError(413, 'too many parameters')
151+
throw createError(413, 'too many parameters', {
152+
type: 'parameters.too.many'
153+
})
151154
}
152155

153156
var arrayLimit = Math.max(100, paramCount)
@@ -257,7 +260,9 @@ function simpleparser (options) {
257260

258261
if (paramCount === undefined) {
259262
debug('too many parameters')
260-
throw createError(413, 'too many parameters')
263+
throw createError(413, 'too many parameters', {
264+
type: 'parameters.too.many'
265+
})
261266
}
262267

263268
debug('parse urlencoding')

test/json.js

+75
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,15 @@ describe('bodyParser.json()', function () {
9090
.expect(400, parseError('{"user"'), done)
9191
})
9292

93+
it('should error with type = "entity.parse.failed"', function (done) {
94+
request(this.server)
95+
.post('/')
96+
.set('Content-Type', 'application/json')
97+
.set('X-Error-Property', 'type')
98+
.send(' {"user"')
99+
.expect(400, 'entity.parse.failed', done)
100+
})
101+
93102
it('should include original body on error object', function (done) {
94103
request(this.server)
95104
.post('/')
@@ -111,6 +120,17 @@ describe('bodyParser.json()', function () {
111120
.expect(413, done)
112121
})
113122

123+
it('should error with type = "entity.too.large"', function (done) {
124+
var buf = Buffer.alloc(1024, '.')
125+
request(createServer({ limit: '1kb' }))
126+
.post('/')
127+
.set('Content-Type', 'application/json')
128+
.set('Content-Length', '1034')
129+
.set('X-Error-Property', 'type')
130+
.send(JSON.stringify({ str: buf.toString() }))
131+
.expect(413, 'entity.too.large', done)
132+
})
133+
114134
it('should 413 when over limit with chunked encoding', function (done) {
115135
var buf = Buffer.alloc(1024, '.')
116136
var server = createServer({ limit: '1kb' })
@@ -244,6 +264,15 @@ describe('bodyParser.json()', function () {
244264
.send(' { "user": "tobi" }')
245265
.expect(200, '{"user":"tobi"}', done)
246266
})
267+
268+
it('should error with type = "entity.parse.failed"', function (done) {
269+
request(this.server)
270+
.post('/')
271+
.set('Content-Type', 'application/json')
272+
.set('X-Error-Property', 'type')
273+
.send('true')
274+
.expect(400, 'entity.parse.failed', done)
275+
})
247276
})
248277
})
249278

@@ -329,6 +358,19 @@ describe('bodyParser.json()', function () {
329358
.expect(403, 'no arrays', done)
330359
})
331360

361+
it('should error with type = "entity.verify.failed"', function (done) {
362+
var server = createServer({verify: function (req, res, buf) {
363+
if (buf[0] === 0x5b) throw new Error('no arrays')
364+
}})
365+
366+
request(server)
367+
.post('/')
368+
.set('Content-Type', 'application/json')
369+
.set('X-Error-Property', 'type')
370+
.send('["tobi"]')
371+
.expect(403, 'entity.verify.failed', done)
372+
})
373+
332374
it('should allow custom codes', function (done) {
333375
var server = createServer({verify: function (req, res, buf) {
334376
if (buf[0] !== 0x5b) return
@@ -344,6 +386,22 @@ describe('bodyParser.json()', function () {
344386
.expect(400, 'no arrays', done)
345387
})
346388

389+
it('should allow custom type', function (done) {
390+
var server = createServer({verify: function (req, res, buf) {
391+
if (buf[0] !== 0x5b) return
392+
var err = new Error('no arrays')
393+
err.type = 'foo.bar'
394+
throw err
395+
}})
396+
397+
request(server)
398+
.post('/')
399+
.set('Content-Type', 'application/json')
400+
.set('X-Error-Property', 'type')
401+
.send('["tobi"]')
402+
.expect(403, 'foo.bar', done)
403+
})
404+
347405
it('should include original body on error object', function (done) {
348406
var server = createServer({verify: function (req, res, buf) {
349407
if (buf[0] === 0x5b) throw new Error('no arrays')
@@ -432,6 +490,14 @@ describe('bodyParser.json()', function () {
432490
test.write(Buffer.from('7b226e616d65223a22cec5d4227d', 'hex'))
433491
test.expect(415, 'unsupported charset "KOI8-R"', done)
434492
})
493+
494+
it('should error with type = "charset.unsupported"', function (done) {
495+
var test = request(this.server).post('/')
496+
test.set('Content-Type', 'application/json; charset=koi8-r')
497+
test.set('X-Error-Property', 'type')
498+
test.write(Buffer.from('7b226e616d65223a22cec5d4227d', 'hex'))
499+
test.expect(415, 'charset.unsupported', done)
500+
})
435501
})
436502

437503
describe('encoding', function () {
@@ -486,6 +552,15 @@ describe('bodyParser.json()', function () {
486552
test.expect(415, 'unsupported content encoding "nulls"', done)
487553
})
488554

555+
it('should error with type = "encoding.unsupported"', function (done) {
556+
var test = request(this.server).post('/')
557+
test.set('Content-Encoding', 'nulls')
558+
test.set('Content-Type', 'application/json')
559+
test.set('X-Error-Property', 'type')
560+
test.write(Buffer.from('000000000000', 'hex'))
561+
test.expect(415, 'encoding.unsupported', done)
562+
})
563+
489564
it('should 400 on malformed encoding', function (done) {
490565
var test = request(this.server).post('/')
491566
test.set('Content-Encoding', 'gzip')

0 commit comments

Comments
 (0)