Skip to content

Commit de42e62

Browse files
beck3905praneetap
authored andcommitted
feat: add RequestParameters Support (#953)
1 parent f280aae commit de42e62

File tree

13 files changed

+1388
-1
lines changed

13 files changed

+1388
-1
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
exports.handler = function(event, context, callback) {
2+
callback(null, {
3+
"statusCode": 200,
4+
"body": "hello world"
5+
});
6+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
AWSTemplateFormatVersion: '2010-09-09'
2+
Transform: AWS::Serverless-2016-10-31
3+
Description: Simple API Endpoint configured using Swagger specified inline and backed by a Lambda function
4+
Globals:
5+
Api:
6+
CacheClusterEnabled: true
7+
CacheClusterSize: '0.5'
8+
9+
Resources:
10+
11+
MyLambdaFunction:
12+
Type: AWS::Serverless::Function
13+
Properties:
14+
Handler: index.handler
15+
Runtime: nodejs8.10
16+
CodeUri: src/
17+
Events:
18+
GetApi:
19+
Type: Api
20+
Properties:
21+
Path: /post
22+
Method: POST
23+
RequestParameters:
24+
- method.request.header.Authorization:
25+
Required: true
26+
Caching: true
27+
- method.request.querystring.type
28+
29+
Outputs:
30+
31+
ApiURL:
32+
Description: "API endpoint URL for Prod environment"
33+
Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/post"

samtranslator/model/eventsources/push.py

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717

1818
CONDITION = 'Condition'
1919

20+
REQUEST_PARAMETER_PROPERTIES = ["Required", "Caching"]
21+
2022

2123
class PushEventSource(ResourceMacro):
2224
"""Base class for push event sources for SAM Functions.
@@ -397,7 +399,8 @@ class Api(PushEventSource):
397399
# Api Event sources must "always" be paired with a Serverless::Api
398400
'RestApiId': PropertyType(True, is_str()),
399401
'Auth': PropertyType(False, is_type(dict)),
400-
'RequestModel': PropertyType(False, is_type(dict))
402+
'RequestModel': PropertyType(False, is_type(dict)),
403+
'RequestParameters': PropertyType(False, is_type(list))
401404
}
402405

403406
def resources_to_link(self, resources):
@@ -607,6 +610,61 @@ def _add_swagger_integration(self, api, function):
607610
editor.add_request_model_to_method(path=self.Path, method_name=self.Method,
608611
request_model=self.RequestModel)
609612

613+
if self.RequestParameters:
614+
615+
default_value = {
616+
'Required': False,
617+
'Caching': False
618+
}
619+
620+
parameters = []
621+
for parameter in self.RequestParameters:
622+
623+
if isinstance(parameter, dict):
624+
625+
parameter_name, parameter_value = next(iter(parameter.items()))
626+
627+
if not re.match('method\.request\.(querystring|path|header)\.', parameter_name):
628+
raise InvalidEventException(
629+
self.relative_id,
630+
"Invalid value for 'RequestParameters' property. Keys must be in the format "
631+
"'method.request.[querystring|path|header].{value}', "
632+
"e.g 'method.request.header.Authorization'.")
633+
634+
if not isinstance(parameter_value, dict) or not all(key in REQUEST_PARAMETER_PROPERTIES
635+
for key in parameter_value.keys()):
636+
raise InvalidEventException(
637+
self.relative_id,
638+
"Invalid value for 'RequestParameters' property. Values must be an object, "
639+
"e.g { Required: true, Caching: false }")
640+
641+
settings = default_value.copy()
642+
settings.update(parameter_value)
643+
settings.update({'Name': parameter_name})
644+
645+
parameters.append(settings)
646+
647+
elif isinstance(parameter, string_types):
648+
if not re.match('method\.request\.(querystring|path|header)\.', parameter):
649+
raise InvalidEventException(
650+
self.relative_id,
651+
"Invalid value for 'RequestParameters' property. Keys must be in the format "
652+
"'method.request.[querystring|path|header].{value}', "
653+
"e.g 'method.request.header.Authorization'.")
654+
655+
settings = default_value.copy()
656+
settings.update({'Name': parameter})
657+
658+
parameters.append(settings)
659+
660+
else:
661+
raise InvalidEventException(
662+
self.relative_id,
663+
"Invalid value for 'RequestParameters' property. Property must be either a string or an object")
664+
665+
editor.add_request_parameters_to_method(path=self.Path, method_name=self.Method,
666+
request_parameters=parameters)
667+
610668
api["DefinitionBody"] = editor.swagger
611669

612670

samtranslator/swagger/swagger.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ class SwaggerEditor(object):
2323
_X_APIGW_GATEWAY_RESPONSES = 'x-amazon-apigateway-gateway-responses'
2424
_X_APIGW_POLICY = 'x-amazon-apigateway-policy'
2525
_X_ANY_METHOD = 'x-amazon-apigateway-any-method'
26+
_CACHE_KEY_PARAMETERS = 'cacheKeyParameters'
2627

2728
def __init__(self, doc):
2829
"""
@@ -820,6 +821,53 @@ def add_resource_policy(self, resource_policy):
820821

821822
self._doc[self._X_APIGW_POLICY] = self.resource_policy
822823

824+
def add_request_parameters_to_method(self, path, method_name, request_parameters):
825+
"""
826+
Add Parameters to Swagger.
827+
828+
:param string path: Path name
829+
:param string method_name: Method name
830+
:param list request_parameters: Dictionary of Parameters
831+
:return:
832+
"""
833+
834+
normalized_method_name = self._normalize_method_name(method_name)
835+
# It is possible that the method could have two definitions in a Fn::If block.
836+
for method_definition in self.get_method_contents(self.get_path(path)[normalized_method_name]):
837+
838+
# If no integration given, then we don't need to process this definition (could be AWS::NoValue)
839+
if not self.method_definition_has_integration(method_definition):
840+
continue
841+
842+
existing_parameters = method_definition.get('parameters', [])
843+
844+
for request_parameter in request_parameters:
845+
846+
parameter_name = request_parameter['Name']
847+
location_name = parameter_name.replace('method.request.', '')
848+
location, name = location_name.split('.')
849+
850+
if location == 'querystring':
851+
location = 'query'
852+
853+
parameter = {
854+
'in': location,
855+
'name': name,
856+
'required': request_parameter['Required'],
857+
'type': 'string'
858+
}
859+
860+
existing_parameters.append(parameter)
861+
862+
if request_parameter['Caching']:
863+
864+
integration = method_definition[self._X_APIGW_INTEGRATION]
865+
cache_parameters = integration.get(self._CACHE_KEY_PARAMETERS, [])
866+
cache_parameters.append(parameter_name)
867+
integration[self._CACHE_KEY_PARAMETERS] = cache_parameters
868+
869+
method_definition['parameters'] = existing_parameters
870+
823871
@property
824872
def swagger(self):
825873
"""

tests/swagger/test_swagger.py

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1116,6 +1116,7 @@ def test_must_add_body_parameter_to_method_openapi_required_true(self):
11161116

11171117
self.assertEqual(expected, editor.swagger['paths']['/foo']['get']['requestBody'])
11181118

1119+
11191120
class TestSwaggerEditor_add_auth(TestCase):
11201121

11211122
def setUp(self):
@@ -1231,3 +1232,137 @@ def test_set_method_apikey_handling_apikeyrequired_true(self):
12311232

12321233
self.editor._set_method_apikey_handling(path, method, True)
12331234
self.assertEqual(expected, self.editor.swagger["paths"][path][method]["security"])
1235+
1236+
1237+
class TestSwaggerEditor_add_request_parameter_to_method(TestCase):
1238+
1239+
def setUp(self):
1240+
self.original_swagger = {
1241+
"swagger": "2.0",
1242+
"paths": {
1243+
"/foo": {
1244+
'get': {
1245+
'x-amazon-apigateway-integration': {
1246+
'test': 'must have integration'
1247+
}
1248+
}
1249+
}
1250+
}
1251+
}
1252+
1253+
self.editor = SwaggerEditor(self.original_swagger)
1254+
1255+
def test_must_add_parameter_to_method_with_required_and_caching_true(self):
1256+
1257+
parameters = [{
1258+
'Name': 'method.request.header.Authorization',
1259+
'Required': True,
1260+
'Caching': True
1261+
}]
1262+
1263+
self.editor.add_request_parameters_to_method('/foo', 'get', parameters)
1264+
1265+
expected_parameters = [
1266+
{
1267+
'in': 'header',
1268+
'required': True,
1269+
'name': 'Authorization',
1270+
'type': 'string'
1271+
}
1272+
]
1273+
1274+
method_swagger = self.editor.swagger['paths']['/foo']['get']
1275+
1276+
self.assertEqual(expected_parameters, method_swagger['parameters'])
1277+
self.assertEqual(['method.request.header.Authorization'], method_swagger[_X_INTEGRATION]['cacheKeyParameters'])
1278+
1279+
def test_must_add_parameter_to_method_with_required_and_caching_false(self):
1280+
1281+
parameters = [{
1282+
'Name': 'method.request.header.Authorization',
1283+
'Required': False,
1284+
'Caching': False
1285+
}]
1286+
1287+
self.editor.add_request_parameters_to_method('/foo', 'get', parameters)
1288+
1289+
expected_parameters = [
1290+
{
1291+
'in': 'header',
1292+
'required': False,
1293+
'name': 'Authorization',
1294+
'type': 'string'
1295+
}
1296+
]
1297+
1298+
method_swagger = self.editor.swagger['paths']['/foo']['get']
1299+
1300+
self.assertEqual(expected_parameters, method_swagger['parameters'])
1301+
self.assertNotIn('cacheKeyParameters', method_swagger[_X_INTEGRATION].keys())
1302+
1303+
def test_must_add_parameter_to_method_with_existing_parameters(self):
1304+
1305+
original_swagger = {
1306+
"swagger": "2.0",
1307+
"paths": {
1308+
"/foo": {
1309+
'get': {
1310+
'x-amazon-apigateway-integration': {
1311+
'test': 'must have integration'
1312+
},
1313+
'parameters': [{'test': 'existing parameter'}]
1314+
}
1315+
}
1316+
}
1317+
}
1318+
1319+
editor = SwaggerEditor(original_swagger)
1320+
1321+
parameters = [{
1322+
'Name': 'method.request.header.Authorization',
1323+
'Required': False,
1324+
'Caching': False
1325+
}]
1326+
1327+
editor.add_request_parameters_to_method('/foo', 'get', parameters)
1328+
1329+
expected_parameters = [
1330+
{
1331+
'test': 'existing parameter'
1332+
},
1333+
{
1334+
'in': 'header',
1335+
'required': False,
1336+
'name': 'Authorization',
1337+
'type': 'string'
1338+
}
1339+
]
1340+
1341+
method_swagger = editor.swagger['paths']['/foo']['get']
1342+
1343+
self.assertEqual(expected_parameters, method_swagger['parameters'])
1344+
self.assertNotIn('cacheKeyParameters', method_swagger[_X_INTEGRATION].keys())
1345+
1346+
def test_must_not_add_parameter_to_method_without_integration(self):
1347+
original_swagger = {
1348+
"swagger": "2.0",
1349+
"paths": {
1350+
"/foo": {
1351+
'get': {}
1352+
}
1353+
}
1354+
}
1355+
1356+
editor = SwaggerEditor(original_swagger)
1357+
1358+
parameters = [{
1359+
'Name': 'method.request.header.Authorization',
1360+
'Required': True,
1361+
'Caching': True
1362+
}]
1363+
1364+
editor.add_request_parameters_to_method('/foo', 'get', parameters)
1365+
1366+
expected = {}
1367+
1368+
self.assertEqual(expected, editor.swagger['paths']['/foo']['get'])

0 commit comments

Comments
 (0)