import http = require('http');
import { value } from '@artisan-framework/core';
import { detectErrorMessage, detectErrorStatus, isProd } from '../utils';
import { WebContext, WebProviderConfig, WEB_PROVIDER_CONFIG_KEY } from '../web-protocol';
import { WebErrorHandler } from './error-protocol';
import escapeHtml = require('escape-html');
const isDev = !isProd();
const HTML_ERROR = isDev
? `
Error - {{status}}
Error
Looks like something broke!
{{stack}}
`
: `
Error - {{status}}
Error
Looks like something broke!
`;
export class ArtisanWebErrorHandler implements WebErrorHandler {
@value({ el: WEB_PROVIDER_CONFIG_KEY, default: {} })
private _config: WebProviderConfig;
canHandle(): boolean {
return true;
}
handle(err: any, ctx: WebContext): void {
ctx.status = detectErrorStatus(err);
ctx.set(err.headers);
const _accepts = ctx.accepts('html', 'text', 'json');
const type = typeof _accepts === 'string' ? _accepts : 'text';
let message = http.STATUS_CODES[ctx.status];
if (isDev || err.expose) {
const msg = detectErrorMessage(ctx, err);
message = msg || message;
}
// redirect
if (['html', 'text'].includes(type) && this._config.onError?.errorPage) {
const _errorPage = this._config.onError?.errorPage;
const errorPage = typeof _errorPage === 'function' ? _errorPage(err, ctx) : _errorPage;
ctx.redirect(errorPage);
} else {
// setType
ctx.type = type;
if (type === 'html') {
ctx.body = HTML_ERROR.replace('{{status}}', escapeHtml(`${ctx.status}`)).replace(
'{{stack}}',
escapeHtml(err.stack),
);
} else if (type === 'text') {
// unset all headers, and set those specified
if (typeof ctx.res.getHeaderNames === 'function') {
for (const name of ctx.res.getHeaderNames()) {
ctx.res.removeHeader(name);
}
} else {
(ctx.res as any)._headers = {};
}
ctx.set(err.headers);
ctx.body = message;
} else {
if (isDev) {
ctx.body = JSON.stringify({
message,
error: err.stack,
});
} else {
ctx.body = JSON.stringify({
message,
});
}
}
}
ctx.res.end(ctx.body);
}
}