Skip to content

Commit 9ea4d43

Browse files
authored
Merge pull request #122 from mfogel/feat/s3-templates
Feature: support for s3 request & response templates
2 parents c116294 + cfe2512 commit 9ea4d43

File tree

5 files changed

+355
-12
lines changed

5 files changed

+355
-12
lines changed

README.md

+53-1
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,12 @@ This Serverless Framework plugin supports the AWS service proxy integration feat
1919
- [Customizing responses](#customizing-responses)
2020
- [S3](#s3)
2121
- [Customizing request parameters](#customizing-request-parameters-1)
22+
- [Customizing request templates](#customizing-request-templates)
2223
- [Customize the Path Override in API Gateway](#customize-the-path-override-in-api-gateway)
2324
- [Can use greedy, for deeper Folders](#can-use-greedy--for-deeper-folders)
24-
- [SNS](#sns)
2525
- [Customizing responses](#customizing-responses-1)
26+
- [SNS](#sns)
27+
- [Customizing responses](#customizing-responses-2)
2628
- [DynamoDB](#dynamodb)
2729
- [EventBridge](#eventbridge)
2830
- [Common API Gateway features](#common-api-gateway-features)
@@ -293,6 +295,29 @@ custom:
293295
'integration.request.header.cache-control': "'public, max-age=31536000, immutable'"
294296
```
295297

298+
#### Customizing request templates
299+
300+
If you'd like use custom [API Gateway request templates](https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-override-request-response-parameters.html), you can do so like so:
301+
302+
```yml
303+
custom:
304+
apiGatewayServiceProxies:
305+
- s3:
306+
path: /s3
307+
method: get
308+
action: GetObject
309+
bucket:
310+
Ref: S3Bucket
311+
request:
312+
template:
313+
application/json: |
314+
#set ($specialStuff = $context.request.header.x-special)
315+
#set ($context.requestOverride.path.object = $specialStuff.replaceAll('_', '-'))
316+
{}
317+
```
318+
319+
Note that if the client does not provide a `Content-Type` header in the request, [ApiGateway defaults to `application/json`](https://docs.aws.amazon.com/apigateway/latest/developerguide/integration-passthrough-behaviors.html).
320+
296321
#### Customize the Path Override in API Gateway
297322

298323
Added the new customization parameter that lets the user set a custom Path Override in API Gateway other than the `{bucket}/{object}`
@@ -351,6 +376,33 @@ custom:
351376

352377
This will translate for example `/s3/a/b/c` to `a/b/c.xml`
353378

379+
#### Customizing responses
380+
381+
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`.
382+
383+
```yml
384+
custom:
385+
apiGatewayServiceProxies:
386+
- s3:
387+
path: /s3
388+
method: post
389+
action: PutObject
390+
bucket:
391+
Ref: S3Bucket
392+
key: static-key.json
393+
response:
394+
template:
395+
# `success` is used when the integration response is 200
396+
success: |-
397+
{ "message: "accepted" }
398+
# `clientError` is used when the integration response is 400
399+
clientError: |-
400+
{ "message": "there is an error in your request" }
401+
# `serverError` is used when the integration response is 500
402+
serverError: |-
403+
{ "message": "there was an error handling your request" }
404+
```
405+
354406
### SNS
355407
356408
Sample syntax for SNS proxy in `serverless.yml`.

lib/apiGateway/schema.js

+17-8
Original file line numberDiff line numberDiff line change
@@ -256,15 +256,24 @@ const proxiesSchemas = {
256256
.valid('GetObject', 'PutObject', 'DeleteObject')
257257
.required(),
258258
bucket: stringOrRef.required(),
259-
// don't accept a key when requestParameters has a 'integration.request.path.object' property
260-
key: Joi.when('requestParameters', {
261-
is: requestParameters
262-
.keys({ 'integration.request.path.object': Joi.string().required() })
263-
.required(),
264-
then: Joi.forbidden(),
265-
otherwise: key.required()
259+
// key is
260+
// - optional when using a request mapping template
261+
// - forbidden if requestParameter has a 'integration.request.path.object' property
262+
// - otherwise required
263+
key: Joi.when('request', {
264+
is: request.required(),
265+
then: key,
266+
otherwise: Joi.when('requestParameters', {
267+
is: requestParameters
268+
.keys({ 'integration.request.path.object': Joi.string().required() })
269+
.required(),
270+
then: Joi.forbidden(),
271+
otherwise: key.required()
272+
})
266273
}),
267-
requestParameters
274+
requestParameters,
275+
request,
276+
response: extendedResponse
268277
})
269278
}),
270279
sns: Joi.object({

lib/apiGateway/validate.test.js

+14
Original file line numberDiff line numberDiff line change
@@ -1331,6 +1331,20 @@ describe('#validateServiceProxies()', () => {
13311331
'integration.request.header.cache-control': "'public, max-age=31536000, immutable'"
13321332
})
13331333
})
1334+
1335+
it('should not throw if request template is defined and key is not defined', () => {
1336+
shouldSucceed('request', { template: {} }, 'key')
1337+
})
1338+
1339+
it('should not throw if request template is defined and key is defined', () => {
1340+
shouldSucceed('request', { template: {} })
1341+
})
1342+
1343+
it('should not throw if response template is defined', () => {
1344+
shouldSucceed('response', { template: {} })
1345+
shouldSucceed('response', { template: { clientError: 'not empty' } })
1346+
shouldSucceed('response', { template: { success: 'a', clientError: 'b', serverError: 'c' } })
1347+
})
13341348
})
13351349

13361350
describe('sns', () => {

lib/package/s3/compileMethodsToS3.js

+15-3
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,11 @@ module.exports = {
119119
}
120120
},
121121

122+
getS3IntegrationResponseTemplate(http, statusType) {
123+
const template = _.get(http, ['response', 'template', statusType])
124+
return Object.assign({}, template && { 'application/json': template })
125+
},
126+
122127
getS3MethodIntegration(http) {
123128
const bucket = http.bucket
124129
const httpMethod = this.getIntegrationHttpMethod(http)
@@ -159,25 +164,32 @@ module.exports = {
159164
RequestParameters: _.merge(requestParams, http.requestParameters)
160165
}
161166

167+
const customRequestTemplates = _.get(http, ['request', 'template'])
168+
169+
if (!_.isEmpty(customRequestTemplates)) {
170+
integration.PassthroughBehavior = 'NEVER'
171+
integration.RequestTemplates = customRequestTemplates
172+
}
173+
162174
const integrationResponse = {
163175
IntegrationResponses: [
164176
{
165177
StatusCode: 400,
166178
SelectionPattern: '4\\d{2}',
167179
ResponseParameters: {},
168-
ResponseTemplates: {}
180+
ResponseTemplates: this.getS3IntegrationResponseTemplate(http, 'clientError')
169181
},
170182
{
171183
StatusCode: 500,
172184
SelectionPattern: '5\\d{2}',
173185
ResponseParameters: {},
174-
ResponseTemplates: {}
186+
ResponseTemplates: this.getS3IntegrationResponseTemplate(http, 'serverError')
175187
},
176188
{
177189
StatusCode: 200,
178190
SelectionPattern: '2\\d{2}',
179191
ResponseParameters: responseParams,
180-
ResponseTemplates: {}
192+
ResponseTemplates: this.getS3IntegrationResponseTemplate(http, 'success')
181193
}
182194
]
183195
}

0 commit comments

Comments
 (0)