Skip to content

Commit 822d69e

Browse files
authored
Merge pull request #9 from iDanielBot/feat/5xx-error-codes
feat: 502 / 504 error codes
2 parents a3ceaaa + 25e8ff1 commit 822d69e

File tree

2 files changed

+62
-11
lines changed

2 files changed

+62
-11
lines changed

src/lambda-invoker.test.ts

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,21 +16,21 @@ describe('Lambda invoker', () => {
1616
StatusCode: 200,
1717
Payload: Uint8ArrayBlobAdapter.fromString(JSON.stringify({
1818
statusCode: 404,
19-
body: {
19+
body: JSON.stringify({
2020
message: 'User not found',
21-
},
21+
}),
2222
}))
2323
})
2424

2525
// then
2626
const resp = await invokeLambda({ payload: '', functionName: 'test' })
2727
expect(resp.statusCode).toEqual(404)
28-
expect(resp.body).toEqual({
28+
expect(resp.body).toEqual(JSON.stringify({
2929
message: 'User not found',
30-
})
30+
}))
3131
})
3232

33-
it('process 200 response with timeout error returned', async () => {
33+
it('converts 200 responses with timeout to 504 Gateway Timeout error', async () => {
3434
// given
3535
mockLambda.on(InvokeCommand).resolves({
3636
StatusCode: 200,
@@ -41,10 +41,18 @@ describe('Lambda invoker', () => {
4141
})
4242

4343
// then
44-
await expect(invokeLambda({ payload: '', functionName: 'test' })).rejects.toThrow()
44+
const resp = await invokeLambda({ payload: '', functionName: 'test' })
45+
expect(resp.statusCode).toEqual(504)
46+
expect(resp.body).toEqual(
47+
JSON.stringify({
48+
"message": "Service timed out.",
49+
"functionName": "test", "error": "Unhandled",
50+
"payload": JSON.stringify({ "errorMessage": "2023-01-09T10:48:53.262Z 873b04e4-991b-4d5f-b7ca-b99df84bfd66 Task timed out after 1.00 seconds" })
51+
})
52+
)
4553
})
4654

47-
it('process 200 response with non APIGatewayProxyStructuredResultV2 payload', async () => {
55+
it('converts 200 response with non APIGatewayProxyStructuredResultV2 payload to 502 Bad Gateway error', async () => {
4856
// given
4957
mockLambda.on(InvokeCommand).resolves({
5058
StatusCode: 200,
@@ -54,6 +62,19 @@ describe('Lambda invoker', () => {
5462
})
5563

5664
// then
57-
await expect(invokeLambda({ payload: '', functionName: 'test' })).rejects.toThrow()
65+
const resp = await invokeLambda({ payload: '', functionName: 'test' })
66+
expect(resp.statusCode).toEqual(502)
67+
expect(resp.body).toEqual(
68+
JSON.stringify({
69+
"message": "Service returned a wrongly formatted response.",
70+
"functionName": "test",
71+
"payload": JSON.stringify({
72+
username: 'this should fail'
73+
}),
74+
"payloadJson": {
75+
username: 'this should fail'
76+
}
77+
})
78+
)
5879
})
5980
})

src/lambda-invoker.ts

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@ import { APIGatewayProxyStructuredResultV2 } from 'aws-lambda'
22
import { InvocationResponse, Lambda } from '@aws-sdk/client-lambda'
33
import { safeParseJson } from './util'
44

5+
import createDebug from 'debug'
6+
7+
const debug = createDebug('openapi-lambda-adapter')
8+
59
let lambdaClient: Lambda
610
const getLambdaClient = () => {
711
if (!lambdaClient) {
@@ -23,21 +27,37 @@ export const invokeLambda = async (params: { payload: string, functionName: stri
2327
Payload: Buffer.from(payload)
2428
})
2529

30+
debug('Lambda invocation response', res)
31+
2632
if (!isSuccess(res.StatusCode)) {
27-
throw new Error(`Failed to invoke lambda ${params.functionName} synchronously`)
33+
return toApiGwResp({
34+
statusCode: 502,
35+
body: { message: `Failed to invoke lambda ${params.functionName} synchronously. Check your IAM setup.` }
36+
})
2837
}
2938

3039
const safePayload = Buffer.from(res.Payload).toString('utf-8')
3140

3241
if (hasTimeout(res, safePayload)) {
33-
throw new Error(safePayload)
42+
return toApiGwResp({
43+
statusCode: 504,
44+
body: { message: 'Service timed out.', functionName: params.functionName, error: res.FunctionError, payload: safePayload }
45+
})
3446
}
3547

3648
const response = safeParseJson(safePayload)
3749

3850
// response is not in JSON format
3951
if (!isApiGwStructuredResp(response)) {
40-
throw new Error('Received a response which is not in APIGatewayProxyStructuredResultV2 format')
52+
return toApiGwResp({
53+
statusCode: 502,
54+
body: {
55+
message: 'Service returned a wrongly formatted response.',
56+
functionName: params.functionName,
57+
payload: safePayload,
58+
payloadJson: response
59+
}
60+
})
4161
}
4262

4363
return response
@@ -58,3 +78,13 @@ const isApiGwStructuredResp = (resp: unknown): resp is APIGatewayProxyStructured
5878
}
5979

6080
const hasTimeout = (res: InvocationResponse, safePayload: string) => 'FunctionError' in res && safePayload?.includes('Task timed out')
81+
82+
const toApiGwResp = (params: { statusCode: number, body: Record<string, unknown> }) => {
83+
return {
84+
statusCode: params.statusCode,
85+
headers: {
86+
'Content-Type': 'application/json'
87+
},
88+
body: JSON.stringify(params.body)
89+
}
90+
}

0 commit comments

Comments
 (0)