You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
121 lines
4.1 KiB
121 lines
4.1 KiB
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.'
|
|
} else if ( this.thrownError instanceof ErrorWithContext ) {
|
|
if ( typeof this.thrownError.context.suggestion === 'string' ) {
|
|
return this.thrownError.context.suggestion
|
|
}
|
|
}
|
|
|
|
return ''
|
|
}
|
|
}
|