Skip to content

Commit 90ea72e

Browse files
committed
fix: Inject user function error handle middleware chain into framework instead of just the middleware
1 parent eb61842 commit 90ea72e

4 files changed

+128
-124
lines changed

Diff for: src/middleware/propagate_error_to_client_error_handle.ts renamed to src/middleware/inject_user_function_error_handle_middleware_chain.ts

+49-47
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1212
// See the License for the specific language governing permissions and
1313
// limitations under the License.
14-
import {Request, Response, NextFunction, Express} from 'express';
14+
import {Express} from 'express';
1515
import {HandlerFunction} from '../functions';
1616
import {ILayer} from 'express-serve-static-core';
1717

@@ -31,72 +31,74 @@ const COMMON_EXPRESS_OBJECT_PROPERTIES = [
3131
/** The number of parameters on an express error handler. */
3232
const EXPRESS_ERROR_HANDLE_PARAM_LENGTH = 4;
3333

34-
/** A express app error handle. */
35-
interface ErrorHandle {
36-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
37-
(err: Error, req: Request, res: Response, next: NextFunction): any;
38-
}
39-
4034
/**
41-
* Express middleware to propagate framework errors to the user function error handle.
42-
* This enables users to handle framework errors that would otherwise be handled by the
43-
* default Express error handle. If the user function doesn't have an error handle,
44-
* it falls back to the default Express error handle.
35+
* Injects the user function error handle middleware and its subsequent middleware
36+
* chain into the framework. This enables users to handle framework errors that would
37+
* otherwise be handled by the default Express error handle.
38+
* @param frameworkApp - Framework app
4539
* @param userFunction - User handler function
4640
*/
47-
export const createPropagateErrorToClientErrorHandleMiddleware = (
41+
export const injectUserFunctionErrorHandleMiddlewareChain = (
42+
frameworkApp: Express,
4843
userFunction: HandlerFunction
49-
): ErrorHandle => {
50-
const userFunctionErrorHandle =
51-
getFirstUserFunctionErrorHandleMiddleware(userFunction);
44+
) => {
45+
// Check if user function is an express app that can register middleware.
46+
if (!isExpressApp(userFunction)) {
47+
return;
48+
}
49+
50+
// Get the index of the user's first error handle middleware.
51+
const firstErrorHandleMiddlewareIndex =
52+
getFirstUserFunctionErrorHandleMiddlewareIndex(userFunction);
53+
if (!firstErrorHandleMiddlewareIndex) {
54+
return;
55+
}
5256

53-
return function (
54-
err: Error,
55-
req: Request,
56-
res: Response,
57-
next: NextFunction
57+
// Inject their error handle middleware chain into the framework app.
58+
const middlewares = (userFunction as Express)._router.stack;
59+
for (
60+
let index = firstErrorHandleMiddlewareIndex;
61+
index < middlewares.length;
62+
index++
5863
) {
59-
// Propagate error to user function error handle.
60-
if (userFunctionErrorHandle) {
61-
return userFunctionErrorHandle(err, req, res, next);
64+
const middleware = middlewares[index];
65+
66+
// We don't care about routes.
67+
if (middleware.route) {
68+
continue;
6269
}
6370

64-
// Propagate error to default Express error handle.
65-
return next();
66-
};
71+
frameworkApp.use(middleware.handle);
72+
}
6773
};
6874

6975
/**
70-
* Returns the first user handler function defined error handle, if available.
71-
* @param userFunction - User handler function
76+
* Returns if the user function contains common properties of an Express app.
77+
* @param userFunction
7278
*/
73-
const getFirstUserFunctionErrorHandleMiddleware = (
74-
userFunction: HandlerFunction
75-
): ErrorHandle | null => {
76-
if (!isExpressApp(userFunction)) {
77-
return null;
78-
}
79+
const isExpressApp = (userFunction: HandlerFunction): boolean => {
80+
const userFunctionProperties = Object.getOwnPropertyNames(userFunction);
81+
return COMMON_EXPRESS_OBJECT_PROPERTIES.every(prop =>
82+
userFunctionProperties.includes(prop)
83+
);
84+
};
7985

86+
/**
87+
* Returns the index of the first error handle middleware in the user function.
88+
*/
89+
const getFirstUserFunctionErrorHandleMiddlewareIndex = (
90+
userFunction: HandlerFunction
91+
): number | null => {
8092
const middlewares: ILayer[] = (userFunction as Express)._router.stack;
81-
for (const middleware of middlewares) {
93+
for (let index = 0; index < middlewares.length; index++) {
94+
const middleware = middlewares[index];
8295
if (
8396
middleware.handle &&
8497
middleware.handle.length === EXPRESS_ERROR_HANDLE_PARAM_LENGTH
8598
) {
86-
return middleware.handle as unknown as ErrorHandle;
99+
return index;
87100
}
88101
}
89102

90103
return null;
91104
};
92-
93-
/**
94-
* Returns if the user function contains common properties of an Express app.
95-
* @param userFunction
96-
*/
97-
const isExpressApp = (userFunction: HandlerFunction): boolean => {
98-
const userFunctionProperties = Object.getOwnPropertyNames(userFunction);
99-
return COMMON_EXPRESS_OBJECT_PROPERTIES.every(prop =>
100-
userFunctionProperties.includes(prop)
101-
);
102-
};

Diff for: src/server.ts

+2-4
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ import {wrapUserFunction} from './function_wrappers';
2626
import {asyncLocalStorageMiddleware} from './async_local_storage';
2727
import {executionContextMiddleware} from './execution_context';
2828
import {FrameworkOptions} from './options';
29-
import {createPropagateErrorToClientErrorHandleMiddleware} from './middleware/propagate_error_to_client_error_handle';
29+
import {injectUserFunctionErrorHandleMiddlewareChain} from './middleware/inject_user_function_error_handle_middleware_chain';
3030

3131
/**
3232
* Creates and configures an Express application and returns an HTTP server
@@ -174,9 +174,7 @@ export function getServer(
174174
}
175175

176176
if (options.propagateFrameworkErrors) {
177-
const errorHandleMiddleware =
178-
createPropagateErrorToClientErrorHandleMiddleware(userFunction);
179-
app.use(errorHandleMiddleware);
177+
injectUserFunctionErrorHandleMiddlewareChain(app, userFunction);
180178
}
181179

182180
return http.createServer(app);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import * as assert from 'assert';
2+
import {NextFunction} from 'express';
3+
import {Request, Response} from '../../src';
4+
import * as express from 'express';
5+
import {injectUserFunctionErrorHandleMiddlewareChain} from '../../src/middleware/inject_user_function_error_handle_middleware_chain';
6+
import {ILayer} from 'express-serve-static-core';
7+
8+
describe('injectUserFunctionErrorHandleMiddlewareChain', () => {
9+
it('user app with error handle middleware injects into framework app', () => {
10+
const frameworkApp = express();
11+
const userApp = express();
12+
userApp.use(appBErrorHandle);
13+
function appBErrorHandle(
14+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
15+
_err: Error,
16+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
17+
_req: Request,
18+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
19+
_res: Response,
20+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
21+
_next: NextFunction
22+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
23+
): any {}
24+
userApp.use(appBFollowUpMiddleware);
25+
function appBFollowUpMiddleware(
26+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
27+
_req: Request,
28+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
29+
_res: Response,
30+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
31+
_next: NextFunction
32+
) {}
33+
34+
injectUserFunctionErrorHandleMiddlewareChain(frameworkApp, userApp);
35+
36+
const appAMiddleware = frameworkApp._router.stack;
37+
const appAMiddlewareNames = appAMiddleware.map(
38+
(middleware: ILayer) => middleware.name
39+
);
40+
41+
assert.deepStrictEqual(appAMiddlewareNames, [
42+
'query',
43+
'expressInit',
44+
'appBErrorHandle',
45+
'appBFollowUpMiddleware',
46+
]);
47+
});
48+
49+
it('user app without error handle not injected into framework app', () => {
50+
const frameworkApp = express();
51+
const userApp = express();
52+
userApp.use(appBFollowUpMiddleware);
53+
function appBFollowUpMiddleware(
54+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
55+
_req: Request,
56+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
57+
_res: Response,
58+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
59+
_next: NextFunction
60+
) {}
61+
62+
injectUserFunctionErrorHandleMiddlewareChain(frameworkApp, userApp);
63+
64+
assert.strictEqual('_router' in frameworkApp, false);
65+
});
66+
67+
it('non-express user app not injected into framework app', () => {
68+
const frameworkApp = express();
69+
const userApp = (_req: Request, res: Response) => {
70+
res.send('Hello, World!');
71+
};
72+
73+
injectUserFunctionErrorHandleMiddlewareChain(frameworkApp, userApp);
74+
75+
assert.strictEqual('_router' in frameworkApp, false);
76+
});
77+
});

Diff for: test/middleware/propagate_error_to_client_error_handle.ts

-73
This file was deleted.

0 commit comments

Comments
 (0)