Skip to content

Commit c33308a

Browse files
committed
feat: create propagate error to client error handle middleware
1 parent 5679093 commit c33308a

File tree

2 files changed

+169
-0
lines changed

2 files changed

+169
-0
lines changed
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
// Copyright 2019 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
import {Request, Response, NextFunction, Express} from 'express';
15+
import {HandlerFunction} from '../functions';
16+
import {ILayer} from 'express-serve-static-core';
17+
18+
/**
19+
* Common properties that exists on Express object instances. Extracted by calling
20+
* `Object.getOwnPropertyNames` on an express instance.
21+
*/
22+
const COMMON_EXPRESS_OBJECT_PROPERTIES = [
23+
'_router',
24+
'use',
25+
'get',
26+
'post',
27+
'put',
28+
'delete',
29+
];
30+
31+
/** The number of parameters on an express error handler. */
32+
const EXPRESS_ERROR_HANDLE_PARAM_LENGTH = 4;
33+
34+
/** A express app error handle. */
35+
interface ErrorHandle {
36+
(err: Error, req: Request, res: Response, next: NextFunction): any;
37+
}
38+
39+
/**
40+
* Express middleware to propagate framework errors to the user function error handle.
41+
* This enables users to handle framework errors that would otherwise be handled by the
42+
* default Express error handle. If the user function doesn't have an error handle,
43+
* it falls back to the default Express error handle.
44+
* @param userFunction - User handler function
45+
*/
46+
export const createPropagateErrorToClientErrorHandleMiddleware = (
47+
userFunction: HandlerFunction
48+
): ErrorHandle => {
49+
const userFunctionErrorHandle =
50+
getFirstUserFunctionErrorHandleMiddleware(userFunction);
51+
52+
return function (
53+
err: Error,
54+
req: Request,
55+
res: Response,
56+
next: NextFunction
57+
) {
58+
// Propagate error to user function error handle.
59+
if (userFunctionErrorHandle) {
60+
return userFunctionErrorHandle(err, req, res, next);
61+
}
62+
63+
// Propagate error to default Express error handle.
64+
return next();
65+
};
66+
};
67+
68+
/**
69+
* Returns the first user handler function defined error handle, if available.
70+
* @param userFunction - User handler function
71+
*/
72+
const getFirstUserFunctionErrorHandleMiddleware = (
73+
userFunction: HandlerFunction
74+
): ErrorHandle | null => {
75+
if (!isExpressApp(userFunction)) {
76+
return null;
77+
}
78+
79+
const middlewares: ILayer[] = (userFunction as Express)._router.stack;
80+
for (const middleware of middlewares) {
81+
if (
82+
middleware.handle &&
83+
middleware.handle.length === EXPRESS_ERROR_HANDLE_PARAM_LENGTH
84+
) {
85+
return middleware.handle as unknown as ErrorHandle;
86+
}
87+
}
88+
89+
return null;
90+
};
91+
92+
/**
93+
* Returns if the user function contains common properties of an Express app.
94+
* @param userFunction
95+
*/
96+
const isExpressApp = (userFunction: HandlerFunction): boolean => {
97+
const userFunctionProperties = Object.getOwnPropertyNames(userFunction);
98+
return COMMON_EXPRESS_OBJECT_PROPERTIES.every(prop =>
99+
userFunctionProperties.includes(prop)
100+
);
101+
};
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import * as assert from 'assert';
2+
import * as sinon from 'sinon';
3+
import {NextFunction} from 'express';
4+
import {Request, Response} from '../../src';
5+
import {createPropagateErrorToClientErrorHandleMiddleware} from '../../src/middleware/propagate_error_to_client_error_handle';
6+
import * as express from 'express';
7+
8+
describe('propagateErrorToClientErrorHandleMiddleware', () => {
9+
let request: Request;
10+
let response: Response;
11+
let next: NextFunction;
12+
let errListener = () => {};
13+
let error: Error;
14+
beforeEach(() => {
15+
error = Error('Something went wrong!');
16+
request = {
17+
setTimeout: sinon.spy(),
18+
on: sinon.spy(),
19+
} as unknown as Request;
20+
response = {
21+
on: sinon.spy(),
22+
} as unknown as Response;
23+
next = sinon.spy();
24+
errListener = sinon.spy();
25+
});
26+
27+
it('user express app with error handle calls user function app error handle', () => {
28+
const app = express();
29+
app.use(
30+
(
31+
_err: Error,
32+
_req: Request,
33+
_res: Response,
34+
_next: NextFunction
35+
): any => {
36+
errListener();
37+
}
38+
);
39+
40+
const middleware = createPropagateErrorToClientErrorHandleMiddleware(app);
41+
middleware(error, request, response, next);
42+
43+
assert.strictEqual((errListener as sinon.SinonSpy).called, true);
44+
assert.strictEqual((next as sinon.SinonSpy).called, false);
45+
});
46+
47+
it('user express app without error handle calls default express error handle', () => {
48+
const app = express();
49+
50+
const middleware = createPropagateErrorToClientErrorHandleMiddleware(app);
51+
middleware(error, request, response, next);
52+
53+
assert.strictEqual((errListener as sinon.SinonSpy).called, false);
54+
assert.strictEqual((next as sinon.SinonSpy).called, true);
55+
});
56+
57+
it('non-express user app calls default express error handle', () => {
58+
const app = (_req: Request, res: Response) => {
59+
res.send('Hello, World!');
60+
};
61+
62+
const middleware = createPropagateErrorToClientErrorHandleMiddleware(app);
63+
middleware(error, request, response, next);
64+
65+
assert.strictEqual((errListener as sinon.SinonSpy).called, false);
66+
assert.strictEqual((next as sinon.SinonSpy).called, true);
67+
});
68+
});

0 commit comments

Comments
 (0)