Skip to content

Commit 5ecc134

Browse files
authored
fix: Raise correct exception when path/method items from get_conditional_contents are not dict (#2683)
1 parent 8c41641 commit 5ecc134

File tree

6 files changed

+35
-26
lines changed

6 files changed

+35
-26
lines changed

samtranslator/open_api/base_editor.py

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -48,18 +48,20 @@ def method_definition_has_integration(method_definition: Dict[str, Any]) -> bool
4848
:param dict method_definition: method definition dictionary
4949
:return: True if an integration exists
5050
"""
51-
5251
return bool(method_definition.get(BaseEditor._X_APIGW_INTEGRATION))
5352

54-
def method_has_integration(self, method: Dict[str, Any]) -> bool:
53+
def method_has_integration(self, raw_method_definition: Dict[str, Any], path: str, method: str) -> bool:
5554
"""
5655
Returns true if the given method contains a valid method definition.
5756
This uses the get_conditional_contents function to handle conditionals.
5857
59-
:param dict method: method dictionary
58+
:param dict raw_method_definition: raw method dictionary
59+
:param str path: path name
60+
:param str method: method name
6061
:return: true if method has one or multiple integrations
6162
"""
62-
for method_definition in self.get_conditional_contents(method):
63+
for method_definition in self.get_conditional_contents(raw_method_definition):
64+
self.validate_method_definition_is_dict(method_definition, path, method)
6365
if self.method_definition_has_integration(method_definition):
6466
return True
6567
return False
@@ -117,7 +119,7 @@ def has_path(self, path: str, method: Optional[str] = None) -> bool:
117119
method = self._normalize_method_name(method)
118120
if method:
119121
for path_item in self.get_conditional_contents(self.paths.get(path)):
120-
if not path_item or method not in path_item:
122+
if not isinstance(path_item, dict) or method not in path_item:
121123
return False
122124
return True
123125

@@ -136,8 +138,11 @@ def has_integration(self, path: str, method: str) -> bool:
136138
return False
137139

138140
for path_item in self.get_conditional_contents(self.paths.get(path)):
141+
BaseEditor.validate_path_item_is_dict(path_item, path)
139142
method_definition = path_item.get(method)
140-
if not (isinstance(method_definition, dict) and self.method_has_integration(method_definition)):
143+
if not (
144+
isinstance(method_definition, dict) and self.method_has_integration(method_definition, path, method)
145+
):
141146
return False
142147
# Integration present and non-empty
143148
return True
@@ -192,7 +197,9 @@ def iter_on_method_definitions_for_path_at_method(
192197
normalized_method_name = self._normalize_method_name(method_name)
193198

194199
for path_item in self.get_conditional_contents(self.paths.get(path_name)):
200+
BaseEditor.validate_path_item_is_dict(path_item, path_name)
195201
for method_definition in self.get_conditional_contents(path_item.get(normalized_method_name)):
202+
BaseEditor.validate_method_definition_is_dict(method_definition, path_name, method_name)
196203
if skip_methods_without_apigw_integration and not self.method_definition_has_integration(
197204
method_definition
198205
):
@@ -224,6 +231,12 @@ def validate_path_item_is_dict(path_item: Any, path: str) -> None:
224231
path_item, "Value of '{}' path must be a dictionary according to Swagger spec.".format(path)
225232
)
226233

234+
@staticmethod
235+
def validate_method_definition_is_dict(method_definition: Optional[Any], path: str, method: str) -> None:
236+
BaseEditor.validate_is_dict(
237+
method_definition, f"Definition of method '{method}' for path '{path}' should be a map."
238+
)
239+
227240
@staticmethod
228241
def safe_compare_regex_with_string(regex: str, data: Any) -> bool:
229242
return re.match(regex, str(data)) is not None

samtranslator/open_api/open_api.py

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@ def add_lambda_integration( # type: ignore[no-untyped-def]
127127
integration_uri = make_conditional(condition, integration_uri)
128128

129129
for path_item in self.get_conditional_contents(self.paths.get(path)):
130+
BaseEditor.validate_path_item_is_dict(path_item, path)
130131
# create as Py27Dict and insert key one by one to preserve input order
131132
if path_item[method] is None:
132133
path_item[method] = Py27Dict()
@@ -155,8 +156,10 @@ def iter_on_all_methods_for_path(self, path_name, skip_methods_without_apigw_int
155156
:yields list of (method name, method definition) tuples
156157
"""
157158
for path_item in self.get_conditional_contents(self.paths.get(path_name)):
159+
BaseEditor.validate_path_item_is_dict(path_item, path_name)
158160
for method_name, method in path_item.items():
159161
for method_definition in self.get_conditional_contents(method):
162+
BaseEditor.validate_method_definition_is_dict(method_definition, path_name, method_name)
160163
if skip_methods_without_apigw_integration and not self.method_definition_has_integration(
161164
method_definition
162165
):
@@ -253,6 +256,7 @@ def set_path_default_authorizer(
253256
:param dict authorizers: Dict of Authorizer configurations defined on the related Api.
254257
"""
255258
for path_item in self.get_conditional_contents(self.paths.get(path)):
259+
BaseEditor.validate_path_item_is_dict(path_item, path)
256260
for method_name, method in path_item.items():
257261
normalized_method_name = self._normalize_method_name(method_name)
258262
# Excluding parameters section
@@ -270,16 +274,8 @@ def set_path_default_authorizer(
270274
]
271275
)
272276
for method_definition in self.get_conditional_contents(method):
273-
# check if there is any method_definition given by customer
274-
if not method_definition:
275-
raise InvalidDocumentException(
276-
[
277-
InvalidTemplateException(
278-
f"Invalid method definition ({normalized_method_name}) for path: {path}"
279-
)
280-
]
281-
)
282277
# If no integration given, then we don't need to process this definition (could be AWS::NoValue)
278+
BaseEditor.validate_method_definition_is_dict(method_definition, path, method_name)
283279
if not self.method_definition_has_integration(method_definition):
284280
continue
285281
existing_security = method_definition.get("security")

samtranslator/swagger/swagger.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ def add_lambda_integration( # type: ignore[no-untyped-def]
121121
integration_uri = make_conditional(condition, integration_uri)
122122

123123
for path_item in self.get_conditional_contents(self.paths.get(path)):
124+
BaseEditor.validate_path_item_is_dict(path_item, path)
124125
path_item[method][self._X_APIGW_INTEGRATION] = Py27Dict()
125126
# insert key one by one to preserce input order
126127
path_item[method][self._X_APIGW_INTEGRATION]["type"] = "aws_proxy"
@@ -187,6 +188,7 @@ def add_state_machine_integration( # type: ignore[no-untyped-def]
187188
integration_uri = make_conditional(condition, integration_uri)
188189

189190
for path_item in self.get_conditional_contents(self.paths.get(path)):
191+
BaseEditor.validate_path_item_is_dict(path_item, path)
190192
# Responses
191193
integration_responses = Py27Dict()
192194
# insert key one by one to preserce input order
@@ -233,18 +235,14 @@ def iter_on_all_methods_for_path(self, path_name, skip_methods_without_apigw_int
233235
:yields list of (method name, method definition) tuples
234236
"""
235237
for path_item in self.get_conditional_contents(self.paths.get(path_name)):
238+
BaseEditor.validate_path_item_is_dict(path_item, path_name)
236239
for method_name, method in path_item.items():
237240
# Excluding non-method sections
238241
if method_name in SwaggerEditor._EXCLUDED_PATHS_FIELDS:
239242
continue
240243

241244
for method_definition in self.get_conditional_contents(method):
242-
SwaggerEditor.validate_is_dict(
243-
method_definition,
244-
'Value of "{}" ({}) for path {} is not a valid dictionary.'.format(
245-
method_name, method_definition, path_name
246-
),
247-
)
245+
BaseEditor.validate_method_definition_is_dict(method_definition, path_name, method_name)
248246
if skip_methods_without_apigw_integration and not self.method_definition_has_integration(
249247
method_definition
250248
):
@@ -280,6 +278,7 @@ def add_cors( # type: ignore[no-untyped-def]
280278
"""
281279

282280
for path_item in self.get_conditional_contents(self.paths.get(path)):
281+
BaseEditor.validate_path_item_is_dict(path_item, path)
283282
# Skip if Options is already present
284283
method = self._normalize_method_name(self._OPTIONS_METHOD)
285284
if method in path_item:
@@ -290,7 +289,7 @@ def add_cors( # type: ignore[no-untyped-def]
290289

291290
if not allowed_methods:
292291
# AllowMethods is not given. Let's try to generate the list from the given Swagger.
293-
allowed_methods = self._make_cors_allowed_methods_for_path_item(path_item) # type: ignore[no-untyped-call]
292+
allowed_methods = self._make_cors_allowed_methods_for_path_item(path_item)
294293

295294
# APIGW expects the value to be a "string expression". Hence wrap in another quote. Ex: "'GET,POST,DELETE'"
296295
allowed_methods = "'{}'".format(allowed_methods)
@@ -412,7 +411,7 @@ def _options_method_response_for_cors( # type: ignore[no-untyped-def]
412411
to_return["responses"]["200"]["headers"] = response_headers
413412
return to_return
414413

415-
def _make_cors_allowed_methods_for_path_item(self, path_item): # type: ignore[no-untyped-def]
414+
def _make_cors_allowed_methods_for_path_item(self, path_item: Dict[str, Any]) -> str:
416415
"""
417416
Creates the value for Access-Control-Allow-Methods header for given path item. All HTTP methods defined for this
418417
path item will be included in the result. If the path item contains "ANY" method, then *all available* HTTP methods will
@@ -993,6 +992,7 @@ def _get_method_path_uri_list(self, path, stage): # type: ignore[no-untyped-def
993992
"""
994993
methods = []
995994
for path_item in self.get_conditional_contents(self.paths.get(path)):
995+
BaseEditor.validate_path_item_is_dict(path_item, path)
996996
methods += list(path_item.keys())
997997

998998
uri_list = []
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
{
2-
"errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 1. Structure of the SAM template is invalid. Invalid method definition (post) for path: /test"
2+
"errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 1. Structure of the SAM template is invalid. Definition of method 'post' for path '/test' should be a map."
33
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
{
2-
"errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 1. Structure of the SAM template is invalid. Value of \"tags\" (['InvalidMethodDefinition']) for path / is not a valid dictionary."
2+
"errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 1. Structure of the SAM template is invalid. Definition of method 'tags' for path '/' should be a map."
33
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
{
2-
"errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 1. Structure of the SAM template is invalid. Value of \"get\" (None) for path / is not a valid dictionary."
2+
"errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 1. Structure of the SAM template is invalid. Definition of method 'get' for path '/' should be a map."
33
}

0 commit comments

Comments
 (0)