diff --git a/package-lock.json b/package-lock.json index 0e2ae13ab..10a804dc6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2488,6 +2488,10 @@ "resolved": "packages/appconfig", "link": true }, + "node_modules/@middy/cloudformation-response": { + "resolved": "packages/cloudformation-response", + "link": true + }, "node_modules/@middy/cloudformation-router": { "resolved": "packages/cloudformation-router", "link": true @@ -11319,6 +11323,22 @@ "url": "https://github.com/sponsors/willfarrell" } }, + "packages/cloudformation-response": { + "name": "@middy/cloudformation-response", + "version": "6.0.0", + "license": "MIT", + "devDependencies": { + "@middy/core": "6.0.0", + "@types/aws-lambda": "^8.10.100" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/willfarrell" + } + }, "packages/cloudformation-router": { "name": "@middy/cloudformation-router", "version": "6.0.0", diff --git a/packages/cloudformation-response/README.md b/packages/cloudformation-response/README.md new file mode 100644 index 000000000..beb6620f2 --- /dev/null +++ b/packages/cloudformation-response/README.md @@ -0,0 +1,46 @@ +
+

Middy cloudformation lambda middleware

+ Middy logo +

CloudFormation Custom Response event response handling for the middy framework, the stylish Node.js middleware engine for AWS Lambda

+

+ + npm version + + + npm install size + + + GitHub Actions CI status badge + +
+ + Standard Code Style + + + Known Vulnerabilities + + + CodeQL + + + Core Infrastructure Initiative (CII) Best Practices + +
+ + Chat on Gitter + + + Ask questions on StackOverflow + +

+

You can read the documentation at: https://middy.js.org/docs/middleware/cloudformation-response

+
+ +## 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). + + + FOSSA Status + diff --git a/packages/cloudformation-response/__benchmarks__/index.js b/packages/cloudformation-response/__benchmarks__/index.js new file mode 100644 index 000000000..42c8ee848 --- /dev/null +++ b/packages/cloudformation-response/__benchmarks__/index.js @@ -0,0 +1,27 @@ +import { Bench } from 'tinybench' +import middy from '../../core/index.js' +import middleware from '../index.js' + +const bench = new Bench({ time: 1_000 }) + +const context = { + getRemainingTimeInMillis: () => 30000 +} +const setupHandler = () => { + const baseHandler = () => {} + return middy(baseHandler).use(middleware()) +} + +const coldHandler = setupHandler() + +const event = {} +await bench + .add('without cache', async () => { + try { + await coldHandler(event, context) + } catch (e) {} + }) + + .run() + +console.table(bench.table()) diff --git a/packages/cloudformation-response/__tests__/fuzz.js b/packages/cloudformation-response/__tests__/fuzz.js new file mode 100644 index 000000000..b39db8e3c --- /dev/null +++ b/packages/cloudformation-response/__tests__/fuzz.js @@ -0,0 +1,23 @@ +import { test } from 'node:test' +import fc from 'fast-check' +import middy from '../../core/index.js' +import middleware from '../index.js' + +const handler = middy((event) => event).use(middleware()) +const context = { + getRemainingTimeInMillis: () => 1000 +} + +test('fuzz `event` w/ `object`', async () => { + fc.assert( + fc.asyncProperty(fc.object(), async (event) => { + await handler(event, context) + }), + { + numRuns: 100_000, + verbose: 2, + + examples: [] + } + ) +}) diff --git a/packages/cloudformation-response/__tests__/index.js b/packages/cloudformation-response/__tests__/index.js new file mode 100644 index 000000000..d91de9526 --- /dev/null +++ b/packages/cloudformation-response/__tests__/index.js @@ -0,0 +1,88 @@ +import { test } from 'node:test' +import { deepEqual } from 'node:assert/strict' + +import middy from '../../core/index.js' + +import cloudformationResponse from '../index.js' + +const defaultEvent = { + RequestType: 'Create', + RequestId: 'RequestId', + LogicalResourceId: 'LogicalResourceId', + StackId: 'StackId' +} +const context = { + getRemainingTimeInMillis: () => 1000 +} + +test('It should return SUCCESS when empty response', async (t) => { + const handler = middy((event, context) => {}) + + handler.use(cloudformationResponse()) + + const event = defaultEvent + const response = await handler(event, context) + deepEqual(response, { + Status: 'SUCCESS', + RequestId: 'RequestId', + LogicalResourceId: 'LogicalResourceId', + StackId: 'StackId' + }) +}) + +test('It should return SUCCESS when empty object', async (t) => { + const handler = middy((event, context) => { + return {} + }) + + handler.use(cloudformationResponse()) + + const event = defaultEvent + const response = await handler(event, context) + deepEqual(response, { + Status: 'SUCCESS', + RequestId: 'RequestId', + LogicalResourceId: 'LogicalResourceId', + StackId: 'StackId' + }) +}) + +test('It should return FAILURE when error thrown', async (t) => { + const handler = middy((event, context) => { + throw new Error('Internal Error') + }) + + handler.use(cloudformationResponse()) + + const event = defaultEvent + const response = await handler(event, context) + deepEqual(response, { + Status: 'FAILED', + Reason: 'Internal Error', + RequestId: 'RequestId', + LogicalResourceId: 'LogicalResourceId', + StackId: 'StackId' + }) +}) + +test('It should not override response values', async (t) => { + const handler = middy((event, context) => { + return { + Status: 'FAILED', + RequestId: 'RequestId*', + LogicalResourceId: 'LogicalResourceId*', + StackId: 'StackId*' + } + }) + + handler.use(cloudformationResponse()) + + const event = defaultEvent + const response = await handler(event, context) + deepEqual(response, { + Status: 'FAILED', + RequestId: 'RequestId*', + LogicalResourceId: 'LogicalResourceId*', + StackId: 'StackId*' + }) +}) diff --git a/packages/cloudformation-response/index.d.ts b/packages/cloudformation-response/index.d.ts new file mode 100644 index 000000000..95d6904ee --- /dev/null +++ b/packages/cloudformation-response/index.d.ts @@ -0,0 +1,5 @@ +import middy from '@middy/core' + +declare function cloudformationResponse (): middy.MiddlewareObj + +export default cloudformationResponse diff --git a/packages/cloudformation-response/index.js b/packages/cloudformation-response/index.js new file mode 100644 index 000000000..7f6f5ca55 --- /dev/null +++ b/packages/cloudformation-response/index.js @@ -0,0 +1,25 @@ +const cloudformationCustomResourceMiddleware = () => { + const cloudformationCustomResourceMiddlewareAfter = (request) => { + let { response } = request + response ??= {} + response.Status ??= 'SUCCESS' + response.RequestId ??= request.event.RequestId + response.LogicalResourceId ??= request.event.LogicalResourceId + response.StackId ??= request.event.StackId + request.response = response + } + const cloudformationCustomResourceMiddlewareOnError = (request) => { + const response = { + Status: 'FAILED', + Reason: request.error.message + } + request.response = response + cloudformationCustomResourceMiddlewareAfter(request) + } + return { + after: cloudformationCustomResourceMiddlewareAfter, + onError: cloudformationCustomResourceMiddlewareOnError + } +} + +export default cloudformationCustomResourceMiddleware diff --git a/packages/cloudformation-response/index.test-d.ts b/packages/cloudformation-response/index.test-d.ts new file mode 100644 index 000000000..b9d16f8af --- /dev/null +++ b/packages/cloudformation-response/index.test-d.ts @@ -0,0 +1,8 @@ +import { expectType } from 'tsd' +import middy from '@middy/core' + +import cloudformationResponse from '.' + +// use with default options +const middleware = cloudformationResponse() +expectType(middleware) diff --git a/packages/cloudformation-response/package.json b/packages/cloudformation-response/package.json new file mode 100644 index 000000000..6d4d8a913 --- /dev/null +++ b/packages/cloudformation-response/package.json @@ -0,0 +1,69 @@ +{ + "name": "@middy/cloudformation-response", + "version": "6.0.0", + "description": "CloudFormation Custom Response event response handling 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" + ], + "author": { + "name": "Middy contributors", + "url": "https://github.com/middyjs/middy/graphs/contributors" + }, + "repository": { + "type": "git", + "url": "github:middyjs/middy", + "directory": "packages/cloudformation-response" + }, + "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" +} diff --git a/packages/cloudformation-router/README.md b/packages/cloudformation-router/README.md new file mode 100644 index 000000000..2a10c20e9 --- /dev/null +++ b/packages/cloudformation-router/README.md @@ -0,0 +1,46 @@ +
+

Middy cloudformation-router lambda handler

+ Middy logo +

CloudFormation Custom Response router for the middy framework, the stylish Node.js middleware engine for AWS Lambda

+

+ + npm version + + + npm install size + + + GitHub Actions CI status badge + +
+ + Standard Code Style + + + Known Vulnerabilities + + + CodeQL + + + Core Infrastructure Initiative (CII) Best Practices + +
+ + Chat on Gitter + + + Ask questions on StackOverflow + +

+

You can read the documentation at: https://middy.js.org/docs/routers/cloudformation-router

+
+ +## 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). + + + FOSSA Status + diff --git a/packages/cloudformation-router/__benchmarks__/index.js b/packages/cloudformation-router/__benchmarks__/index.js new file mode 100644 index 000000000..42c8ee848 --- /dev/null +++ b/packages/cloudformation-router/__benchmarks__/index.js @@ -0,0 +1,27 @@ +import { Bench } from 'tinybench' +import middy from '../../core/index.js' +import middleware from '../index.js' + +const bench = new Bench({ time: 1_000 }) + +const context = { + getRemainingTimeInMillis: () => 30000 +} +const setupHandler = () => { + const baseHandler = () => {} + return middy(baseHandler).use(middleware()) +} + +const coldHandler = setupHandler() + +const event = {} +await bench + .add('without cache', async () => { + try { + await coldHandler(event, context) + } catch (e) {} + }) + + .run() + +console.table(bench.table()) diff --git a/packages/cloudformation-router/__tests__/fuzz.js b/packages/cloudformation-router/__tests__/fuzz.js new file mode 100644 index 000000000..fc1f4b07e --- /dev/null +++ b/packages/cloudformation-router/__tests__/fuzz.js @@ -0,0 +1,56 @@ +import { test } from 'node:test' +import fc from 'fast-check' +import middy from '../../core/index.js' +import router from '../index.js' + +const handler = middy(router()) +const context = { + getRemainingTimeInMillis: () => 1000 +} + +test('fuzz `event` w/ `object`', async () => { + fc.assert( + fc.asyncProperty(fc.object(), async (event) => { + try { + await handler(event, context) + } catch (e) { + if (e.cause?.package !== '@middy/cloudformation-router') { + throw e + } + } + }), + { + numRuns: 100_000, + verbose: 2, + + examples: [] + } + ) +}) + +test('fuzz `event` w/ `record`', async () => { + fc.assert( + fc.asyncProperty( + fc.record({ + RequestType: fc.string(), + RequestId: fc.string(), + LogicalResourceId: fc.string(), + StackId: fc.string() + }), + async (event) => { + try { + await handler(event, context) + } catch (e) { + if (e.cause?.package !== '@middy/cloudformation-router') { + throw e + } + } + } + ), + { + numRuns: 100_000, + verbose: 2, + examples: [[{ requestContext: { routeKey: 'valueOf' } }]] + } + ) +}) diff --git a/packages/cloudformation-router/__tests__/index.js b/packages/cloudformation-router/__tests__/index.js new file mode 100644 index 000000000..4af8adfd2 --- /dev/null +++ b/packages/cloudformation-router/__tests__/index.js @@ -0,0 +1,117 @@ +import { test } from 'node:test' +import { ok, equal } from 'node:assert/strict' +import middy from '../../core/index.js' +import cloudformationRouter from '../index.js' + +// const event = {} +const context = { + getRemainingTimeInMillis: () => 1000 +} + +// Types of routes +test('It should route to a static route', async (t) => { + const event = { + RequestType: 'Create' + } + const handler = cloudformationRouter([ + { + requestType: 'Create', + handler: () => true + } + ]) + const response = await handler(event, context) + ok(response) +}) + +test('It should thrown FAILURE when route not found', async (t) => { + const event = { + RequestType: 'Update' + } + const handler = cloudformationRouter([ + { + requestType: 'Create', + handler: () => true + } + ]) + try { + await handler(event, context) + } catch (e) { + equal(e.message, 'Route does not exist') + } +}) + +test('It should thrown FAILURE when route not found, using notFoundResponse', async (t) => { + const event = { + RequestType: 'Update' + } + const handler = cloudformationRouter({ + routes: [ + { + requestType: 'Create', + handler: () => true + } + ], + notFoundResponse: (args) => { + return { + Status: 'SUCCESS' + } + } + }) + const res = await handler(event, context) + + equal(res.Status, 'SUCCESS') +}) + +// with middleware +test('It should run middleware that are part of route handler', async (t) => { + const event = { + RequestType: 'Create' + } + const handler = cloudformationRouter([ + { + requestType: 'Create', + handler: middy(() => false).after((request) => { + request.response = true + }) + } + ]) + const response = await handler(event, context) + ok(response) +}) + +test('It should middleware part of router', async (t) => { + const event = { + RequestType: 'Create' + } + const handler = middy( + cloudformationRouter([ + { + requestType: 'Create', + handler: () => false + } + ]) + ).after((request) => { + request.response = true + }) + const response = await handler(event, context) + ok(response) +}) + +// Errors + +test('It should throw when not a cloudformation event', async (t) => { + const event = { + path: '/' + } + const handler = cloudformationRouter([ + { + requestType: 'Create', + handler: () => true + } + ]) + try { + await handler(event, context) + } catch (e) { + equal(e.message, 'Unknown CloudFormation Custom Response event format') + } +}) diff --git a/packages/cloudformation-router/index.d.ts b/packages/cloudformation-router/index.d.ts new file mode 100644 index 000000000..84bf5af25 --- /dev/null +++ b/packages/cloudformation-router/index.d.ts @@ -0,0 +1,13 @@ +import middy from '@middy/core' +import { CloudFormationCustomResourceHandler } from 'aws-lambda' + +interface Route { + requestType: string + handler: CloudFormationCustomResourceHandler +} + +declare function cloudformationRouterHandler ( + routes: Route[] +): middy.MiddyfiedHandler + +export default cloudformationRouterHandler diff --git a/packages/cloudformation-router/index.js b/packages/cloudformation-router/index.js new file mode 100644 index 000000000..aba4c2220 --- /dev/null +++ b/packages/cloudformation-router/index.js @@ -0,0 +1,57 @@ +const defaults = { + routes: [], + notFoundResponse: ({ requestType }) => { + const err = new Error('Route does not exist', { + casue: { + package: '@middy/cloudformation-router', + data: { requestType } + } + }) + throw err + } +} +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 + } + + const requestTypes = { + Create: true, + Update: true, + Delete: true + } + return (event, context, abort) => { + const { RequestType: requestType } = event + if ( + !requestType || + !Object.hasOwnProperty.call(requestTypes, requestType) + ) { + throw new Error('Unknown CloudFormation Custom Response event format', { + cause: { + package: '@middy/cloudformation-router', + data: { requestType } + } + }) + } + + // Static + if (Object.hasOwnProperty.call(routesStatic, requestType)) { + const handler = routesStatic[requestType] + return handler(event, context, abort) + } + + // Not Found + return notFoundResponse({ requestType }) + } +} + +export default cloudformationCustomResourceRouteHandler diff --git a/packages/cloudformation-router/index.test-d.ts b/packages/cloudformation-router/index.test-d.ts new file mode 100644 index 000000000..4fd634e3b --- /dev/null +++ b/packages/cloudformation-router/index.test-d.ts @@ -0,0 +1,28 @@ +import middy from '@middy/core' +// import { CloudFormationCustomResourceHandler } from 'aws-lambda' +import { expectType } from 'tsd' +import cloudformationRouterHandler from '.' + +const createLambdaHandler: any = async () => { + return { + Status: 'SUCCESS' + } +} + +const deleteLambdaHandler: any = async () => { + return { + Status: 'SUCCESS' + } +} + +const middleware = cloudformationRouterHandler([ + { + requestType: 'Create', + handler: createLambdaHandler + }, + { + requestType: 'Delete', + handler: deleteLambdaHandler + } +]) +expectType(middleware) diff --git a/packages/cloudformation-router/package.json b/packages/cloudformation-router/package.json new file mode 100644 index 000000000..095be145f --- /dev/null +++ b/packages/cloudformation-router/package.json @@ -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" +} diff --git a/packages/http-router/index.js b/packages/http-router/index.js index aa9dd8928..3dee4af4f 100644 --- a/packages/http-router/index.js +++ b/packages/http-router/index.js @@ -24,7 +24,7 @@ const httpRouteHandler = (opts = {}) => { // Prevents `routesType[method][path] = handler` from flagging: This assignment may alter Object.prototype if a malicious '__proto__' string is injected from library input. if (!enumMethods.includes(method)) { throw new Error('Method not allowed', { - cause: { package: '@middy/http-router', data: method } + cause: { package: '@middy/http-router', data: { method } } }) } @@ -48,12 +48,12 @@ const httpRouteHandler = (opts = {}) => { if (!method) { throw new Error('Unknown http event format', { - cause: { package: '@middy/http-router', data: method } + cause: { package: '@middy/http-router', data: { method } } }) } if (!path) { throw new Error('Unknown http event format', { - cause: { package: '@middy/http-router', data: path } + cause: { package: '@middy/http-router', data: { path } } }) } diff --git a/website/docs/events/cloud-formation.md b/website/docs/events/cloud-formation.md index c8b081bd0..2b3744e80 100644 --- a/website/docs/events/cloud-formation.md +++ b/website/docs/events/cloud-formation.md @@ -14,9 +14,52 @@ This page is a work in progress. If you want to help us to make this page better ## Example ```javascript import middy from '@middy/core' +import cloudformationRouterHandler from '@middy/cloudformation-router' +import cloudformationResponseMiddleware from '@middy/cloudformation-response' +import validatorMiddleware from '@middy/validator' -export const handler = middy() - .handler((event, context, {signal}) => { - // ... +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(cloudformationResponseMiddleware()) + .handler(cloudformationRouterHandler(routes)) ``` diff --git a/website/docs/middlewares/cloudformation-response.md b/website/docs/middlewares/cloudformation-response.md new file mode 100644 index 000000000..8ad7aa045 --- /dev/null +++ b/website/docs/middlewares/cloudformation-response.md @@ -0,0 +1,34 @@ +--- +title: cloudformation-response +--- + +Manage CloudFormation Custom Resource responses. + +## Install + +To install this middleware you can use NPM: + +```bash npm2yarn +npm install --save @middy/cloudformation-response +``` + +## Options + +None + +## Sample usage + +### General + +```javascript +import middy from '@middy/core' +import cloudformationResponse from '@middy/cloudformation-response' + +export const handler = middy((event, context) => { + return { + PhysicalResourceId:'...' + } +}) + +handler.use(cloudformationResponse()) +``` diff --git a/website/docs/routers/cloudformation-router.md b/website/docs/routers/cloudformation-router.md new file mode 100644 index 000000000..fc5e5732c --- /dev/null +++ b/website/docs/routers/cloudformation-router.md @@ -0,0 +1,80 @@ +--- +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 FAILED response 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 cloudformationResponseMiddleware from '@middy/cloudformation-response' +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(cloudformationResponseMiddleware()) + .handler(cloudformationRouterHandler(routes)) +```