-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #3 from mrc-ide/mrc-6085-error-handling
mrc-6085 Error handling
- Loading branch information
Showing
28 changed files
with
555 additions
and
51 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,9 @@ | ||
import { Request, Response } from "express"; | ||
import { jsonResponseSuccess } from "../jsonResponse"; | ||
|
||
export class IndexController { | ||
static getIndex = (_req: Request, res: Response) => { | ||
const version = process.env.npm_package_version; | ||
res.status(200).json({ version }); | ||
jsonResponseSuccess({ version }, res); | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,28 +1,49 @@ | ||
import { Request, Response } from "express"; | ||
import { NextFunction, Request, Response } from "express"; | ||
import { AppLocals } from "../types/app"; | ||
import asyncControllerHandler from "../errors/asyncControllerHandler"; | ||
import notFound from "../errors/notFound"; | ||
import { GroutError } from "../errors/groutError"; | ||
import { ErrorType } from "../errors/errorType"; | ||
|
||
const parseIntParam = (param: string): number => { | ||
// Native parseInt is not strict (ignores whitespace and trailing chars) so test with a regex | ||
if (!/^\d+$/.test(param)) { | ||
throw new GroutError( | ||
`"${param}" is not an integer`, | ||
ErrorType.BAD_REQUEST | ||
); | ||
} | ||
return parseInt(param, 10); | ||
}; | ||
|
||
export class TileController { | ||
static getTile = async (req: Request, res: Response) => { | ||
const { dataset, level, z, x, y } = req.params; | ||
const { tileDatasets } = req.app.locals as AppLocals; | ||
static getTile = async ( | ||
req: Request, | ||
res: Response, | ||
next: NextFunction | ||
) => { | ||
await asyncControllerHandler(next, async () => { | ||
const { dataset, level, z, x, y } = req.params; | ||
const { tileDatasets } = req.app.locals as AppLocals; | ||
|
||
let tileData = null; | ||
if (tileDatasets[dataset] && tileDatasets[dataset][level]) { | ||
const db = tileDatasets[dataset][level]; | ||
tileData = await db.getTileData( | ||
parseInt(z), | ||
parseInt(x), | ||
parseInt(y) | ||
); | ||
} | ||
let tileData = null; | ||
if (tileDatasets[dataset] && tileDatasets[dataset][level]) { | ||
const db = tileDatasets[dataset][level]; | ||
tileData = await db.getTileData( | ||
parseIntParam(z), | ||
parseIntParam(x), | ||
parseIntParam(y) | ||
); | ||
} | ||
|
||
if (tileData) { | ||
res.writeHead(200, { | ||
"Content-Type": "application/octet-stream", | ||
"Content-Encoding": "gzip" | ||
}).end(tileData); | ||
} else { | ||
res.writeHead(404).end(); | ||
} | ||
if (tileData) { | ||
res.writeHead(200, { | ||
"Content-Type": "application/octet-stream", | ||
"Content-Encoding": "gzip" | ||
}).end(tileData); | ||
} else { | ||
notFound(req); | ||
} | ||
}); | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
import { NextFunction } from "express"; | ||
|
||
// This method should be used to wrap any async controller methods to ensure error handling is applied | ||
export default async (next: NextFunction, method: () => void) => { | ||
try { | ||
await method(); | ||
} catch (error) { | ||
next(error); | ||
} | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
export const enum ErrorType { | ||
BAD_REQUEST = "BAD_REQUEST", | ||
NOT_FOUND = "NOT_FOUND", | ||
UNEXPECTED_ERROR = "UNEXPECTED_ERROR" | ||
} | ||
|
||
export const ErrorTypeStatuses: { [key in ErrorType]: number } = { | ||
[ErrorType.BAD_REQUEST]: 400, | ||
[ErrorType.NOT_FOUND]: 404, | ||
[ErrorType.UNEXPECTED_ERROR]: 500 | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import { ErrorType, ErrorTypeStatuses } from "./errorType"; | ||
|
||
export class GroutError extends Error { | ||
errorType: ErrorType; | ||
|
||
constructor(message: string, errorType: ErrorType) { | ||
super(message); | ||
|
||
this.name = "GroutError"; | ||
this.errorType = errorType; | ||
} | ||
|
||
get status() { | ||
return ErrorTypeStatuses[this.errorType]; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
import { NextFunction, Response } from "express"; | ||
import { uid } from "uid"; | ||
import { GroutError } from "./groutError"; | ||
import { ErrorType } from "./errorType"; | ||
import { RequestWithError } from "../logging"; | ||
import { jsonResponseError } from "../jsonResponse"; | ||
|
||
// We need to include the unused next var for this to be used correctly as an error handler | ||
export const handleError = ( | ||
err: Error, | ||
req: RequestWithError, | ||
res: Response, | ||
_: NextFunction // eslint-disable-line @typescript-eslint/no-unused-vars | ||
) => { | ||
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 | ||
req.errorType = type; | ||
req.errorDetail = detail; | ||
req.errorStack = err.stack; | ||
|
||
jsonResponseError(status, type, detail, res); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
import { Request } from "express"; | ||
import { ErrorType } from "./errorType"; | ||
import { GroutError } from "./groutError"; | ||
|
||
export default (req: Request) => { | ||
const { url } = req; | ||
throw new GroutError(`Route not found: ${url}`, ErrorType.NOT_FOUND); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
import { Response } from "express"; | ||
|
||
const addContentType = (res: Response) => { | ||
res.header("Content-Type", "application/json"); | ||
}; | ||
|
||
export const jsonResponseSuccess = (data: object | string, res: Response) => { | ||
addContentType(res); | ||
const responseObject = { | ||
status: "success", | ||
errors: null, | ||
data | ||
}; | ||
res.end(JSON.stringify(responseObject)); | ||
}; | ||
|
||
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)); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,10 +1,23 @@ | ||
import { Router } from "express"; | ||
import { IndexController } from "./controllers/indexController"; | ||
import { TileController } from "./controllers/tileController"; | ||
import notFound from "./errors/notFound"; | ||
|
||
export const registerRoutes = () => { | ||
const router = Router(); | ||
router.get("/", IndexController.getIndex); | ||
router.get("/tile/:dataset/:level/:z/:x/:y", TileController.getTile); | ||
|
||
// provide an endpoint we can use to test 500 response behaviour by throwing an "unexpected error" - but only if we | ||
// are running in a non-production mode indicated by an env var | ||
if (process.env.GROUT_ERROR_TEST) { | ||
router.get("/error-test", () => { | ||
throw Error("Testing error behaviour"); | ||
}); | ||
} | ||
|
||
// Throw 404 error for any unmatched routes | ||
router.use(notFound); | ||
|
||
return router; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.