2021-06-03 03:36:25 +00:00
import { ResponseFactory } from './ResponseFactory'
import { ErrorWithContext , HTTPStatus } from '../../util'
import { Request } from '../lifecycle/Request'
import * as api from './api'
2021-03-08 16:07:10 +00:00
2021-03-25 13:50:13 +00:00
/ * *
* Helper to create a new ErrorResponseFactory , with the given HTTP status and output format .
2021-06-03 03:36:25 +00:00
* @param thrownError
2021-03-25 13:50:13 +00:00
* @param status
* @param output
* /
2021-03-08 16:07:10 +00:00
export function error (
2021-06-03 03:36:25 +00:00
thrownError : Error | string ,
2021-03-08 16:07:10 +00:00
status : HTTPStatus = HTTPStatus . INTERNAL_SERVER_ERROR ,
2021-06-03 03:36:25 +00:00
output : 'json' | 'html' | 'auto' = 'auto' ,
2021-03-08 16:07:10 +00:00
) : ErrorResponseFactory {
2021-06-03 03:36:25 +00:00
if ( typeof thrownError === 'string' ) {
thrownError = new Error ( thrownError )
}
return new ErrorResponseFactory ( thrownError , status , output )
2021-03-08 16:07:10 +00:00
}
2021-03-25 13:50:13 +00:00
/ * *
* Response factory that renders an Error object to the client in a specified format .
* /
2021-03-08 16:07:10 +00:00
export class ErrorResponseFactory extends ResponseFactory {
protected targetMode : 'json' | 'html' | 'auto' = 'auto'
constructor (
2021-06-03 03:36:25 +00:00
public readonly thrownError : Error ,
2021-03-08 16:07:10 +00:00
status : HTTPStatus ,
2021-06-03 03:36:25 +00:00
output : 'json' | 'html' | 'auto' = 'auto' ,
2021-03-08 16:07:10 +00:00
) {
super ( )
this . status ( status )
this . mode ( output )
}
public mode ( output : 'json' | 'html' | 'auto' ) : ErrorResponseFactory {
this . targetMode = output
return this
}
2021-06-03 03:36:25 +00:00
public async write ( request : Request ) : Promise < Request > {
2021-03-08 16:07:10 +00:00
request = await super . write ( request )
const wants = request . wants ( )
if ( this . targetMode === 'json' || ( this . targetMode === 'auto' && wants === 'json' ) ) {
request . response . setHeader ( 'Content-Type' , 'application/json' )
2021-06-03 03:36:25 +00:00
request . response . body = this . buildJSON ( this . thrownError )
2021-03-08 16:07:10 +00:00
} else if ( this . targetMode === 'html' || ( this . targetMode === 'auto' && ( wants === 'html' || wants === 'unknown' ) ) ) {
request . response . setHeader ( 'Content-Type' , 'text/html' )
2021-06-03 03:36:25 +00:00
request . response . body = this . buildHTML ( this . thrownError )
2021-03-08 16:07:10 +00:00
}
// FIXME XML support
return request
}
/ * *
* Build the HTML display for the given error .
* @param { Error } error
* @return string
* /
2021-06-03 03:36:25 +00:00
protected buildHTML ( thrownError : Error ) : string {
2021-03-08 16:07:10 +00:00
let context : any
2021-06-03 03:36:25 +00:00
if ( thrownError instanceof ErrorWithContext ) {
context = thrownError . context
if ( thrownError . originalError ) {
thrownError = thrownError . originalError
2021-03-08 16:07:10 +00:00
}
}
2022-03-29 06:14:46 +00:00
const suggestion = this . getSuggestion ( )
2021-03-08 16:07:10 +00:00
let str = `
< b > Sorry , an unexpected error occurred while processing your request . < / b >
< br >
2022-03-29 06:14:46 +00:00
$ { suggestion ? '<br><b>Suggestion:</b> ' + suggestion + '<br>' : '' }
2021-03-08 16:07:10 +00:00
< pre > < code >
2021-06-03 03:36:25 +00:00
Name : $ { thrownError . name }
Message : $ { thrownError . message }
2021-03-08 16:07:10 +00:00
Stack trace :
2021-06-03 03:36:25 +00:00
- $ { thrownError . stack ? thrownError . stack . split ( /\s+at\s+/ ) . slice ( 1 )
. join ( '<br> - ' ) : 'none' }
2021-03-08 16:07:10 +00:00
< / code > < / pre >
`
if ( context && typeof context === 'object' ) {
str += `
< pre > < code >
Context :
2022-03-29 06:14:46 +00:00
$ { Object . keys ( context ) . map ( key = > ` - ${ key } : ${ JSON . stringify ( context [ key ] ) . replace ( /\n/g , '<br>' ) } ` )
2021-06-03 03:36:25 +00:00
. join ( '\n' ) }
2021-03-08 16:07:10 +00:00
< / code > < / pre >
`
}
return str
}
2021-06-03 03:36:25 +00:00
protected buildJSON ( thrownError : Error ) : string {
return JSON . stringify ( api . error ( thrownError ) )
2021-03-08 16:07:10 +00:00
}
2022-03-29 06:14:46 +00:00
protected getSuggestion ( ) : string {
if ( this . thrownError . message . startsWith ( 'No such dependency is registered with this container: class SecurityContext' ) ) {
return 'It looks like this route relies on the security framework. Is the route you are accessing inside a middleware (e.g. SessionAuthMiddleware)?'
2022-04-05 19:02:16 +00:00
} else if ( this . thrownError . message . startsWith ( 'Unable to resolve schema for validator' ) ) {
return 'Make sure the directory in which the interface file is located is listed in extollo.cc.zodify in package.json, and that it ends with the proper .type.ts suffix.'
2022-07-14 02:35:18 +00:00
} else if ( this . thrownError instanceof ErrorWithContext ) {
if ( typeof this . thrownError . context . suggestion === 'string' ) {
return this . thrownError . context . suggestion
}
2022-03-29 06:14:46 +00:00
}
return ''
}
2021-03-08 16:07:10 +00:00
}