Skip to content

Commit 276a6da

Browse files
authored
feat: add support for SNS and DynamoDB event sources (#468)
1 parent 8542d9a commit 276a6da

File tree

25 files changed

+2104
-82
lines changed

25 files changed

+2104
-82
lines changed

README.md

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,50 @@ serverlessExpress({
170170
})
171171
```
172172

173+
#### eventSourceRoutes
174+
175+
Introduced in `@vendia/[email protected]` native support for `aws:sns` and `aws:dynamodb` events were introduced.
176+
177+
A single function can be configured to handle events from SNS and DynamoDB, as well as the previously supported events.
178+
179+
Assuming the following function configuration in `serverless.yml`:
180+
181+
```yaml
182+
functions:
183+
lambda-handler:
184+
handler: src/lambda.handler
185+
events:
186+
- http:
187+
path: /
188+
method: get
189+
- sns:
190+
topicName: my-topic
191+
- stream:
192+
type: dynamodb
193+
arn: arn:aws:dynamodb:us-east-1:012345678990:table/my-table/stream/2021-07-15T15:05:51.683
194+
```
195+
196+
And the following configuration:
197+
198+
```js
199+
serverlessExpress({
200+
app,
201+
eventSourceRoutes: {
202+
'AWS_SNS': '/sns',
203+
'AWS_DYNAMODB': '/dynamodb'
204+
}
205+
})
206+
```
207+
208+
Events from SNS and DynamoDB will `POST` to the routes configured in Express to handle `/sns` and `/dynamodb`,
209+
respectively.
210+
211+
Also, to ensure the events propagated from an internal event and not externally, it is **highly recommended** to
212+
ensure the `Host` header matches:
213+
214+
- SNS: `sns.amazonaws.com`
215+
- DynamoDB: `dynamodb.amazonaws.com`
216+
173217
### logSettings
174218

175219
Specify log settings that are passed to the default logger. Currently, you can only set the log `level`.

__tests__/unit.api-gateway-v2.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
const eventSources = require('../src/event-sources')
2-
const testUtils = require('../src/event-sources/utils.test')
2+
const testUtils = require('./utils')
33

44
const apiGatewayEventSource = eventSources.getEventSource({
55
eventSourceName: 'AWS_API_GATEWAY_V2'
@@ -15,7 +15,7 @@ test('request has correct headers', () => {
1515
})
1616

1717
function getReq () {
18-
const event = testUtils.sam_httpapi_event
18+
const event = testUtils.samHttpApiEvent
1919
const request = apiGatewayEventSource.getRequest({ event })
2020
return request
2121
}

__tests__/unit.dynamodb.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
const eventSources = require('../src/event-sources')
2+
const testUtils = require('./utils')
3+
4+
const dynamodbEventSource = eventSources.getEventSource({
5+
eventSourceName: 'AWS_DYNAMODB'
6+
})
7+
8+
test('request is correct', () => {
9+
const req = getReq()
10+
expect(typeof req).toEqual('object')
11+
expect(req.headers).toEqual({ host: 'dynamodb.amazonaws.com' })
12+
expect(req.body).toEqual(testUtils.dynamoDbEvent)
13+
expect(req.method).toEqual('POST')
14+
})
15+
16+
function getReq () {
17+
const event = testUtils.dynamoDbEvent
18+
const request = dynamodbEventSource.getRequest({ event })
19+
return request
20+
}

__tests__/unit.js

Lines changed: 26 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -112,15 +112,17 @@ test('getRequestResponse: without headers', async () => {
112112
describe('respondToEventSourceWithError', () => {
113113
test('responds with 500 status', () => {
114114
return new Promise(
115-
(resolve) => {
116-
const context = new MockContext(resolve)
115+
(resolve, reject) => {
116+
const context = new MockContext(resolve, reject)
117117
const contextResolver = {
118-
succeed: (p) => context.succeed(p.response)
118+
succeed: (p) => context.succeed(p.response),
119+
fail: (p) => context.fail(p.error)
119120
}
120121
serverlessExpressTransport.respondToEventSourceWithError({
121122
error: new Error('ERROR'),
122123
resolver: contextResolver,
123124
log,
125+
eventSourceName: 'AWS_API_GATEWAY_V1',
124126
eventSource: apiGatewayEventSource
125127
})
126128
}
@@ -133,16 +135,18 @@ describe('respondToEventSourceWithError', () => {
133135
})
134136
test('responds with 500 status and stack trace', () => {
135137
return new Promise(
136-
(resolve) => {
137-
const context = new MockContext(resolve)
138+
(resolve, reject) => {
139+
const context = new MockContext(resolve, reject)
138140
const contextResolver = {
139-
succeed: (p) => context.succeed(p.response)
141+
succeed: (p) => context.succeed(p.response),
142+
fail: (p) => context.fail(p.error)
140143
}
141144
serverlessExpressTransport.respondToEventSourceWithError({
142145
error: new Error('There was an error...'),
143146
resolver: contextResolver,
144147
log,
145148
respondWithErrors: true,
149+
eventSourceName: 'AWS_API_GATEWAY_V1',
146150
eventSource: apiGatewayEventSource
147151
})
148152
}
@@ -158,10 +162,11 @@ describe('respondToEventSourceWithError', () => {
158162
})
159163
})
160164

161-
function getContextResolver (resolve) {
162-
const context = new MockContext(resolve)
165+
function getContextResolver (resolve, reject) {
166+
const context = new MockContext(resolve, reject)
163167
const contextResolver = {
164-
succeed: (p) => context.succeed(p.response)
168+
succeed: (p) => context.succeed(p.response),
169+
fail: (p) => context.fail(p.error)
165170
}
166171

167172
return contextResolver
@@ -174,8 +179,8 @@ describe.skip('forwardResponse: content-type encoding', () => {
174179
const { requestResponse } = await getReqRes(multiValueHeaders)
175180
const response = new ServerlessResponse(requestResponse.request)
176181
return new Promise(
177-
(resolve) => {
178-
const contextResolver = getContextResolver(resolve)
182+
(resolve, reject) => {
183+
const contextResolver = getContextResolver(resolve, reject)
179184
serverlessExpressTransport.forwardResponse({
180185
binaryMimeTypes,
181186
response,
@@ -199,8 +204,8 @@ describe.skip('forwardResponse: content-type encoding', () => {
199204
const body = 'hello world'
200205
const response = new ServerlessResponse(200, multiValueHeaders, body)
201206
return new Promise(
202-
(resolve) => {
203-
const contextResolver = getContextResolver(resolve)
207+
(resolve, reject) => {
208+
const contextResolver = getContextResolver(resolve, reject)
204209
serverlessExpressTransport.forwardResponse({
205210
binaryMimeTypes,
206211
response,
@@ -223,8 +228,8 @@ describe.skip('forwardResponse: content-type encoding', () => {
223228
const body = JSON.stringify({ hello: 'world' })
224229
const response = new ServerlessResponse(200, multiValueHeaders, body)
225230
return new Promise(
226-
(resolve) => {
227-
const contextResolver = getContextResolver(resolve)
231+
(resolve, reject) => {
232+
const contextResolver = getContextResolver(resolve, reject)
228233
serverlessExpressTransport.forwardResponse({
229234
binaryMimeTypes,
230235
response,
@@ -247,8 +252,8 @@ describe.skip('forwardResponse: content-type encoding', () => {
247252
const body = 'hello world'
248253
const response = new ServerlessResponse(200, multiValueHeaders, body)
249254
return new Promise(
250-
(resolve) => {
251-
const contextResolver = getContextResolver(resolve)
255+
(resolve, reject) => {
256+
const contextResolver = getContextResolver(resolve, reject)
252257
serverlessExpressTransport.forwardResponse({
253258
binaryMimeTypes,
254259
response,
@@ -271,8 +276,8 @@ describe.skip('forwardResponse: content-type encoding', () => {
271276
const body = 'hello world'
272277
const response = new ServerlessResponse(200, multiValueHeaders, body)
273278
return new Promise(
274-
(resolve) => {
275-
const contextResolver = getContextResolver(resolve)
279+
(resolve, reject) => {
280+
const contextResolver = getContextResolver(resolve, reject)
276281
serverlessExpressTransport.forwardResponse({
277282
binaryMimeTypes,
278283
response,
@@ -293,8 +298,8 @@ describe.skip('forwardResponse: content-type encoding', () => {
293298
describe('makeResolver', () => {
294299
test('CONTEXT (specified)', () => {
295300
return new Promise(
296-
(resolve) => {
297-
const context = new MockContext(resolve)
301+
(resolve, reject) => {
302+
const context = new MockContext(resolve, reject)
298303
const contextResolver = makeResolver({
299304
context,
300305
resolutionMode: 'CONTEXT'

__tests__/unit.sns.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
const eventSources = require('../src/event-sources')
2+
const testUtils = require('./utils')
3+
4+
const snsEventSource = eventSources.getEventSource({
5+
eventSourceName: 'AWS_SNS'
6+
})
7+
8+
test('request is correct', () => {
9+
const req = getReq()
10+
expect(typeof req).toEqual('object')
11+
expect(req.headers).toEqual({ host: 'sns.amazonaws.com' })
12+
expect(req.body).toEqual(testUtils.snsEvent)
13+
expect(req.method).toEqual('POST')
14+
})
15+
16+
function getReq () {
17+
const event = testUtils.snsEvent
18+
const request = snsEventSource.getRequest({ event })
19+
return request
20+
}

0 commit comments

Comments
 (0)