diff --git a/README.md b/README.md index 297c8ffb..2ea7d8dd 100644 --- a/README.md +++ b/README.md @@ -186,6 +186,32 @@ stepFunctions: Configuring the cors property sets Access-Control-Allow-Origin, Access-Control-Allow-Headers, Access-Control-Allow-Methods,Access-Control-Allow-Credentials headers in the CORS preflight response. +#### Customizing request body mapping templates + +The plugin generates default body mapping templates for `application/json` and `application/x-www-form-urlencoded` content types. If you'd like to add more content types or customize the default ones, you can do so by including them in `serverless.yml`: + +```yml +stepFunctions: + stateMachines: + hello: + events: + - http: + path: posts/create + method: POST + request: + template: + application/json: | + #set( $body = $util.escapeJavaScript($input.json('$')) ) + #set( $name = $util.escapeJavaScript($input.json('$.data.attributes.order_id')) ) + { + "input": "$body", + "name": "$name", + "stateMachineArn":"arn:aws:states:#{AWS::Region}:#{AWS::AccountId}:stateMachine:processOrderFlow-${opt:stage}" + } + name: processOrderFlow-${opt:stage} + definition: +``` + #### Send request to an API You can input an value as json in request body, the value is passed as the input value of your statemachine diff --git a/lib/deploy/events/apiGateway/methods.js b/lib/deploy/events/apiGateway/methods.js index 330176e3..8c761b45 100644 --- a/lib/deploy/events/apiGateway/methods.js +++ b/lib/deploy/events/apiGateway/methods.js @@ -43,7 +43,6 @@ module.exports = { }, getMethodIntegration(stateMachineName, customName, http) { - const stateMachineLogicalId = this.getStateMachineLogicalId(stateMachineName, customName); const apiToStepFunctionsIamRoleLogicalId = this.getApiToStepFunctionsIamRoleLogicalId(); const integration = { IntegrationHttpMethod: 'POST', @@ -67,32 +66,7 @@ module.exports = { ], }, PassthroughBehavior: 'NEVER', - RequestTemplates: { - 'application/json': { - 'Fn::Join': [ - '', [ - "#set( $body = $util.escapeJavaScript($input.json('$')) ) \n\n", - '{"input": "$body","name": "$context.requestId","stateMachineArn":"', - { - Ref: `${stateMachineLogicalId}`, - }, - '"}', - ], - ], - }, - 'application/x-www-form-urlencoded': { - 'Fn::Join': [ - '', [ - "#set( $body = $util.escapeJavaScript($input.json('$')) ) \n\n", - '{"input": "$body","name": "$context.requestId","stateMachineArn":"', - { - Ref: `${stateMachineLogicalId}`, - }, - '"}', - ], - ], - }, - }, + RequestTemplates: this.getIntegrationRequestTemplates(stateMachineName, customName, http), }; const integrationResponse = { @@ -134,6 +108,37 @@ module.exports = { }; }, + getIntegrationRequestTemplates(stateMachineName, customName, http) { + const defaultRequestTemplates = this.getDefaultRequestTemplates(stateMachineName, customName); + return Object.assign( + defaultRequestTemplates, + _.get(http, ['request', 'template']) + ); + }, + + getDefaultRequestTemplates(stateMachineName, customName) { + const stateMachineLogicalId = this.getStateMachineLogicalId(stateMachineName, customName); + return { + 'application/json': this.buildDefaultRequestTemplate(stateMachineLogicalId), + 'application/x-www-form-urlencoded': this.buildDefaultRequestTemplate(stateMachineLogicalId), + }; + }, + + buildDefaultRequestTemplate(stateMachineLogicalId) { + return { + 'Fn::Join': [ + '', [ + "#set( $body = $util.escapeJavaScript($input.json('$')) ) \n\n", + '{"input": "$body","name": "$context.requestId","stateMachineArn":"', + { + Ref: `${stateMachineLogicalId}`, + }, + '"}', + ], + ], + }; + }, + getMethodResponses(http) { const methodResponse = { Properties: { diff --git a/lib/deploy/events/apiGateway/methods.test.js b/lib/deploy/events/apiGateway/methods.test.js index b8a5b805..8170afab 100644 --- a/lib/deploy/events/apiGateway/methods.test.js +++ b/lib/deploy/events/apiGateway/methods.test.js @@ -82,19 +82,6 @@ describe('#methods()', () => { .to.have.property('Integration'); }); - it('should set stateMachinelogical ID to RequestTemplates when customName is not set', () => { - expect(serverlessStepFunctions.getMethodIntegration('stateMachine').Properties - .Integration.RequestTemplates['application/json']['Fn::Join'][1][2].Ref) - .to.be.equal('StateMachineStepFunctionsStateMachine'); - }); - - it('should set custom stateMachinelogical ID to RequestTemplates when customName is set', - () => { - expect(serverlessStepFunctions.getMethodIntegration('stateMachine', 'custom').Properties - .Integration.RequestTemplates['application/json']['Fn::Join'][1][2].Ref) - .to.be.equal('Custom'); - }); - it('should set Access-Control-Allow-Origin header when cors is true', () => { expect(serverlessStepFunctions.getMethodIntegration('stateMachine', 'custom', { @@ -115,6 +102,93 @@ describe('#methods()', () => { }); }); + describe('#getIntegrationRequestTemplates()', () => { + it('should set stateMachinelogical ID in default templates when customName is not set', () => { + const requestTemplates = serverlessStepFunctions + .getIntegrationRequestTemplates('stateMachine'); + expect(requestTemplates['application/json']['Fn::Join'][1][2].Ref) + .to.be.equal('StateMachineStepFunctionsStateMachine'); + }); + + it('should set custom stateMachinelogical ID in default templates when customName is set', + () => { + const requestTemplates = serverlessStepFunctions + .getIntegrationRequestTemplates('stateMachine', 'custom'); + expect(requestTemplates['application/json']['Fn::Join'][1][2].Ref) + .to.be.equal('Custom'); + }); + + it('should return the default template for application/json when one is not given', () => { + const httpWithoutRequestTemplate = { + path: 'foo/bar1', + method: 'post', + request: { + template: { + 'application/x-www-form-urlencoded': 'custom template', + }, + }, + }; + const requestTemplates = serverlessStepFunctions + .getMethodIntegration('stateMachine', undefined, httpWithoutRequestTemplate) + .Properties.Integration.RequestTemplates; + expect(requestTemplates['application/json']['Fn::Join'][1][2].Ref) + .to.be.equal('StateMachineStepFunctionsStateMachine'); + }); + + it('should return a custom template for application/json when one is given', () => { + const httpWithRequestTemplate = { + path: 'foo/bar1', + method: 'post', + request: { + template: { + 'application/json': 'custom template', + }, + }, + }; + const requestTemplates = serverlessStepFunctions + .getMethodIntegration('stateMachine', undefined, httpWithRequestTemplate) + .Properties.Integration.RequestTemplates; + expect(requestTemplates['application/json']) + .to.be.equal('custom template'); + }); + + it('should return the default for application/x-www-form-urlencoded when one is not given', + () => { + const httpWithoutRequestTemplate = { + path: 'foo/bar1', + method: 'post', + request: { + template: { + 'application/json': 'custom template', + }, + }, + }; + const requestTemplates = serverlessStepFunctions + .getMethodIntegration('stateMachine', undefined, httpWithoutRequestTemplate) + .Properties.Integration.RequestTemplates; + expect(requestTemplates['application/x-www-form-urlencoded']['Fn::Join'][1][2].Ref) + .to.be.equal('StateMachineStepFunctionsStateMachine'); + }); + + it('should return a custom template for application/x-www-form-urlencoded when one is given', + () => { + const httpWithRequestTemplate = { + path: 'foo/bar1', + method: 'post', + request: { + template: { + 'application/x-www-form-urlencoded': 'custom template', + }, + }, + }; + const requestTemplates = serverlessStepFunctions + .getMethodIntegration('stateMachine', undefined, httpWithRequestTemplate) + .Properties.Integration.RequestTemplates; + expect(requestTemplates['application/x-www-form-urlencoded']) + .to.be.equal('custom template'); + }); + }); + describe('#getMethodResponses()', () => { it('should return a corresponding methodResponses resource', () => { expect(serverlessStepFunctions.getMethodResponses().Properties)