Skip to content

Commit

Permalink
feat: add support for SNS and DynamoDB event sources (#468)
Browse files Browse the repository at this point in the history
  • Loading branch information
cnuss authored Oct 6, 2021
1 parent 8542d9a commit 276a6da
Show file tree
Hide file tree
Showing 25 changed files with 2,104 additions and 82 deletions.
44 changes: 44 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,50 @@ serverlessExpress({
})
```

#### eventSourceRoutes

Introduced in `@vendia/serverless-express@4.4.0` native support for `aws:sns` and `aws:dynamodb` events were introduced.

A single function can be configured to handle events from SNS and DynamoDB, as well as the previously supported events.

Assuming the following function configuration in `serverless.yml`:

```yaml
functions:
lambda-handler:
handler: src/lambda.handler
events:
- http:
path: /
method: get
- sns:
topicName: my-topic
- stream:
type: dynamodb
arn: arn:aws:dynamodb:us-east-1:012345678990:table/my-table/stream/2021-07-15T15:05:51.683
```

And the following configuration:

```js
serverlessExpress({
app,
eventSourceRoutes: {
'AWS_SNS': '/sns',
'AWS_DYNAMODB': '/dynamodb'
}
})
```

Events from SNS and DynamoDB will `POST` to the routes configured in Express to handle `/sns` and `/dynamodb`,
respectively.

Also, to ensure the events propagated from an internal event and not externally, it is **highly recommended** to
ensure the `Host` header matches:

- SNS: `sns.amazonaws.com`
- DynamoDB: `dynamodb.amazonaws.com`

### logSettings

Specify log settings that are passed to the default logger. Currently, you can only set the log `level`.
Expand Down
4 changes: 2 additions & 2 deletions __tests__/unit.api-gateway-v2.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
const eventSources = require('../src/event-sources')
const testUtils = require('../src/event-sources/utils.test')
const testUtils = require('./utils')

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

function getReq () {
const event = testUtils.sam_httpapi_event
const event = testUtils.samHttpApiEvent
const request = apiGatewayEventSource.getRequest({ event })
return request
}
20 changes: 20 additions & 0 deletions __tests__/unit.dynamodb.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
const eventSources = require('../src/event-sources')
const testUtils = require('./utils')

const dynamodbEventSource = eventSources.getEventSource({
eventSourceName: 'AWS_DYNAMODB'
})

test('request is correct', () => {
const req = getReq()
expect(typeof req).toEqual('object')
expect(req.headers).toEqual({ host: 'dynamodb.amazonaws.com' })
expect(req.body).toEqual(testUtils.dynamoDbEvent)
expect(req.method).toEqual('POST')
})

function getReq () {
const event = testUtils.dynamoDbEvent
const request = dynamodbEventSource.getRequest({ event })
return request
}
47 changes: 26 additions & 21 deletions __tests__/unit.js
Original file line number Diff line number Diff line change
Expand Up @@ -112,15 +112,17 @@ test('getRequestResponse: without headers', async () => {
describe('respondToEventSourceWithError', () => {
test('responds with 500 status', () => {
return new Promise(
(resolve) => {
const context = new MockContext(resolve)
(resolve, reject) => {
const context = new MockContext(resolve, reject)
const contextResolver = {
succeed: (p) => context.succeed(p.response)
succeed: (p) => context.succeed(p.response),
fail: (p) => context.fail(p.error)
}
serverlessExpressTransport.respondToEventSourceWithError({
error: new Error('ERROR'),
resolver: contextResolver,
log,
eventSourceName: 'AWS_API_GATEWAY_V1',
eventSource: apiGatewayEventSource
})
}
Expand All @@ -133,16 +135,18 @@ describe('respondToEventSourceWithError', () => {
})
test('responds with 500 status and stack trace', () => {
return new Promise(
(resolve) => {
const context = new MockContext(resolve)
(resolve, reject) => {
const context = new MockContext(resolve, reject)
const contextResolver = {
succeed: (p) => context.succeed(p.response)
succeed: (p) => context.succeed(p.response),
fail: (p) => context.fail(p.error)
}
serverlessExpressTransport.respondToEventSourceWithError({
error: new Error('There was an error...'),
resolver: contextResolver,
log,
respondWithErrors: true,
eventSourceName: 'AWS_API_GATEWAY_V1',
eventSource: apiGatewayEventSource
})
}
Expand All @@ -158,10 +162,11 @@ describe('respondToEventSourceWithError', () => {
})
})

function getContextResolver (resolve) {
const context = new MockContext(resolve)
function getContextResolver (resolve, reject) {
const context = new MockContext(resolve, reject)
const contextResolver = {
succeed: (p) => context.succeed(p.response)
succeed: (p) => context.succeed(p.response),
fail: (p) => context.fail(p.error)
}

return contextResolver
Expand All @@ -174,8 +179,8 @@ describe.skip('forwardResponse: content-type encoding', () => {
const { requestResponse } = await getReqRes(multiValueHeaders)
const response = new ServerlessResponse(requestResponse.request)
return new Promise(
(resolve) => {
const contextResolver = getContextResolver(resolve)
(resolve, reject) => {
const contextResolver = getContextResolver(resolve, reject)
serverlessExpressTransport.forwardResponse({
binaryMimeTypes,
response,
Expand All @@ -199,8 +204,8 @@ describe.skip('forwardResponse: content-type encoding', () => {
const body = 'hello world'
const response = new ServerlessResponse(200, multiValueHeaders, body)
return new Promise(
(resolve) => {
const contextResolver = getContextResolver(resolve)
(resolve, reject) => {
const contextResolver = getContextResolver(resolve, reject)
serverlessExpressTransport.forwardResponse({
binaryMimeTypes,
response,
Expand All @@ -223,8 +228,8 @@ describe.skip('forwardResponse: content-type encoding', () => {
const body = JSON.stringify({ hello: 'world' })
const response = new ServerlessResponse(200, multiValueHeaders, body)
return new Promise(
(resolve) => {
const contextResolver = getContextResolver(resolve)
(resolve, reject) => {
const contextResolver = getContextResolver(resolve, reject)
serverlessExpressTransport.forwardResponse({
binaryMimeTypes,
response,
Expand All @@ -247,8 +252,8 @@ describe.skip('forwardResponse: content-type encoding', () => {
const body = 'hello world'
const response = new ServerlessResponse(200, multiValueHeaders, body)
return new Promise(
(resolve) => {
const contextResolver = getContextResolver(resolve)
(resolve, reject) => {
const contextResolver = getContextResolver(resolve, reject)
serverlessExpressTransport.forwardResponse({
binaryMimeTypes,
response,
Expand All @@ -271,8 +276,8 @@ describe.skip('forwardResponse: content-type encoding', () => {
const body = 'hello world'
const response = new ServerlessResponse(200, multiValueHeaders, body)
return new Promise(
(resolve) => {
const contextResolver = getContextResolver(resolve)
(resolve, reject) => {
const contextResolver = getContextResolver(resolve, reject)
serverlessExpressTransport.forwardResponse({
binaryMimeTypes,
response,
Expand All @@ -293,8 +298,8 @@ describe.skip('forwardResponse: content-type encoding', () => {
describe('makeResolver', () => {
test('CONTEXT (specified)', () => {
return new Promise(
(resolve) => {
const context = new MockContext(resolve)
(resolve, reject) => {
const context = new MockContext(resolve, reject)
const contextResolver = makeResolver({
context,
resolutionMode: 'CONTEXT'
Expand Down
20 changes: 20 additions & 0 deletions __tests__/unit.sns.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
const eventSources = require('../src/event-sources')
const testUtils = require('./utils')

const snsEventSource = eventSources.getEventSource({
eventSourceName: 'AWS_SNS'
})

test('request is correct', () => {
const req = getReq()
expect(typeof req).toEqual('object')
expect(req.headers).toEqual({ host: 'sns.amazonaws.com' })
expect(req.body).toEqual(testUtils.snsEvent)
expect(req.method).toEqual('POST')
})

function getReq () {
const event = testUtils.snsEvent
const request = snsEventSource.getRequest({ event })
return request
}
Loading

0 comments on commit 276a6da

Please sign in to comment.