@@ -11,6 +11,7 @@ const fastSafeStringify = require('fast-safe-stringify');
1111const humanize = require ( 'humanize-string' ) ;
1212const statuses = require ( 'statuses' ) ;
1313const toIdentifier = require ( 'toidentifier' ) ;
14+ const { RedisError } = require ( 'redis-errors' ) ;
1415const { convert } = require ( 'html-to-text' ) ;
1516
1617// lodash
@@ -22,32 +23,33 @@ const _isString = require('lodash.isstring');
2223const _map = require ( 'lodash.map' ) ;
2324const _values = require ( 'lodash.values' ) ;
2425
25- // NOTE: if you change this, be sure to sync in `forward-email`
2626// <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+ ] ) ;
5153
5254const opts = {
5355 encoding : 'utf8'
@@ -73,13 +75,19 @@ const passportLocalMongooseErrorNames = new Set([
7375 'UserExistsError'
7476] ) ;
7577
78+ const passportLocalMongooseTooManyRequests = new Set ( [
79+ 'AttemptTooSoonError' ,
80+ 'TooManyAttemptsError'
81+ ] ) ;
82+
83+ //
7684// initialize try/catch error handling right away
7785// adapted from: https://github.com/koajs/onerror/blob/master/index.js
7886// https://github.com/koajs/examples/issues/20#issuecomment-31568401
7987//
8088// 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+ //
8391
8492function errorHandler (
8593 cookiesKey = false ,
@@ -105,34 +113,46 @@ function errorHandler(
105113 return ;
106114 }
107115
116+ // translate messages
117+ const translate = ( message ) =>
118+ _isFunction ( this . request . t ) ? this . request . t ( message ) : message ;
119+
108120 const logger = useCtxLogger && this . logger ? this . logger : _logger ;
109121
110122 if ( ! _isError ( err ) ) err = new Error ( err ) ;
111123
112124 const type = this . accepts ( [ 'text' , 'json' , 'html' ] ) ;
113125
114126 if ( ! type ) {
115- logger . warn ( 'invalid type, sending 406 error' ) ;
116127 err . status = 406 ;
117- err . message = Boom . notAcceptable ( ) . output . payload ;
128+ err . message = translate ( Boom . notAcceptable ( ) . output . payload ) ;
118129 }
119130
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
124131 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
126134 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
127146
128147 // check if we have a boom error that specified
129148 // a status code already for us (and then use it)
130149 if ( _isObject ( err . output ) && _isNumber ( err . output . statusCode ) ) {
131150 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 ) ) {
133152 // check if this was a DNS error and if so
134153 // 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 ) ;
136156 }
137157
138158 if ( ! _isNumber ( err . status ) ) err . status = 500 ;
@@ -169,13 +189,8 @@ function errorHandler(
169189 // fix page title and description
170190 if ( ! this . api ) {
171191 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 ;
179194 }
180195
181196 switch ( type ) {
@@ -295,21 +310,14 @@ function makeAPIFriendly(ctx, message) {
295310 : message ;
296311}
297312
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 ) {
305314 // passport-local-mongoose support
306315 if ( passportLocalMongooseErrorNames . has ( err . name ) ) {
307- err . message = translate ( err . message ) ;
316+ if ( ! err . no_translate ) err . message = translate ( err . message ) ;
308317 // this ensures the error shows up client-side
309318 err . status = 400 ;
310319 // 429 = too many requests
311- if ( [ 'AttemptTooSoonError' , 'TooManyAttemptsError' ] . includes ( err . name ) )
312- err . status = 429 ;
320+ if ( passportLocalMongooseTooManyRequests . has ( err . name ) ) err . status = 429 ;
313321 return err ;
314322 }
315323
@@ -335,9 +343,12 @@ function parseValidationError(ctx, err) {
335343 // loop over the errors object of the Validation Error
336344 // with support for HTML error lists
337345 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 ) ;
339348 } 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+ ) ;
341352 err . message = makeAPIFriendly (
342353 ctx ,
343354 `<ul class="text-left mb-0"><li>${ errors . join ( '</li><li>' ) } </li></ul>`
0 commit comments