Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Native SNS and DynamoDB Support #468

Merged
merged 8 commits into from
Oct 6, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -114,15 +114,17 @@ test('getRequestResponse: without headers', async (done) => {
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 @@ -135,16 +137,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 @@ -160,10 +164,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 @@ -176,8 +181,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 @@ -201,8 +206,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 @@ -225,8 +230,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 @@ -249,8 +254,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 @@ -273,8 +278,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 @@ -295,8 +300,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