Skip to content

Commit fcac549

Browse files
authored
Merge pull request #89 from glb/80-sqs-response-templates
feat(sqs): add response template support for sqs
2 parents 9121de8 + 9e87c94 commit fcac549

File tree

4 files changed

+321
-22
lines changed

4 files changed

+321
-22
lines changed

README.md

+52
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ This Serverless Framework plugin supports the AWS service proxy integration feat
1313
- [Kinesis](#kinesis)
1414
- [SQS](#sqs)
1515
- [Customizing request parameters](#customizing-request-parameters)
16+
- [Customizing responses](#customizing-responses)
1617
- [S3](#s3)
1718
- [Customizing request parameters](#customizing-request-parameters-1)
1819
- [Customize the Path Override in API Gateway](#customize-the-path-override-in-api-gateway)
@@ -155,6 +156,57 @@ custom:
155156
'integration.request.querystring.MessageAttribute.2.Value.DataType': "'String'"
156157
```
157158

159+
#### Customizing responses
160+
161+
##### Simplified response template customization
162+
163+
You can get a simple customization of the responses by providing a template for the possible responses. The template is assumed to be `application/json`.
164+
165+
```yml
166+
custom:
167+
apiGatewayServiceProxies:
168+
- sqs:
169+
path: /queue
170+
method: post
171+
queueName: !GetAtt MyQueue.QueueName
172+
cors: true
173+
response:
174+
template:
175+
# `success` is used when the integration response is 200
176+
success: |-
177+
{ "message: "accepted" }
178+
# `clientError` is used when the integration response is 400
179+
clientError: |-
180+
{ "message": "there is an error in your request" }
181+
# `serverError` is used when the integration response is 500
182+
serverError: |-
183+
{ "message": "there was an error handling your request" }
184+
```
185+
186+
##### Full response customization
187+
188+
If you want more control over the integration response, you can
189+
provide an array of objects for the `response` value:
190+
191+
```yml
192+
custom:
193+
apiGatewayServiceProxies:
194+
- sqs:
195+
path: /queue
196+
method: post
197+
queueName: !GetAtt MyQueue.QueueName
198+
cors: true
199+
response:
200+
- statusCode: 200
201+
selectionPattern: '2\\d{2}'
202+
responseParameters: {}
203+
responseTemplates:
204+
application/json: |-
205+
{ "message": "accepted" }
206+
```
207+
208+
The object keys correspond to the API Gateway [integration response](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apitgateway-method-integration-integrationresponse.html#cfn-apigateway-method-integration-integrationresponse-responseparameters) object.
209+
158210
### S3
159211

160212
Sample syntax for S3 proxy in `serverless.yml`.

lib/apiGateway/schema.js

+20-1
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,24 @@ const response = Joi.object({
218218
})
219219
})
220220

221+
const sqsResponse = Joi.alternatives().try([
222+
Joi.object({
223+
template: Joi.object().keys({
224+
success: Joi.string(),
225+
clientError: Joi.string(),
226+
serverError: Joi.string()
227+
})
228+
}),
229+
Joi.array().items(
230+
Joi.object().keys({
231+
statusCode: Joi.alternatives().try([Joi.number(), Joi.string()]),
232+
selectionPattern: Joi.alternatives().try([Joi.number(), Joi.string()]),
233+
responseParameters: Joi.object(),
234+
responseTemplates: Joi.object()
235+
})
236+
)
237+
])
238+
221239
const allowedProxies = ['kinesis', 'sqs', 's3', 'sns', 'dynamodb', 'eventbridge']
222240

223241
const proxiesSchemas = {
@@ -247,7 +265,8 @@ const proxiesSchemas = {
247265
sqs: Joi.object({
248266
sqs: proxy.append({
249267
queueName: stringOrGetAtt('queueName', 'QueueName').required(),
250-
requestParameters
268+
requestParameters,
269+
response: sqsResponse
251270
})
252271
}),
253272
dynamodb: Joi.object({

lib/package/sqs/compileMethodsToSqs.js

+64-21
Original file line numberDiff line numberDiff line change
@@ -68,27 +68,65 @@ module.exports = {
6868
RequestTemplates: { 'application/json': '{statusCode:200}' }
6969
}
7070

71-
const integrationResponse = {
72-
IntegrationResponses: [
73-
{
74-
StatusCode: 200,
75-
SelectionPattern: 200,
76-
ResponseParameters: {},
77-
ResponseTemplates: {}
78-
},
79-
{
80-
StatusCode: 400,
81-
SelectionPattern: 400,
82-
ResponseParameters: {},
83-
ResponseTemplates: {}
84-
},
85-
{
86-
StatusCode: 500,
87-
SelectionPattern: 500,
88-
ResponseParameters: {},
89-
ResponseTemplates: {}
90-
}
91-
]
71+
let integrationResponse
72+
73+
if (_.get(http.response, 'template.success')) {
74+
// support a simplified model
75+
integrationResponse = {
76+
IntegrationResponses: [
77+
{
78+
StatusCode: 200,
79+
SelectionPattern: 200,
80+
ResponseParameters: {},
81+
ResponseTemplates: this.getSQSIntegrationResponseTemplate(http, 'success')
82+
},
83+
{
84+
StatusCode: 400,
85+
SelectionPattern: 400,
86+
ResponseParameters: {},
87+
ResponseTemplates: this.getSQSIntegrationResponseTemplate(http, 'clientError')
88+
},
89+
{
90+
StatusCode: 500,
91+
SelectionPattern: 500,
92+
ResponseParameters: {},
93+
ResponseTemplates: this.getSQSIntegrationResponseTemplate(http, 'serverError')
94+
}
95+
]
96+
}
97+
} else if (_.isArray(http.response)) {
98+
// support full usage
99+
integrationResponse = {
100+
IntegrationResponses: http.response.map((i) => ({
101+
StatusCode: i.statusCode,
102+
SelectionPattern: i.selectionPattern || i.statusCode,
103+
ResponseParameters: i.responseParameters || {},
104+
ResponseTemplates: i.responseTemplates || {}
105+
}))
106+
}
107+
} else {
108+
integrationResponse = {
109+
IntegrationResponses: [
110+
{
111+
StatusCode: 200,
112+
SelectionPattern: 200,
113+
ResponseParameters: {},
114+
ResponseTemplates: {}
115+
},
116+
{
117+
StatusCode: 400,
118+
SelectionPattern: 400,
119+
ResponseParameters: {},
120+
ResponseTemplates: {}
121+
},
122+
{
123+
StatusCode: 500,
124+
SelectionPattern: 500,
125+
ResponseParameters: {},
126+
ResponseTemplates: {}
127+
}
128+
]
129+
}
92130
}
93131

94132
this.addCors(http, integrationResponse)
@@ -100,5 +138,10 @@ module.exports = {
100138
Integration: integration
101139
}
102140
}
141+
},
142+
143+
getSQSIntegrationResponseTemplate(http, statusType) {
144+
const template = _.get(http, ['response', 'template', statusType])
145+
return Object.assign({}, template && { 'application/json': template })
103146
}
104147
}

lib/package/sqs/compileMethodsToSqs.test.js

+185
Original file line numberDiff line numberDiff line change
@@ -656,4 +656,189 @@ describe('#compileMethodsToSqs()', () => {
656656
.Properties.RequestParameters
657657
).to.be.deep.equal({ 'method.request.header.Custom-Header': true })
658658
})
659+
660+
it('should throw error if simplified response template uses an unsupported key', () => {
661+
serverlessApigatewayServiceProxy.serverless.service.custom = {
662+
apiGatewayServiceProxies: [
663+
{
664+
sqs: {
665+
path: '/sqs',
666+
method: 'post',
667+
queueName: 'queueName',
668+
response: {
669+
template: {
670+
test: 'test template'
671+
}
672+
}
673+
}
674+
}
675+
]
676+
}
677+
678+
expect(() => serverlessApigatewayServiceProxy.validateServiceProxies()).to.throw(
679+
serverless.classes.Error,
680+
'child "sqs" fails because [child "response" fails because [child "template" fails because ["test" is not allowed], "response" must be an array]]'
681+
)
682+
})
683+
684+
it('should throw error if complex response template uses an unsupported key', () => {
685+
serverlessApigatewayServiceProxy.serverless.service.custom = {
686+
apiGatewayServiceProxies: [
687+
{
688+
sqs: {
689+
path: '/sqs',
690+
method: 'post',
691+
queueName: 'queueName',
692+
response: [
693+
{
694+
test: 'test'
695+
}
696+
]
697+
}
698+
}
699+
]
700+
}
701+
702+
expect(() => serverlessApigatewayServiceProxy.validateServiceProxies()).to.throw(
703+
serverless.classes.Error,
704+
'child "sqs" fails because [child "response" fails because ["response" must be an object, "response" at position 0 fails because ["test" is not allowed]]]'
705+
)
706+
})
707+
708+
it('should transform simplified integration responses', () => {
709+
serverlessApigatewayServiceProxy.validated = {
710+
events: [
711+
{
712+
serviceName: 'sqs',
713+
http: {
714+
queueName: 'myQueue',
715+
path: 'sqs',
716+
method: 'post',
717+
auth: {
718+
authorizationType: 'NONE'
719+
},
720+
response: {
721+
template: {
722+
success: 'success template',
723+
clientError: 'client error template',
724+
serverError: 'server error template'
725+
}
726+
}
727+
}
728+
}
729+
]
730+
}
731+
serverlessApigatewayServiceProxy.apiGatewayRestApiLogicalId = 'ApiGatewayRestApi'
732+
serverlessApigatewayServiceProxy.apiGatewayResources = {
733+
sqs: {
734+
name: 'sqs',
735+
resourceLogicalId: 'ApiGatewayResourceSqs'
736+
}
737+
}
738+
739+
serverlessApigatewayServiceProxy.compileMethodsToSqs()
740+
741+
expect(
742+
serverless.service.provider.compiledCloudFormationTemplate.Resources.ApiGatewayMethodsqsPost
743+
.Properties.Integration.IntegrationResponses
744+
).to.be.deep.equal([
745+
{
746+
StatusCode: 200,
747+
SelectionPattern: 200,
748+
ResponseParameters: {},
749+
ResponseTemplates: {
750+
'application/json': 'success template'
751+
}
752+
},
753+
{
754+
StatusCode: 400,
755+
SelectionPattern: 400,
756+
ResponseParameters: {},
757+
ResponseTemplates: {
758+
'application/json': 'client error template'
759+
}
760+
},
761+
{
762+
StatusCode: 500,
763+
SelectionPattern: 500,
764+
ResponseParameters: {},
765+
ResponseTemplates: {
766+
'application/json': 'server error template'
767+
}
768+
}
769+
])
770+
})
771+
772+
it('should transform complex integration responses', () => {
773+
serverlessApigatewayServiceProxy.validated = {
774+
events: [
775+
{
776+
serviceName: 'sqs',
777+
http: {
778+
queueName: 'myQueue',
779+
path: 'sqs',
780+
method: 'post',
781+
auth: {
782+
authorizationType: 'NONE'
783+
},
784+
response: [
785+
{
786+
statusCode: 200,
787+
responseTemplates: {
788+
'text/plain': 'ok'
789+
}
790+
},
791+
{
792+
statusCode: 400,
793+
selectionPattern: '4\\d{2}',
794+
responseParameters: {
795+
a: 'b'
796+
}
797+
},
798+
{
799+
statusCode: 500
800+
}
801+
]
802+
}
803+
}
804+
]
805+
}
806+
serverlessApigatewayServiceProxy.apiGatewayRestApiLogicalId = 'ApiGatewayRestApi'
807+
serverlessApigatewayServiceProxy.apiGatewayResources = {
808+
sqs: {
809+
name: 'sqs',
810+
resourceLogicalId: 'ApiGatewayResourceSqs'
811+
}
812+
}
813+
814+
serverlessApigatewayServiceProxy.compileMethodsToSqs()
815+
816+
expect(
817+
serverless.service.provider.compiledCloudFormationTemplate.Resources.ApiGatewayMethodsqsPost
818+
.Properties.Integration.IntegrationResponses
819+
).to.be.deep.equal([
820+
{
821+
StatusCode: 200,
822+
SelectionPattern: 200,
823+
ResponseParameters: {},
824+
ResponseTemplates: {
825+
'text/plain': 'ok'
826+
}
827+
},
828+
{
829+
StatusCode: 400,
830+
SelectionPattern: '4\\d{2}',
831+
ResponseParameters: {
832+
a: 'b'
833+
},
834+
ResponseTemplates: {}
835+
},
836+
{
837+
StatusCode: 500,
838+
SelectionPattern: 500,
839+
ResponseParameters: {},
840+
ResponseTemplates: {}
841+
}
842+
])
843+
})
659844
})

0 commit comments

Comments
 (0)