Skip to content

Commit 2fc508c

Browse files
committed
feat: propagate framework errors to client when option enabled
1 parent e20f823 commit 2fc508c

File tree

2 files changed

+94
-0
lines changed

2 files changed

+94
-0
lines changed

src/server.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +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';
2930

3031
/**
3132
* Creates and configures an Express application and returns an HTTP server
@@ -172,5 +173,11 @@ export function getServer(
172173
app.post('/*', requestHandler);
173174
}
174175

176+
if (options.propagateFrameworkErrors) {
177+
const errorHandleMiddleware =
178+
createPropagateErrorToClientErrorHandleMiddleware(userFunction);
179+
app.use(errorHandleMiddleware);
180+
}
181+
175182
return http.createServer(app);
176183
}

test/integration/http.ts

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ import * as supertest from 'supertest';
1818

1919
import * as functions from '../../src/index';
2020
import {getTestServer} from '../../src/testing';
21+
import {Request, Response, NextFunction} from 'express';
22+
import * as express from 'express';
23+
import {getServer} from '../../src/server';
2124

2225
describe('HTTP Function', () => {
2326
let callCount = 0;
@@ -111,4 +114,88 @@ describe('HTTP Function', () => {
111114
assert.strictEqual(callCount, test.expectedCallCount);
112115
});
113116
});
117+
118+
it('default error handler', async () => {
119+
const app = express();
120+
app.post('/foo', async (req, res) => {
121+
res.send('Foo!');
122+
});
123+
app.use(
124+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
125+
(_err: Error, _req: Request, res: Response, _next: NextFunction) => {
126+
res.status(500).send('Caught error!');
127+
}
128+
);
129+
functions.http('testHttpFunction', app);
130+
const malformedBody = '{"key": "value", "anotherKey": }';
131+
const st = supertest(
132+
getServer(app, {
133+
port: '',
134+
target: '',
135+
sourceLocation: '',
136+
signatureType: 'http',
137+
printHelp: false,
138+
enableExecutionId: false,
139+
timeoutMilliseconds: 0,
140+
ignoredRoutes: null,
141+
propagateFrameworkErrors: false,
142+
})
143+
);
144+
const resBody =
145+
'<!DOCTYPE html>\n' +
146+
'<html lang="en">\n' +
147+
'<head>\n' +
148+
'<meta charset="utf-8">\n' +
149+
'<title>Error</title>\n' +
150+
'</head>\n' +
151+
'<body>\n' +
152+
'<pre>SyntaxError: Unexpected token &#39;}&#39;, ...&quot;therKey&quot;: }&quot; is not valid JSON<br> &nbsp; &nbsp;at JSON.parse (&lt;anonymous&gt;)<br> &nbsp; &nbsp;at parse (/Users/gregei/IdeaProjects/functions-framework-nodejs/node_modules/body-parser/lib/types/json.js:92:19)<br> &nbsp; &nbsp;at /Users/gregei/IdeaProjects/functions-framework-nodejs/node_modules/body-parser/lib/read.js:128:18<br> &nbsp; &nbsp;at AsyncResource.runInAsyncScope (node:async_hooks:211:14)<br> &nbsp; &nbsp;at invokeCallback (/Users/gregei/IdeaProjects/functions-framework-nodejs/node_modules/raw-body/index.js:238:16)<br> &nbsp; &nbsp;at done (/Users/gregei/IdeaProjects/functions-framework-nodejs/node_modules/raw-body/index.js:227:7)<br> &nbsp; &nbsp;at IncomingMessage.onEnd (/Users/gregei/IdeaProjects/functions-framework-nodejs/node_modules/raw-body/index.js:287:7)<br> &nbsp; &nbsp;at IncomingMessage.emit (node:events:518:28)<br> &nbsp; &nbsp;at IncomingMessage.emit (node:domain:552:15)<br> &nbsp; &nbsp;at endReadableNT (node:internal/streams/readable:1698:12)<br> &nbsp; &nbsp;at process.processTicksAndRejections (node:internal/process/task_queues:90:21)</pre>\n' +
153+
'</body>\n' +
154+
'</html>\n';
155+
156+
const response = await st
157+
.post('/foo')
158+
.set('Content-Type', 'application/json')
159+
.send(malformedBody);
160+
161+
assert.strictEqual(response.status, 400);
162+
assert.equal(response.text, resBody);
163+
});
164+
165+
it('user application error handler', async () => {
166+
const app = express();
167+
const resBody = 'Caught error!';
168+
app.post('/foo', async (req, res) => {
169+
res.send('Foo!');
170+
});
171+
app.use(
172+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
173+
(_err: Error, _req: Request, res: Response, _next: NextFunction) => {
174+
res.status(500).send(resBody);
175+
}
176+
);
177+
functions.http('testHttpFunction', app);
178+
const malformedBody = '{"key": "value", "anotherKey": }';
179+
const st = supertest(
180+
getServer(app, {
181+
port: '',
182+
target: '',
183+
sourceLocation: '',
184+
signatureType: 'http',
185+
printHelp: false,
186+
enableExecutionId: false,
187+
timeoutMilliseconds: 0,
188+
ignoredRoutes: null,
189+
propagateFrameworkErrors: true,
190+
})
191+
);
192+
193+
const response = await st
194+
.post('/foo')
195+
.set('Content-Type', 'application/json')
196+
.send(malformedBody);
197+
198+
assert.strictEqual(response.status, 500);
199+
assert.equal(response.text, resBody);
200+
});
114201
});

0 commit comments

Comments
 (0)