Skip to content

Commit a9ad9f4

Browse files
authored
Merge branch 'master' into master
2 parents 220579d + fcac549 commit a9ad9f4

File tree

4 files changed

+321
-22
lines changed

4 files changed

+321
-22
lines changed

README.md

Lines changed: 52 additions & 0 deletions
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)
@@ -160,6 +161,57 @@ custom:
160161

161162
See the [SQS section](#sqs-1) under [Customizing request body mapping templates](#customizing-request-body-mapping-templates)
162163

164+
#### Customizing responses
165+
166+
##### Simplified response template customization
167+
168+
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`.
169+
170+
```yml
171+
custom:
172+
apiGatewayServiceProxies:
173+
- sqs:
174+
path: /queue
175+
method: post
176+
queueName: !GetAtt MyQueue.QueueName
177+
cors: true
178+
response:
179+
template:
180+
# `success` is used when the integration response is 200
181+
success: |-
182+
{ "message: "accepted" }
183+
# `clientError` is used when the integration response is 400
184+
clientError: |-
185+
{ "message": "there is an error in your request" }
186+
# `serverError` is used when the integration response is 500
187+
serverError: |-
188+
{ "message": "there was an error handling your request" }
189+
```
190+
191+
##### Full response customization
192+
193+
If you want more control over the integration response, you can
194+
provide an array of objects for the `response` value:
195+
196+
```yml
197+
custom:
198+
apiGatewayServiceProxies:
199+
- sqs:
200+
path: /queue
201+
method: post
202+
queueName: !GetAtt MyQueue.QueueName
203+
cors: true
204+
response:
205+
- statusCode: 200
206+
selectionPattern: '2\\d{2}'
207+
responseParameters: {}
208+
responseTemplates:
209+
application/json: |-
210+
{ "message": "accepted" }
211+
```
212+
213+
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.
214+
163215
### S3
164216

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

lib/apiGateway/schema.js

Lines changed: 20 additions & 1 deletion
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 = {
@@ -248,7 +266,8 @@ const proxiesSchemas = {
248266
sqs: proxy.append({
249267
queueName: stringOrGetAtt('queueName', 'QueueName').required(),
250268
requestParameters,
251-
request
269+
request,
270+
response: sqsResponse
252271
})
253272
}),
254273
dynamodb: Joi.object({

lib/package/sqs/compileMethodsToSqs.js

Lines changed: 64 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -68,27 +68,65 @@ module.exports = {
6868
RequestTemplates: this.getSqsIntegrationRequestTemplates(http)
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)
@@ -115,5 +153,10 @@ module.exports = {
115153

116154
buildDefaultSqsRequestTemplate() {
117155
return 'Action=SendMessage&MessageBody=$util.urlEncode($input.body)'
156+
},
157+
158+
getSQSIntegrationResponseTemplate(http, statusType) {
159+
const template = _.get(http, ['response', 'template', statusType])
160+
return Object.assign({}, template && { 'application/json': template })
118161
}
119162
}

lib/package/sqs/compileMethodsToSqs.test.js

Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -759,4 +759,189 @@ describe('#compileMethodsToSqs()', () => {
759759
.Properties.RequestParameters
760760
).to.be.deep.equal({ 'method.request.header.Custom-Header': true })
761761
})
762+
763+
it('should throw error if simplified response template uses an unsupported key', () => {
764+
serverlessApigatewayServiceProxy.serverless.service.custom = {
765+
apiGatewayServiceProxies: [
766+
{
767+
sqs: {
768+
path: '/sqs',
769+
method: 'post',
770+
queueName: 'queueName',
771+
response: {
772+
template: {
773+
test: 'test template'
774+
}
775+
}
776+
}
777+
}
778+
]
779+
}
780+
781+
expect(() => serverlessApigatewayServiceProxy.validateServiceProxies()).to.throw(
782+
serverless.classes.Error,
783+
'child "sqs" fails because [child "response" fails because [child "template" fails because ["test" is not allowed], "response" must be an array]]'
784+
)
785+
})
786+
787+
it('should throw error if complex response template uses an unsupported key', () => {
788+
serverlessApigatewayServiceProxy.serverless.service.custom = {
789+
apiGatewayServiceProxies: [
790+
{
791+
sqs: {
792+
path: '/sqs',
793+
method: 'post',
794+
queueName: 'queueName',
795+
response: [
796+
{
797+
test: 'test'
798+
}
799+
]
800+
}
801+
}
802+
]
803+
}
804+
805+
expect(() => serverlessApigatewayServiceProxy.validateServiceProxies()).to.throw(
806+
serverless.classes.Error,
807+
'child "sqs" fails because [child "response" fails because ["response" must be an object, "response" at position 0 fails because ["test" is not allowed]]]'
808+
)
809+
})
810+
811+
it('should transform simplified integration responses', () => {
812+
serverlessApigatewayServiceProxy.validated = {
813+
events: [
814+
{
815+
serviceName: 'sqs',
816+
http: {
817+
queueName: 'myQueue',
818+
path: 'sqs',
819+
method: 'post',
820+
auth: {
821+
authorizationType: 'NONE'
822+
},
823+
response: {
824+
template: {
825+
success: 'success template',
826+
clientError: 'client error template',
827+
serverError: 'server error template'
828+
}
829+
}
830+
}
831+
}
832+
]
833+
}
834+
serverlessApigatewayServiceProxy.apiGatewayRestApiLogicalId = 'ApiGatewayRestApi'
835+
serverlessApigatewayServiceProxy.apiGatewayResources = {
836+
sqs: {
837+
name: 'sqs',
838+
resourceLogicalId: 'ApiGatewayResourceSqs'
839+
}
840+
}
841+
842+
serverlessApigatewayServiceProxy.compileMethodsToSqs()
843+
844+
expect(
845+
serverless.service.provider.compiledCloudFormationTemplate.Resources.ApiGatewayMethodsqsPost
846+
.Properties.Integration.IntegrationResponses
847+
).to.be.deep.equal([
848+
{
849+
StatusCode: 200,
850+
SelectionPattern: 200,
851+
ResponseParameters: {},
852+
ResponseTemplates: {
853+
'application/json': 'success template'
854+
}
855+
},
856+
{
857+
StatusCode: 400,
858+
SelectionPattern: 400,
859+
ResponseParameters: {},
860+
ResponseTemplates: {
861+
'application/json': 'client error template'
862+
}
863+
},
864+
{
865+
StatusCode: 500,
866+
SelectionPattern: 500,
867+
ResponseParameters: {},
868+
ResponseTemplates: {
869+
'application/json': 'server error template'
870+
}
871+
}
872+
])
873+
})
874+
875+
it('should transform complex integration responses', () => {
876+
serverlessApigatewayServiceProxy.validated = {
877+
events: [
878+
{
879+
serviceName: 'sqs',
880+
http: {
881+
queueName: 'myQueue',
882+
path: 'sqs',
883+
method: 'post',
884+
auth: {
885+
authorizationType: 'NONE'
886+
},
887+
response: [
888+
{
889+
statusCode: 200,
890+
responseTemplates: {
891+
'text/plain': 'ok'
892+
}
893+
},
894+
{
895+
statusCode: 400,
896+
selectionPattern: '4\\d{2}',
897+
responseParameters: {
898+
a: 'b'
899+
}
900+
},
901+
{
902+
statusCode: 500
903+
}
904+
]
905+
}
906+
}
907+
]
908+
}
909+
serverlessApigatewayServiceProxy.apiGatewayRestApiLogicalId = 'ApiGatewayRestApi'
910+
serverlessApigatewayServiceProxy.apiGatewayResources = {
911+
sqs: {
912+
name: 'sqs',
913+
resourceLogicalId: 'ApiGatewayResourceSqs'
914+
}
915+
}
916+
917+
serverlessApigatewayServiceProxy.compileMethodsToSqs()
918+
919+
expect(
920+
serverless.service.provider.compiledCloudFormationTemplate.Resources.ApiGatewayMethodsqsPost
921+
.Properties.Integration.IntegrationResponses
922+
).to.be.deep.equal([
923+
{
924+
StatusCode: 200,
925+
SelectionPattern: 200,
926+
ResponseParameters: {},
927+
ResponseTemplates: {
928+
'text/plain': 'ok'
929+
}
930+
},
931+
{
932+
StatusCode: 400,
933+
SelectionPattern: '4\\d{2}',
934+
ResponseParameters: {
935+
a: 'b'
936+
},
937+
ResponseTemplates: {}
938+
},
939+
{
940+
StatusCode: 500,
941+
SelectionPattern: 500,
942+
ResponseParameters: {},
943+
ResponseTemplates: {}
944+
}
945+
])
946+
})
762947
})

0 commit comments

Comments
 (0)