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

New Router: CloudFormation Custom Resource #1268

Open
wants to merge 15 commits into
base: main
Choose a base branch
from
Open
46 changes: 46 additions & 0 deletions packages/cloudformation-router/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<div align="center">
<h1>Middy cloudformation-router lambda handler</h1>
<img alt="Middy logo" src="https://raw.githubusercontent.com/middyjs/middy/main/docs/img/middy-logo.svg"/>
<p><strong>CloudFormation Custom Response router for the middy framework, the stylish Node.js middleware engine for AWS Lambda</strong></p>
<p>
<a href="https://www.npmjs.com/package/@middy/cloudformation-router?activeTab=versions">
<img src="https://badge.fury.io/js/%40middy%cloudformation-router.svg" alt="npm version" style="max-width:100%;">
</a>
<a href="https://packagephobia.com/result?p=@middy/cloudformation-router">
<img src="https://packagephobia.com/badge?p=@middy/cloudformation-router" alt="npm install size" style="max-width:100%;">
</a>
<a href="https://github.com/middyjs/middy/actions/workflows/tests.yml">
<img src="https://github.com/middyjs/middy/actions/workflows/tests.yml/badge.svg?branch=main&event=push" alt="GitHub Actions CI status badge" style="max-width:100%;">
</a>
<br/>
<a href="https://standardjs.com/">
<img src="https://img.shields.io/badge/code_style-standard-brightgreen.svg" alt="Standard Code Style" style="max-width:100%;">
</a>
<a href="https://snyk.io/test/github/middyjs/middy">
<img src="https://snyk.io/test/github/middyjs/middy/badge.svg" alt="Known Vulnerabilities" data-canonical-src="https://snyk.io/test/github/middyjs/middy" style="max-width:100%;">
</a>
<a href="https://github.com/middyjs/middy/actions/workflows/sast.yml">
<img src="https://github.com/middyjs/middy/actions/workflows/sast.yml/badge.svg
?branch=main&event=push" alt="CodeQL" style="max-width:100%;">
</a>
<a href="https://bestpractices.coreinfrastructure.org/projects/5280">
<img src="https://bestpractices.coreinfrastructure.org/projects/5280/badge" alt="Core Infrastructure Initiative (CII) Best Practices" style="max-width:100%;">
</a>
<br/>
<a href="https://gitter.im/middyjs/Lobby">
<img src="https://badges.gitter.im/gitterHQ/gitter.svg" alt="Chat on Gitter" style="max-width:100%;">
</a>
<a href="https://stackoverflow.com/questions/tagged/middy?sort=Newest&uqlId=35052">
<img src="https://img.shields.io/badge/StackOverflow-[middy]-yellow" alt="Ask questions on StackOverflow" style="max-width:100%;">
</a>
</p>
<p>You can read the documentation at: <a href="https://middy.js.org/docs/routers/cloudformation-router">https://middy.js.org/docs/routers/cloudformation-router</a></p>
</div>

## License

Licensed under [MIT License](LICENSE). Copyright (c) 2017-2024 [Luciano Mammino](https://github.com/lmammino), [will Farrell](https://github.com/willfarrell), and the [Middy team](https://github.com/middyjs/middy/graphs/contributors).

<a href="https://app.fossa.io/projects/git%2Bgithub.com%2Fmiddyjs%2Fmiddy?ref=badge_large">
<img src="https://app.fossa.io/api/projects/git%2Bgithub.com%2Fmiddyjs%2Fmiddy.svg?type=large" alt="FOSSA Status" style="max-width:100%;">
</a>
46 changes: 46 additions & 0 deletions packages/cloudformation-router/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
const defaults = {
routes: [],
notFoundResponse: ({ requestType }) => {
return {
Status: 'FAILED',
Reason: `Route ${requestType} does not exist. @middy/cloudformation-router`
}
}
}
const cloudformationCustomResourceRouteHandler = (opts = {}) => {
if (Array.isArray(opts)) {
opts = { routes: opts }
}
const { routes, notFoundResponse } = { ...defaults, ...opts }

const routesStatic = {}
for (const route of routes) {
const { requestType, handler } = route

// Static
routesStatic[requestType] = handler
}

return (event, context, abort) => {
const { RequestType: requestType } = event
if (!requestType) {
return notFoundResponse({ requestType })

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would for example expect that here we report to cloudformation that we have a failure, because if we don't do that cloudformation will simply wait x time until it timeouts whilst we already know that we won't be handling the request

}

// Static
const handler = routesStatic[requestType]
if (typeof handler !== 'undefined') {
const response = handler(event, context, abort)
response.Status ??= 'SUCCESS'
response.RequestId ??= event.RequestId
response.LogicalResourceId ??= event.LogicalResourceId
response.StackId ??= event.StackId
return response
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be nice if it also reported to the responseUrl, I feel like even though the abstraction is nice, it doesn't really add that much value currently as it's still the responsibility of the developer to correctly handle all errors and respond.

The auto reporting implementation is more what I was looking for honestly


// Not Found
return notFoundResponse({ requestType })
}
}

export default cloudformationCustomResourceRouteHandler
70 changes: 70 additions & 0 deletions packages/cloudformation-router/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
{
"name": "@middy/cloudformation-router",
"version": "6.0.0",
"description": "CloudFormation Custom Response event router for the middy framework",
"type": "module",
"engines": {
"node": ">=20"
},
"engineStrict": true,
"publishConfig": {
"access": "public"
},
"module": "./index.js",
"exports": {
".": {
"import": {
"types": "./index.d.ts",
"default": "./index.js"
},
"require": {
"default": "./index.js"
}
}
},
"types": "index.d.ts",
"files": [
"index.js",
"index.d.ts"
],
"scripts": {
"test": "npm run test:unit",
"test:unit": "node --test __tests__/index.js",
"test:benchmark": "node __benchmarks__/index.js"
},
"license": "MIT",
"keywords": [
"Lambda",
"Middleware",
"Serverless",
"Framework",
"AWS",
"AWS Lambda",
"Middy",
"CloudFormation",
"Custom Response",
"router"
],
"author": {
"name": "Middy contributors",
"url": "https://github.com/middyjs/middy/graphs/contributors"
},
"repository": {
"type": "git",
"url": "github:middyjs/middy",
"directory": "packages/cloudformation-router"
},
"bugs": {
"url": "https://github.com/middyjs/middy/issues"
},
"homepage": "https://middy.js.org",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/willfarrell"
},
"devDependencies": {
"@middy/core": "6.0.0",
"@types/aws-lambda": "^8.10.100"
},
"gitHead": "7a6c0fbb8ab71d6a2171e678697de9f237568431"
}
79 changes: 79 additions & 0 deletions website/docs/routers/cloudformation-router.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
---
title: cloudformation-router
---

This handler can route to requests to one of a nested handler based on `requestType` of a CloudFormation Custom Response event.

## Install

To install this middleware you can use NPM:

```bash
npm install --save @middy/cloudformation-router
```

## Options

- `routes` (array[\{routeKey, handler\}]) (required): Array of route objects.
- `routeKey` (string) (required): AWS formatted request type. ie `Create`, `Update`, `Delete`
- `handler` (function) (required): Any `handler(event, context, {signal})` function
- `notFoundHandler` (function): Override default error thrown with your own custom response. Passes in `{requestType}`

NOTES:

- Reponse parameters are automatically applied for `Status`, `RequestId`, `LogicalResourceId`, and/or `StackId` when not present.
- Errors should be handled as part of the router middleware stack **or** the lambdaHandler middleware stack. Handled errors in the later will trigger the `after` middleware stack of the former.
- Shared middlewares, connected to the router middleware stack, can only be run before the lambdaHandler middleware stack.

## Sample usage

```javascript
import middy from '@middy/core'
import cloudformationRouterHandler from '@middy/cloudformation-router'
import validatorMiddleware from '@middy/validator'

const createHandler = middy()
.use(validatorMiddleware({eventSchema: {...} }))
.handler((event, context) => {
return {
PhysicalResourceId: '...',
Data:{}
}
})

const updateHandler = middy()
.use(validatorMiddleware({eventSchema: {...} }))
.handler((event, context) => {
return {
PhysicalResourceId: '...',
Data: {}
}
})

const deleteHandler = middy()
.use(validatorMiddleware({eventSchema: {...} }))
.handler((event, context) => {
return {
PhysicalResourceId: '...'
}
})

const routes = [
{
requesType: 'Create',
handler: createHandler
},
{
requesType: 'Update',
handler: updateHandler
},
{
routeKey: 'Delete',
handler: deleteHandler
}
]

export const handler = middy()
.use(wsResponseMiddleware())
.handler(cloudformationRouterHandler(routes))
```
Loading