import { ResponseFactory } from './ResponseFactory'
import { ErrorWithContext , HTTPStatus } from '../../util'
import { Request } from '../lifecycle/Request'
import * as api from './api'
/ * *
* Helper to create a new ErrorResponseFactory , with the given HTTP status and output format .
* @param thrownError
* @param status
* @param output
* /
export function error (
thrownError : Error | string ,
status : HTTPStatus = HTTPStatus . INTERNAL_SERVER_ERROR ,
output : 'json' | 'html' | 'auto' = 'auto' ,
) : ErrorResponseFactory {
if ( typeof thrownError === 'string' ) {
thrownError = new Error ( thrownError )
}
return new ErrorResponseFactory ( thrownError , status , output )
}
/ * *
* Response factory that renders an Error object to the client in a specified format .
* /
export class ErrorResponseFactory extends ResponseFactory {
protected targetMode : 'json' | 'html' | 'auto' = 'auto'
constructor (
public readonly thrownError : Error ,
status : HTTPStatus ,
output : 'json' | 'html' | 'auto' = 'auto' ,
) {
super ( )
this . status ( status )
this . mode ( output )
}
public mode ( output : 'json' | 'html' | 'auto' ) : ErrorResponseFactory {
this . targetMode = output
return this
}
public async write ( request : Request ) : Promise < Request > {
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' )
request . response . body = this . buildJSON ( this . thrownError )
} else if ( this . targetMode === 'html' || ( this . targetMode === 'auto' && ( wants === 'html' || wants === 'unknown' ) ) ) {
request . response . setHeader ( 'Content-Type' , 'text/html' )
request . response . body = this . buildHTML ( this . thrownError )
}
// FIXME XML support
return request
}
/ * *
* Build the HTML display for the given error .
* @param { Error } error
* @return string
* /
protected buildHTML ( thrownError : Error ) : string {
let context : any
if ( thrownError instanceof ErrorWithContext ) {
context = thrownError . context
if ( thrownError . originalError ) {
thrownError = thrownError . originalError
}
}
const suggestion = this . getSuggestion ( )
let str = `
< b > Sorry , an unexpected error occurred while processing your request . < / b >
< br >
$ { suggestion ? '<br><b>Suggestion:</b> ' + suggestion + '<br>' : '' }
< pre > < code >
Name : $ { thrownError . name }
Message : $ { thrownError . message }
Stack trace :
- $ { thrownError . stack ? thrownError . stack . split ( /\s+at\s+/ ) . slice ( 1 )
. join ( '<br> - ' ) : 'none' }
< / code > < / pre >
`
if ( context && typeof context === 'object' ) {
str += `
< pre > < code >
Context :
$ { Object . keys ( context ) . map ( key = > ` - ${ key } : ${ JSON . stringify ( context [ key ] ) . replace ( /\n/g , '<br>' ) } ` )
. join ( '\n' ) }
< / code > < / pre >
`
}
return str
}
protected buildJSON ( thrownError : Error ) : string {
return JSON . stringify ( api . error ( thrownError ) )
}
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)?'
} 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.'
}
return ''
}
}