Skip to content

Commit 5be1ae1

Browse files
authored
Merge pull request #148 from LeoDOD/feature/add-context-and-passthrough
Feature/add context and passthrough
2 parents c457527 + 8cd16ef commit 5be1ae1

File tree

5 files changed

+337
-0
lines changed

5 files changed

+337
-0
lines changed

README.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -427,6 +427,24 @@ custom:
427427

428428
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.
429429

430+
##### Content Handling and Pass Through Behaviour customization
431+
432+
If you want to work with binary fata, you can not specify `contentHandling` and `PassThrough` inside the `request` object.
433+
434+
```yml
435+
custom:
436+
apiGatewayServiceProxies:
437+
- sns:
438+
path: /sns
439+
method: post
440+
topicName: { 'Fn::GetAtt': ['SNSTopic', 'TopicName'] }
441+
request:
442+
contentHandling: CONVERT_TO_TEXT
443+
passThrough: WHEN_NO_TEMPLATES
444+
```
445+
446+
The allowed values correspond with the API Gateway Method integration for [ContentHandling](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apitgateway-method-integration.html#cfn-apigateway-method-integration-contenthandling) and [PassthroughBehavior](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apitgateway-method-integration.html#cfn-apigateway-method-integration-passthroughbehavior)
447+
430448
### DynamoDB
431449

432450
Sample syntax for DynamoDB proxy in `serverless.yml`. Currently, the supported [DynamoDB Operations](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Operations.html) are `PutItem`, `GetItem` and `DeleteItem`.

lib/apiGateway/schema.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,8 @@ const eventBridgeDetail = Joi.alternatives().try([
207207
])
208208

209209
const request = Joi.object({
210+
contentHandling: Joi.string().valid('CONVERT_TO_BINARY', 'CONVERT_TO_TEXT'),
211+
passThrough: Joi.string().valid('WHEN_NO_MATCH', 'NEVER', 'WHEN_NO_TEMPLATES'),
210212
template: Joi.object().required()
211213
})
212214

lib/apiGateway/validate.test.js

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -814,6 +814,54 @@ describe('#validateServiceProxies()', () => {
814814
)
815815
})
816816
})
817+
818+
proxiesToTest.forEach(({ proxy, props }) => {
819+
it(`should not throw if a request contextHandling is set for ${proxy}`, () => {
820+
serverlessApigatewayServiceProxy.serverless.service.custom = {
821+
apiGatewayServiceProxies: [
822+
{
823+
[proxy]: Object.assign(
824+
{
825+
path: `/${proxy}`,
826+
method: 'post',
827+
request: { contextHandling: 'CONVERT_TO_TEXT' }
828+
},
829+
props
830+
)
831+
}
832+
]
833+
}
834+
835+
expect(() => serverlessApigatewayServiceProxy.validateServiceProxies()).to.not.throw(
836+
serverless.classes.Error,
837+
`child "${proxy}" fails because ["requestParameters" is not allowed]`
838+
)
839+
})
840+
})
841+
842+
proxiesToTest.forEach(({ proxy, props }) => {
843+
it(`should not throw if a request passthrough is set for ${proxy}`, () => {
844+
serverlessApigatewayServiceProxy.serverless.service.custom = {
845+
apiGatewayServiceProxies: [
846+
{
847+
[proxy]: Object.assign(
848+
{
849+
path: `/${proxy}`,
850+
method: 'post',
851+
request: { passthrough: 'WHEN_NO_TEMPLATES' }
852+
},
853+
props
854+
)
855+
}
856+
]
857+
}
858+
859+
expect(() => serverlessApigatewayServiceProxy.validateServiceProxies()).to.not.throw(
860+
serverless.classes.Error,
861+
`child "${proxy}" fails because ["requestParameters" is not allowed]`
862+
)
863+
})
864+
})
817865
})
818866

819867
describe('kinesis', () => {

lib/package/sns/compileMethodsToSns.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,15 @@ module.exports = {
6262
RequestTemplates: this.getSnsIntegrationRequestTemplates(http)
6363
}
6464

65+
if ('request' in http) {
66+
if ('passThrough' in http.request) {
67+
integration.PassthroughBehavior = http.request.passThrough
68+
}
69+
if ('contentHandling' in http.request) {
70+
integration.ContentHandling = http.request.contentHandling
71+
}
72+
}
73+
6574
let integrationResponse
6675

6776
if (_.get(http.response, 'template.success')) {

lib/package/sns/compileMethodsToSns.test.js

Lines changed: 260 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1000,4 +1000,264 @@ describe('#compileMethodsToSns()', () => {
10001000
}
10011001
])
10021002
})
1003+
1004+
it('should create corresponding resources when sns proxies are given with passthrou', () => {
1005+
serverlessApigatewayServiceProxy.validated = {
1006+
events: [
1007+
{
1008+
serviceName: 'sns',
1009+
http: {
1010+
topicName: 'myTopic',
1011+
path: 'sns',
1012+
method: 'post',
1013+
auth: {
1014+
authorizationType: 'NONE'
1015+
},
1016+
request: {
1017+
passThrough: 'WHEN_NO_TEMPLATES'
1018+
}
1019+
}
1020+
}
1021+
]
1022+
}
1023+
serverlessApigatewayServiceProxy.apiGatewayRestApiLogicalId = 'ApiGatewayRestApi'
1024+
serverlessApigatewayServiceProxy.apiGatewayResources = {
1025+
sns: {
1026+
name: 'Sns',
1027+
resourceLogicalId: 'ApiGatewayResourceSns'
1028+
}
1029+
}
1030+
1031+
serverlessApigatewayServiceProxy.compileMethodsToSns()
1032+
1033+
it('should create corresponding resources when sns proxies are given a passThought option', () => {
1034+
serverlessApigatewayServiceProxy.validated = {
1035+
events: [
1036+
{
1037+
serviceName: 'sns',
1038+
http: {
1039+
topicName: 'myTopic',
1040+
path: 'sns',
1041+
method: 'post',
1042+
auth: {
1043+
authorizationType: 'NONE'
1044+
},
1045+
request: {
1046+
passThrough: 'WHEN_NO_TEMPLATES'
1047+
}
1048+
}
1049+
}
1050+
]
1051+
}
1052+
serverlessApigatewayServiceProxy.apiGatewayRestApiLogicalId = 'ApiGatewayRestApi'
1053+
serverlessApigatewayServiceProxy.apiGatewayResources = {
1054+
sns: {
1055+
name: 'Sns',
1056+
resourceLogicalId: 'ApiGatewayResourceSns'
1057+
}
1058+
}
1059+
1060+
serverlessApigatewayServiceProxy.compileMethodsToSns()
1061+
expect(serverless.service.provider.compiledCloudFormationTemplate.Resources).to.deep.equal({
1062+
ApiGatewayMethodSnsPost: {
1063+
Type: 'AWS::ApiGateway::Method',
1064+
Properties: {
1065+
HttpMethod: 'POST',
1066+
RequestParameters: {},
1067+
AuthorizationType: 'NONE',
1068+
AuthorizationScopes: undefined,
1069+
AuthorizerId: undefined,
1070+
ApiKeyRequired: false,
1071+
ResourceId: { Ref: 'ApiGatewayResourceSns' },
1072+
RestApiId: { Ref: 'ApiGatewayRestApi' },
1073+
Integration: {
1074+
IntegrationHttpMethod: 'POST',
1075+
Type: 'AWS',
1076+
Credentials: { 'Fn::GetAtt': ['ApigatewayToSnsRole', 'Arn'] },
1077+
Uri: {
1078+
'Fn::Sub': 'arn:${AWS::Partition}:apigateway:${AWS::Region}:sns:path//'
1079+
},
1080+
PassthroughBehavior: 'WHEN_NO_TEMPLATES',
1081+
RequestParameters: {
1082+
'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'"
1083+
},
1084+
RequestTemplates: {
1085+
'application/json': {
1086+
'Fn::Join': [
1087+
'',
1088+
[
1089+
"Action=Publish&Message=$util.urlEncode($input.body)&TopicArn=$util.urlEncode('",
1090+
{
1091+
'Fn::Sub': [
1092+
'arn:${AWS::Partition}:sns:${AWS::Region}:${AWS::AccountId}:${topicName}',
1093+
{ topicName: 'myTopic' }
1094+
]
1095+
},
1096+
"')"
1097+
]
1098+
]
1099+
},
1100+
'application/x-www-form-urlencoded': {
1101+
'Fn::Join': [
1102+
'',
1103+
[
1104+
"Action=Publish&Message=$util.urlEncode($input.body)&TopicArn=$util.urlEncode('",
1105+
{
1106+
'Fn::Sub': [
1107+
'arn:${AWS::Partition}:sns:${AWS::Region}:${AWS::AccountId}:${topicName}',
1108+
{ topicName: 'myTopic' }
1109+
]
1110+
},
1111+
"')"
1112+
]
1113+
]
1114+
}
1115+
},
1116+
IntegrationResponses: [
1117+
{
1118+
StatusCode: 200,
1119+
SelectionPattern: 200,
1120+
ResponseParameters: {},
1121+
ResponseTemplates: {}
1122+
},
1123+
{
1124+
StatusCode: 400,
1125+
SelectionPattern: 400,
1126+
ResponseParameters: {},
1127+
ResponseTemplates: {}
1128+
},
1129+
{
1130+
StatusCode: 500,
1131+
SelectionPattern: 500,
1132+
ResponseParameters: {},
1133+
ResponseTemplates: {}
1134+
}
1135+
]
1136+
},
1137+
MethodResponses: [
1138+
{ ResponseParameters: {}, ResponseModels: {}, StatusCode: 200 },
1139+
{ ResponseParameters: {}, ResponseModels: {}, StatusCode: 400 },
1140+
{ ResponseParameters: {}, ResponseModels: {}, StatusCode: 500 }
1141+
]
1142+
}
1143+
}
1144+
})
1145+
})
1146+
})
1147+
1148+
it('should create corresponding resources when sns proxies are given a ContentHandling option', () => {
1149+
serverlessApigatewayServiceProxy.validated = {
1150+
events: [
1151+
{
1152+
serviceName: 'sns',
1153+
http: {
1154+
topicName: 'myTopic',
1155+
path: 'sns',
1156+
method: 'post',
1157+
auth: {
1158+
authorizationType: 'NONE'
1159+
},
1160+
request: {
1161+
passThrough: 'WHEN_NO_TEMPLATES',
1162+
contentHandling: 'CONVERT_TO_TEXT'
1163+
}
1164+
}
1165+
}
1166+
]
1167+
}
1168+
serverlessApigatewayServiceProxy.apiGatewayRestApiLogicalId = 'ApiGatewayRestApi'
1169+
serverlessApigatewayServiceProxy.apiGatewayResources = {
1170+
sns: {
1171+
name: 'Sns',
1172+
resourceLogicalId: 'ApiGatewayResourceSns'
1173+
}
1174+
}
1175+
1176+
serverlessApigatewayServiceProxy.compileMethodsToSns()
1177+
expect(serverless.service.provider.compiledCloudFormationTemplate.Resources).to.deep.equal({
1178+
ApiGatewayMethodSnsPost: {
1179+
Type: 'AWS::ApiGateway::Method',
1180+
Properties: {
1181+
HttpMethod: 'POST',
1182+
RequestParameters: {},
1183+
AuthorizationType: 'NONE',
1184+
AuthorizationScopes: undefined,
1185+
AuthorizerId: undefined,
1186+
ApiKeyRequired: false,
1187+
ResourceId: { Ref: 'ApiGatewayResourceSns' },
1188+
RestApiId: { Ref: 'ApiGatewayRestApi' },
1189+
Integration: {
1190+
IntegrationHttpMethod: 'POST',
1191+
Type: 'AWS',
1192+
Credentials: { 'Fn::GetAtt': ['ApigatewayToSnsRole', 'Arn'] },
1193+
Uri: {
1194+
'Fn::Sub': 'arn:${AWS::Partition}:apigateway:${AWS::Region}:sns:path//'
1195+
},
1196+
PassthroughBehavior: 'WHEN_NO_TEMPLATES',
1197+
ContentHandling: 'CONVERT_TO_TEXT',
1198+
RequestParameters: {
1199+
'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'"
1200+
},
1201+
RequestTemplates: {
1202+
'application/json': {
1203+
'Fn::Join': [
1204+
'',
1205+
[
1206+
"Action=Publish&Message=$util.urlEncode($input.body)&TopicArn=$util.urlEncode('",
1207+
{
1208+
'Fn::Sub': [
1209+
'arn:${AWS::Partition}:sns:${AWS::Region}:${AWS::AccountId}:${topicName}',
1210+
{ topicName: 'myTopic' }
1211+
]
1212+
},
1213+
"')"
1214+
]
1215+
]
1216+
},
1217+
'application/x-www-form-urlencoded': {
1218+
'Fn::Join': [
1219+
'',
1220+
[
1221+
"Action=Publish&Message=$util.urlEncode($input.body)&TopicArn=$util.urlEncode('",
1222+
{
1223+
'Fn::Sub': [
1224+
'arn:${AWS::Partition}:sns:${AWS::Region}:${AWS::AccountId}:${topicName}',
1225+
{ topicName: 'myTopic' }
1226+
]
1227+
},
1228+
"')"
1229+
]
1230+
]
1231+
}
1232+
},
1233+
IntegrationResponses: [
1234+
{
1235+
StatusCode: 200,
1236+
SelectionPattern: 200,
1237+
ResponseParameters: {},
1238+
ResponseTemplates: {}
1239+
},
1240+
{
1241+
StatusCode: 400,
1242+
SelectionPattern: 400,
1243+
ResponseParameters: {},
1244+
ResponseTemplates: {}
1245+
},
1246+
{
1247+
StatusCode: 500,
1248+
SelectionPattern: 500,
1249+
ResponseParameters: {},
1250+
ResponseTemplates: {}
1251+
}
1252+
]
1253+
},
1254+
MethodResponses: [
1255+
{ ResponseParameters: {}, ResponseModels: {}, StatusCode: 200 },
1256+
{ ResponseParameters: {}, ResponseModels: {}, StatusCode: 400 },
1257+
{ ResponseParameters: {}, ResponseModels: {}, StatusCode: 500 }
1258+
]
1259+
}
1260+
}
1261+
})
1262+
})
10031263
})

0 commit comments

Comments
 (0)