Skip to content

Commit 8690870

Browse files
authored
feat: exception handler middleware (VF-000) (#70)
* feat: exception handler middleware (VF-000) * feat: remove previous unused ExceptionHandler * refactor: organize formatters * refactor: review feedback * fix: make types work
1 parent 96dd9ac commit 8690870

File tree

13 files changed

+315
-273
lines changed

13 files changed

+315
-273
lines changed

package.json

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
},
1414
"dependencies": {
1515
"@types/chai": "^4.2.16",
16-
"@types/express": "^4.17.11",
1716
"@types/ioredis": "^4.26.4",
1817
"@types/lodash": "^4.14.168",
1918
"@types/luxon": "^1.26.4",
@@ -22,7 +21,7 @@
2221
"@voiceflow/verror": "^1.1.0",
2322
"chai": "^4.3.4",
2423
"dotenv": "^10.0.0",
25-
"express": "^4.17.1",
24+
"http-errors": "^2.0.0",
2625
"http-status": "^1.4.2",
2726
"lodash": "^4.17.11",
2827
"luxon": "^1.21.3",
@@ -34,6 +33,8 @@
3433
"@istanbuljs/nyc-config-typescript": "1.0.2",
3534
"@types/axios": "^0.14.0",
3635
"@types/chai-as-promised": "^7.1.4",
36+
"@types/express": "^4.17.13",
37+
"@types/http-errors": "^1.8.2",
3738
"@types/mocha": "9.1.0",
3839
"@types/supertest": "^2.0.11",
3940
"@voiceflow/commitlint-config": "2.0.0",
@@ -49,6 +50,7 @@
4950
"depcheck": "1.4.3",
5051
"eslint": "^7.32.0",
5152
"eslint-output": "^3.0.1",
53+
"express": "^4.17.3",
5254
"express-validator": "^6.10.1",
5355
"fixpack": "^4.0.0",
5456
"husky": "^4.3.8",
@@ -72,6 +74,7 @@
7274
"license": "ISC",
7375
"main": "build/index.js",
7476
"peerDependencies": {
77+
"@types/express": "^4.17.13",
7578
"@voiceflow/common": "^7.2.0",
7679
"express-validator": "^6.3.0"
7780
},

src/exceptionHandler.ts

Lines changed: 0 additions & 76 deletions
This file was deleted.

src/index.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
export * from './clients';
22
export * from './env';
3-
export { default as ExceptionHandler } from './exceptionHandler';
43
export { default as FixtureGenerator } from './fixtureGenerator';
54
export * from './middlewares';
65
export { default as ResponseBuilder } from './responseBuilder';
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { HttpError, isHttpError } from 'http-errors';
2+
3+
import type { ExceptionFormatter } from '../types';
4+
5+
export { isHttpError };
6+
7+
export const formatHttpError: ExceptionFormatter<HttpError> = (err) => {
8+
return {
9+
statusCode: err.statusCode,
10+
name: err.name,
11+
message: err.message,
12+
};
13+
};
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import VError from '@voiceflow/verror';
2+
3+
import type { ExceptionFormat } from '../types';
4+
import { formatHttpError, isHttpError } from './httpError';
5+
import { formatJavascriptError, isJavascriptError } from './jsError';
6+
import { formatVError, isVError } from './vError';
7+
8+
export const formatError = (err: unknown): ExceptionFormat => {
9+
let exception: ExceptionFormat = {
10+
statusCode: VError.HTTP_STATUS.INTERNAL_SERVER_ERROR,
11+
name: 'UnknownError',
12+
message: 'Unknown error',
13+
};
14+
15+
if (isVError(err)) exception = mergeExceptionResult(exception, formatVError(err));
16+
else if (isHttpError(err)) exception = mergeExceptionResult(exception, formatHttpError(err));
17+
else if (isJavascriptError(err)) exception = mergeExceptionResult(exception, formatJavascriptError(err));
18+
19+
return exception;
20+
};
21+
22+
export const mergeExceptionResult = (baseException: ExceptionFormat, exception: Partial<ExceptionFormat>) => ({
23+
...baseException,
24+
...exception,
25+
});
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { types } from 'util';
2+
3+
import type { ExceptionFormatter } from '../types';
4+
5+
export const isJavascriptError = types.isNativeError;
6+
7+
export const formatJavascriptError: ExceptionFormatter<Error> = (err) => {
8+
return {
9+
name: err.name,
10+
message: err.message,
11+
};
12+
};
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import VError from '@voiceflow/verror';
2+
3+
import type { ExceptionFormatter } from '../types';
4+
5+
export const isVError = (err: unknown): err is VError => err != null && err instanceof VError;
6+
7+
export const formatVError: ExceptionFormatter<VError> = (err) => {
8+
return {
9+
statusCode: err.code,
10+
name: err.name,
11+
message: err.message,
12+
};
13+
};

src/middlewares/exception/index.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import type { NextFunction, Request, Response } from 'express';
2+
3+
import { AbstractMiddleware } from '../../types';
4+
import { formatError } from './formatters';
5+
6+
export class ExceptionMiddleware extends AbstractMiddleware<never, never> {
7+
public constructor() {
8+
super(undefined as never, undefined as never);
9+
}
10+
11+
public handleError(err: unknown, req: Request, res: Response, _next: NextFunction): void {
12+
const { statusCode, ...body } = formatError(err);
13+
14+
res.status(statusCode).send({
15+
...body,
16+
requestID: req.id.toString(),
17+
});
18+
}
19+
}

src/middlewares/exception/types.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export interface ExceptionFormat {
2+
statusCode: number;
3+
name: string;
4+
message: string;
5+
}
6+
7+
export type ExceptionFormatter<T> = (err: T) => Partial<ExceptionFormat>;

src/middlewares/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1+
export * from './exception';
12
export * from './rateLimit';

0 commit comments

Comments
 (0)