Improve error handling

This commit is contained in:
Tom Moor
2022-12-11 11:27:54 -05:00
parent 5c842087a5
commit 7e22526cc7
4 changed files with 79 additions and 55 deletions

View File

@@ -14,10 +14,7 @@ let errorHtmlCache: Buffer | undefined;
const readErrorFile = (): Buffer => { const readErrorFile = (): Buffer => {
if (isDev) { if (isDev) {
return ( return fs.readFileSync(path.join(__dirname, "error.dev.html"));
errorHtmlCache ??
(errorHtmlCache = fs.readFileSync(path.join(__dirname, "error.dev.html")))
);
} }
if (isProd) { if (isProd) {
@@ -82,10 +79,27 @@ export default function onerror(app: Koa) {
err = newError; err = newError;
} }
if (err.code === "ENOENT") { if (err instanceof ValidationError) {
// @ts-expect-error status is not a property on ValidationError
err.status = 400;
if (err.errors && err.errors[0]) {
err.message = `${err.errors[0].message} (${err.errors[0].path})`;
}
}
if (
err.code === "ENOENT" ||
err instanceof EmptyResultError ||
/Not found/i.test(err.message)
) {
err.status = 404; err.status = 404;
} }
if (/Authorization error/i.test(err.message)) {
err.status = 403;
}
if (typeof err.status !== "number" || !http.STATUS_CODES[err.status]) { if (typeof err.status !== "number" || !http.STATUS_CODES[err.status]) {
err.status = 500; err.status = 500;
} }
@@ -134,47 +148,37 @@ export default function onerror(app: Koa) {
function json(err: any, ctx: Context) { function json(err: any, ctx: Context) {
ctx.status = err.status; ctx.status = err.status;
let message = err.message || err.name; let message;
let error; let id;
if (err instanceof ValidationError) { if (ctx.status === 400) {
// super basic form error handling message = "Validation error";
ctx.status = 400; id = "validation_error";
if (err.errors && err.errors[0]) {
message = `${err.errors[0].message} (${err.errors[0].path})`;
}
} }
if (ctx.status === 401) {
if (err instanceof EmptyResultError || /Not found/i.test(message)) { message = "Authentication error";
id = "authentication_error";
}
if (ctx.status === 403) {
message = "Authorization error";
id = "authorization_error";
}
if (ctx.status === 404) {
message = "Resource not found"; message = "Resource not found";
ctx.status = 404; id = "not_found";
error = "not_found";
} }
if (/Authorization error/i.test(message)) {
ctx.status = 403;
error = "authorization_error";
}
if (ctx.status === 500) { if (ctx.status === 500) {
message = "Internal server error"; message = "Internal server error";
error = "internal_server_error"; id = "internal_server_error";
} }
ctx.body = { ctx.body = {
ok: false, ok: false,
error: snakeCase(err.id || error), error: snakeCase(err.id || id),
status: ctx.status, status: ctx.status,
message, message: err.message || message || err.name,
data: err.errorData, data: err.errorData ?? undefined,
}; };
// @ts-expect-error ts-migrate(2571) FIXME: Object is of type 'unknown'.
if (!ctx.body.data) {
// @ts-expect-error ts-migrate(2571) FIXME: Object is of type 'unknown'.
delete ctx.body.data;
}
} }
/** /**
@@ -188,6 +192,7 @@ function html(err: any, ctx: Context) {
const page = readErrorFile(); const page = readErrorFile();
ctx.body = page ctx.body = page
.toString() .toString()
.replace(/\/\/inject-message\/\//g, escape(err.message))
.replace(/\/\/inject-status\/\//g, escape(err.status)) .replace(/\/\/inject-status\/\//g, escape(err.status))
.replace(/\/\/inject-stack\/\//g, escape(err.stack)); .replace(/\/\/inject-stack\/\//g, escape(err.stack));
ctx.type = "html"; ctx.type = "html";

View File

@@ -83,7 +83,7 @@ Object {
exports[`#groups.update when user is admin fails with validation error when name already taken 1`] = ` exports[`#groups.update when user is admin fails with validation error when name already taken 1`] = `
Object { Object {
"error": "", "error": "validation_error",
"message": "The name of this group is already in use (isUniqueNameInTeam)", "message": "The name of this group is already in use (isUniqueNameInTeam)",
"ok": false, "ok": false,
"status": 400, "status": 400,

View File

@@ -2,33 +2,44 @@
<html> <html>
<head> <head>
<title>Error - //inject-status//</title> <title>Error - //inject-status//</title>
<meta name="viewport" content="user-scalable=no, width=device-width, initial-scale=1.0, maximum-scale=1.0"> <meta
name="viewport"
content="user-scalable=no, width=device-width, initial-scale=1.0, maximum-scale=1.0"
/>
<style> <style>
body { body {
padding: 50px 80px; font: 16px "Helvetica Neue", Helvetica, sans-serif;
font: 14px "Helvetica Neue", Helvetica, sans-serif; display: flex;
align-items: center;
justify-content: center;
padding: 0;
margin: 0;
height: 100vh;
} }
h1 { h1 {
font-size: 2em; font-size: 3em;
margin-bottom: 5px; margin-bottom: 5px;
} }
p {
color: #394351;
}
pre { pre {
font-size: .8em; font-size: 0.8em;
border-radius: 8px;
padding: 12px 16px;
background: #dae1e9;
} }
</style> </style>
</head> </head>
<body> <body>
<div id="error"> <div id="error">
<h1>Error</h1> <h1>//inject-status//</h1>
<p>Looks like something broke!</p> <p>//inject-message//</p>
<pre> <pre>//inject-stack//</pre>
<code>
//inject-stack//
</code>
</pre>
</div> </div>
</body> </body>
</html> </html>

View File

@@ -2,28 +2,36 @@
<html> <html>
<head> <head>
<title>Error - //inject-status//</title> <title>Error - //inject-status//</title>
<meta name="viewport" content="user-scalable=no, width=device-width, initial-scale=1.0, maximum-scale=1.0"> <meta
name="viewport"
content="user-scalable=no, width=device-width, initial-scale=1.0, maximum-scale=1.0"
/>
<style> <style>
body { body {
padding: 50px 80px; font: 16px "Helvetica Neue", Helvetica, sans-serif;
font: 14px "Helvetica Neue", Helvetica, sans-serif; display: flex;
align-items: center;
justify-content: center;
padding: 0;
margin: 0;
height: 100vh;
} }
h1 { h1 {
font-size: 2em; font-size: 3em;
margin-bottom: 5px; margin-bottom: 5px;
} }
pre { p {
font-size: .8em; color: #394351;
} }
</style> </style>
</head> </head>
<body> <body>
<div id="error"> <div id="error">
<h1>Error</h1> <h1>//inject-status//</h1>
<p>Looks like something broke!</p> <p>//inject-message//</p>
</div> </div>
</body> </body>
</html> </html>