Improve error handling
This commit is contained in:
@@ -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) {
|
||||||
|
message = "Authentication error";
|
||||||
|
id = "authentication_error";
|
||||||
}
|
}
|
||||||
|
if (ctx.status === 403) {
|
||||||
if (err instanceof EmptyResultError || /Not found/i.test(message)) {
|
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";
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user