Skip to content

Commit

Permalink
first stab at error handler
Browse files Browse the repository at this point in the history
  • Loading branch information
EmmaLRussell committed Dec 9, 2024
1 parent 8302335 commit 0d14148
Show file tree
Hide file tree
Showing 8 changed files with 109 additions and 4 deletions.
24 changes: 23 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@
"cors": "^2.8.5",
"express": "^4.21.1",
"morgan": "^1.10.0",
"sqlite3": "^5.1.7"
"sqlite3": "^5.1.7",
"uid": "^2.0.2"
},
"devDependencies": {
"@types/cors": "^2.8.17",
Expand Down
4 changes: 4 additions & 0 deletions src/errors/errorType.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export const enum ErrorType {
NOT_FOUND = "NOT_FOUND",
UNEXPECTED_ERROR = "UNEXPECTED_ERROR"
}

Check failure on line 4 in src/errors/errorType.ts

View workflow job for this annotation

GitHub Actions / lint

Insert `⏎`
14 changes: 14 additions & 0 deletions src/errors/groutError.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import {ErrorType} from "./errorType";

Check failure on line 1 in src/errors/groutError.ts

View workflow job for this annotation

GitHub Actions / lint

Replace `ErrorType` with `·ErrorType·`

export class GroutError extends Error {
status: number;
errorType: ErrorType;

constructor(message: string, status: number, errorType: ErrorType) {
super(message);

this.name = "GroutError";
this.status = status;
this.errorType = errorType;
}
}

Check failure on line 14 in src/errors/groutError.ts

View workflow job for this annotation

GitHub Actions / lint

Insert `⏎`
24 changes: 24 additions & 0 deletions src/errors/handleError.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { Request, Response } from "express";
import { uid } from "uid";
import {GroutError} from "./groutError";

Check failure on line 3 in src/errors/handleError.ts

View workflow job for this annotation

GitHub Actions / lint

Replace `GroutError` with `·GroutError·`
import {ErrorType} from "./errorType";

Check failure on line 4 in src/errors/handleError.ts

View workflow job for this annotation

GitHub Actions / lint

Replace `ErrorType` with `·ErrorType·`
import {reqWithError} from "../logging";

Check failure on line 5 in src/errors/handleError.ts

View workflow job for this annotation

GitHub Actions / lint

Replace `reqWithError` with `·reqWithError·`
import {jsonResponseError} from "../jsonResponse";

Check failure on line 6 in src/errors/handleError.ts

View workflow job for this annotation

GitHub Actions / lint

Replace `jsonResponseError` with `·jsonResponseError·`

export const handleError = (err: Error, req: Request, res: Response, _: Function) => {

Check failure on line 8 in src/errors/handleError.ts

View workflow job for this annotation

GitHub Actions / lint

Replace `err:·Error,·req:·Request,·res:·Response,·_:·Function` with `⏎····err:·Error,⏎····req:·Request,⏎····res:·Response,⏎····_:·Function⏎`

Check failure on line 8 in src/errors/handleError.ts

View workflow job for this annotation

GitHub Actions / lint

'_' is defined but never used
const groutError = err instanceof GroutError;

const status = groutError ? err.status : 500;
const type = groutError ? err.errorType : ErrorType.UNEXPECTED_ERROR;

// Do not return raw messages from unexpected errors to the front end
const detail = groutError ? err.message
: `An unexpected error occurred. Please contact support and quote error code ${uid()}`;

// Set error type, detail and stack on req so morgan logs them
reqWithError(req).errorType = type;
reqWithError(req).errorDetail = detail;
reqWithError(req).errorStack = err.stack;

jsonResponseError(status, type, detail, res);
}
24 changes: 24 additions & 0 deletions src/jsonResponse.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { Response } from "express";

//TODO: use json response on success for index response (and metadata). Can't use it for tile data!
const addContentType = (res: Response) => {
res.header("Content-Type", "application/json");
};

export const jsonResponseError = (
httpStatus: number,
error: string,
detail: string,
res: Response
) => {
addContentType(res);
const responseObject = {
status: "failure",
errors: [
{ error, detail }
],
data: null
};
res.status(httpStatus);
res.end(JSON.stringify(responseObject));
};
18 changes: 16 additions & 2 deletions src/logging.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,19 @@ import { Application, Request, Response } from "express";
import morgan from "morgan";
import { Dict } from "./types/utils";

interface RequestWithError {
errorType: string,
errorDetail: string,
errorStack: string | undefined
}
export const reqWithError = (req: Request) => (req as unknown) as RequestWithError;

export const initialiseLogging = (app: Application) => {
// Log error details appended to request by handleError
morgan.token("error-type", (req: Request) => reqWithError(req).errorType);
morgan.token("error-detail", (req: Request) => reqWithError(req).errorDetail);
morgan.token("error-stack", (req: Request) => reqWithError(req).errorStack);

const customFormat = (
tokens: Dict<(req: Request, res: Response, header?: string) => string>,
req: Request,
Expand All @@ -16,8 +28,10 @@ export const initialiseLogging = (app: Application) => {
tokens.status(req, res),
tokens.res(req, res, "content-length"),
"-",
tokens["response-time"](req, res),
"ms"
tokens["response-time"](req, res), "ms",
tokens["error-type"](req),
tokens["error-detail"](req),
tokens["error-stack"](req)
].join(" ");
};

Expand Down
2 changes: 2 additions & 0 deletions src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { GroutConfig } from "./types/app";
import { registerRoutes } from "./routes";
import { initialiseLogging } from "./logging";
import { discoverTileDatasets } from "./discover";
import { handleError } from "../errors/handleError";

const app = express();
initialiseLogging(app);
Expand All @@ -28,6 +29,7 @@ Object.assign(app.locals, {
});

app.use("/", registerRoutes());
app.use(handleError);

app.listen(port, () => {
console.log(`Grout is running on port ${port}`);
Expand Down

0 comments on commit 0d14148

Please sign in to comment.