@@ -11,6 +11,7 @@ const fastSafeStringify = require('fast-safe-stringify');
11
11
const humanize = require ( 'humanize-string' ) ;
12
12
const statuses = require ( 'statuses' ) ;
13
13
const toIdentifier = require ( 'toidentifier' ) ;
14
+ const { RedisError } = require ( 'redis-errors' ) ;
14
15
const { convert } = require ( 'html-to-text' ) ;
15
16
16
17
// lodash
@@ -22,32 +23,33 @@ const _isString = require('lodash.isstring');
22
23
const _map = require ( 'lodash.map' ) ;
23
24
const _values = require ( 'lodash.values' ) ;
24
25
25
- // NOTE: if you change this, be sure to sync in `forward-email`
26
26
// <https://github.com/nodejs/node/blob/08dd4b1723b20d56fbedf37d52e736fe09715f80/lib/dns.js#L296-L320>
27
- const CODES_TO_RESPONSE_CODES = {
28
- EADDRGETNETWORKPARAMS : 421 ,
29
- EADDRINUSE : 421 ,
30
- EAI_AGAIN : 421 ,
31
- EBADFLAGS : 421 ,
32
- EBADHINTS : 421 ,
33
- ECANCELLED : 421 ,
34
- ECONNREFUSED : 421 ,
35
- ECONNRESET : 442 ,
36
- EDESTRUCTION : 421 ,
37
- EFORMERR : 421 ,
38
- ELOADIPHLPAPI : 421 ,
39
- ENETUNREACH : 421 ,
40
- ENODATA : 421 ,
41
- ENOMEM : 421 ,
42
- ENOTFOUND : 421 ,
43
- ENOTINITIALIZED : 421 ,
44
- EPIPE : 421 ,
45
- EREFUSED : 421 ,
46
- ESERVFAIL : 421 ,
47
- ETIMEOUT : 420
48
- } ;
49
-
50
- const RETRY_CODES = Object . keys ( CODES_TO_RESPONSE_CODES ) ;
27
+ const DNS_RETRY_CODES = new Set ( [
28
+ 'EADDRGETNETWORKPARAMS' ,
29
+ 'EBADFAMILY' ,
30
+ 'EBADFLAGS' ,
31
+ 'EBADHINTS' ,
32
+ 'EBADNAME' ,
33
+ 'EBADQUERY' ,
34
+ 'EBADRESP' ,
35
+ 'EBADSTR' ,
36
+ 'ECANCELLED' ,
37
+ 'ECONNREFUSED' ,
38
+ 'EDESTRUCTION' ,
39
+ 'EFILE' ,
40
+ 'EFORMERR' ,
41
+ 'ELOADIPHLPAPI' ,
42
+ 'ENODATA' ,
43
+ 'ENOMEM' ,
44
+ 'ENONAME' ,
45
+ 'ENOTFOUND' ,
46
+ 'ENOTIMP' ,
47
+ 'ENOTINITIALIZED' ,
48
+ 'EOF' ,
49
+ 'EREFUSED' ,
50
+ 'ESERVFAIL' ,
51
+ 'ETIMEOUT'
52
+ ] ) ;
51
53
52
54
const opts = {
53
55
encoding : 'utf8'
@@ -73,13 +75,19 @@ const passportLocalMongooseErrorNames = new Set([
73
75
'UserExistsError'
74
76
] ) ;
75
77
78
+ const passportLocalMongooseTooManyRequests = new Set ( [
79
+ 'AttemptTooSoonError' ,
80
+ 'TooManyAttemptsError'
81
+ ] ) ;
82
+
83
+ //
76
84
// initialize try/catch error handling right away
77
85
// adapted from: https://github.com/koajs/onerror/blob/master/index.js
78
86
// https://github.com/koajs/examples/issues/20#issuecomment-31568401
79
87
//
80
88
// inspired by:
81
- // https://goo.gl/62oU7P
82
- // https://goo.gl/8Z7aMe
89
+ // https://github.com/koajs/koa/blob/9f80296fc49fa0c03db939e866215f3721fcbbc6/lib/context.js#L101-L139
90
+ //
83
91
84
92
function errorHandler (
85
93
cookiesKey = false ,
@@ -105,34 +113,46 @@ function errorHandler(
105
113
return ;
106
114
}
107
115
116
+ // translate messages
117
+ const translate = ( message ) =>
118
+ _isFunction ( this . request . t ) ? this . request . t ( message ) : message ;
119
+
108
120
const logger = useCtxLogger && this . logger ? this . logger : _logger ;
109
121
110
122
if ( ! _isError ( err ) ) err = new Error ( err ) ;
111
123
112
124
const type = this . accepts ( [ 'text' , 'json' , 'html' ] ) ;
113
125
114
126
if ( ! type ) {
115
- logger . warn ( 'invalid type, sending 406 error' ) ;
116
127
err . status = 406 ;
117
- err . message = Boom . notAcceptable ( ) . output . payload ;
128
+ err . message = translate ( Boom . notAcceptable ( ) . output . payload ) ;
118
129
}
119
130
120
- // parse mongoose validation errors
121
- err = parseValidationError ( this , err ) ;
122
-
123
- // check if we threw just a status code in order to keep it simple
124
131
const val = Number . parseInt ( err . message , 10 ) ;
125
- if ( _isNumber ( val ) && val >= 400 )
132
+ if ( _isNumber ( val ) && val >= 400 && val < 600 ) {
133
+ // check if we threw just a status code in order to keep it simple
126
134
err = Boom [ camelCase ( toIdentifier ( statuses . message [ val ] ) ) ] ( ) ;
135
+ err . message = translate ( err . message ) ;
136
+ } else if ( err instanceof RedisError ) {
137
+ // redis errors (e.g. ioredis' MaxRetriesPerRequestError)
138
+ err . status = 408 ;
139
+ err . message = translate ( Boom . clientTimeout ( ) . output . payload ) ;
140
+ } else {
141
+ // parse mongoose validation errors
142
+ err = parseValidationError ( this , err , translate ) ;
143
+ }
144
+
145
+ // TODO: mongodb errors that are not Mongoose ValidationError
127
146
128
147
// check if we have a boom error that specified
129
148
// a status code already for us (and then use it)
130
149
if ( _isObject ( err . output ) && _isNumber ( err . output . statusCode ) ) {
131
150
err . status = err . output . statusCode ;
132
- } else if ( _isString ( err . code ) && RETRY_CODES . includes ( err . code ) ) {
151
+ } else if ( _isString ( err . code ) && DNS_RETRY_CODES . has ( err . code ) ) {
133
152
// check if this was a DNS error and if so
134
153
// then set status code for retries appropriately
135
- err . status = CODES_TO_RESPONSE_CODES [ err . code ] ;
154
+ err . status = 408 ;
155
+ err . message = translate ( Boom . clientTimeout ( ) . output . payload ) ;
136
156
}
137
157
138
158
if ( ! _isNumber ( err . status ) ) err . status = 500 ;
@@ -169,13 +189,8 @@ function errorHandler(
169
189
// fix page title and description
170
190
if ( ! this . api ) {
171
191
this . state . meta = this . state . meta || { } ;
172
- if ( ! err . no_translate && _isFunction ( this . request . t ) ) {
173
- this . state . meta . title = this . request . t ( this . body . error ) ;
174
- this . state . meta . description = this . request . t ( err . message ) ;
175
- } else {
176
- this . state . meta . title = this . body . error ;
177
- this . state . meta . description = err . message ;
178
- }
192
+ this . state . meta . title = this . body . error ;
193
+ this . state . meta . description = err . message ;
179
194
}
180
195
181
196
switch ( type ) {
@@ -295,21 +310,14 @@ function makeAPIFriendly(ctx, message) {
295
310
: message ;
296
311
}
297
312
298
- function parseValidationError ( ctx , err ) {
299
- // translate messages
300
- const translate = ( message ) =>
301
- ! err . no_translate && _isFunction ( ctx . request . t )
302
- ? ctx . request . t ( message )
303
- : message ;
304
-
313
+ function parseValidationError ( ctx , err , translate ) {
305
314
// passport-local-mongoose support
306
315
if ( passportLocalMongooseErrorNames . has ( err . name ) ) {
307
- err . message = translate ( err . message ) ;
316
+ if ( ! err . no_translate ) err . message = translate ( err . message ) ;
308
317
// this ensures the error shows up client-side
309
318
err . status = 400 ;
310
319
// 429 = too many requests
311
- if ( [ 'AttemptTooSoonError' , 'TooManyAttemptsError' ] . includes ( err . name ) )
312
- err . status = 429 ;
320
+ if ( passportLocalMongooseTooManyRequests . has ( err . name ) ) err . status = 429 ;
313
321
return err ;
314
322
}
315
323
@@ -335,9 +343,12 @@ function parseValidationError(ctx, err) {
335
343
// loop over the errors object of the Validation Error
336
344
// with support for HTML error lists
337
345
if ( _values ( err . errors ) . length === 1 ) {
338
- err . message = translate ( _values ( err . errors ) [ 0 ] . message ) ;
346
+ err . message = _values ( err . errors ) [ 0 ] . message ;
347
+ if ( ! err . no_translate ) err . message = translate ( err . message ) ;
339
348
} else {
340
- const errors = _map ( _map ( _values ( err . errors ) , 'message' ) , translate ) ;
349
+ const errors = _map ( _map ( _values ( err . errors ) , 'message' ) , ( message ) =>
350
+ err . no_translate ? message : translate ( message )
351
+ ) ;
341
352
err . message = makeAPIFriendly (
342
353
ctx ,
343
354
`<ul class="text-left mb-0"><li>${ errors . join ( '</li><li>' ) } </li></ul>`
0 commit comments