Description
When an AWS::Serverless::Api has an inline DefinitionBody and MergeDefinitions: true, a function Api event (under AWS::Serverless::Function) with Method: ANY is merged into the body under the lowercase key any. Without MergeDefinitions (SAM-generated body), the same event correctly produces x-amazon-apigateway-any-method.
any is neither a valid OpenAPI/Swagger operation nor the API Gateway extension, so API Gateway silently ignores it. The deployment succeeds, but no ANY method is created on the resource (only OPTIONS if Cors is set). Every request returns 403 from API Gateway and never reaches the Lambda:
Invalid key=value pair (missing equal-sign) in Authorization header ...
A second, related defect: with MergeDefinitions: true and paths empty ({}), null or missing in DefinitionBody, the event's path is left out of the output entirely.
Steps to reproduce
Transform: AWS::Serverless-2016-10-31
Resources:
MyApi:
Type: AWS::Serverless::Api
Properties:
StageName: prod
MergeDefinitions: true
DefinitionBody:
openapi: "3.0.1"
info:
title: repro
version: "1.0"
paths:
/other:
x-amazon-apigateway-any-method:
x-amazon-apigateway-integration:
type: http_proxy
httpMethod: ANY
uri: https://example.com
MyFunction:
Type: AWS::Serverless::Function
Properties:
Runtime: python3.11
Handler: index.handler
InlineCode: "def handler(e, c): return {}"
Events:
AnyApi:
Type: Api
Properties:
Path: /items/{proxy+}
Method: ANY
RestApiId: !Ref MyApi
- Transform the template (I used SAM translator).
- Inspect
Resources.MyApi.Properties.Body.paths["/items/{proxy+}"].
Observed result
"/items/{proxy+}": {
"any": {
"x-amazon-apigateway-integration": {
"type": "aws_proxy",
"httpMethod": "POST",
"uri": { "Fn::Sub": "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFunction.Arn}/invocations" }
},
"responses": {}
}
}
| Configuration |
Method keys on /items/{proxy+} |
No DefinitionBody (SAM-generated), Method: ANY |
x-amazon-apigateway-any-method |
MergeDefinitions: true, OpenAPI 3.0.1, Method: ANY |
any |
MergeDefinitions: true, Swagger 2.0, Method: ANY |
any |
MergeDefinitions: true + Cors, Method: ANY |
any, options |
MergeDefinitions: true, paths: {}, Method: ANY |
path missing |
MergeDefinitions: true, Method: GET |
get (correct) |
Expected result
With MergeDefinitions: true, an Api event with Method: ANY is merged as x-amazon-apigateway-any-method, the same as when SAM generates the body, and the event path is always present in the merged body, including when the inline paths is empty, null or missing.
Possible cause
Api.to_cloudformation lowercases the event method (self.Method = self.Method.lower()). In Api._get_merged_definitions (samtranslator/model/eventsources/push.py), the normalized name is computed but only used to read SAM's generated body. The lookup of the inline method and the write-back both use the raw self.Method:
merged_definition_body = source_definition_body.copy()
source_body_paths = merged_definition_body.get("paths") or {} # (2) new dict never assigned back
try:
path_method_body = dict_deep_get(source_body_paths, [self.Path, self.Method]) or {} # (1) looks up "any"
...
method = editor._normalize_method_name(self.Method) # "x-amazon-apigateway-any-method"
generated_path_method_body = dest_definition_body["paths"][self.Path][method]
...
if self.Path not in source_body_paths:
source_body_paths[self.Path] = {self.Method: merged_path_method_body} # (1) writes "any"
source_body_paths[self.Path][self.Method] = merged_path_method_body # (1) writes "any"
- Wrong key:
self.Method is any, so the inline x-amazon-apigateway-any-method entry is never found (explaining the duplicate keys) and the merged result is written under any.
- Dropped path: when
paths is falsy, or {} creates a new dict that is never stored back on merged_definition_body, so the merged path is discarded.
The existing snapshot tests api_merge_definitions_with_any_method and api_with_merge_definitions_null_paths currently lock in the second defect: their expected output contains no event path.
Proposed solution
Use the normalized method name for lookup, validation message and write-back, and always store the paths dict on the merged body:
merged_definition_body = source_definition_body.copy()
source_body_paths = merged_definition_body.get("paths") or {}
+ merged_definition_body["paths"] = source_body_paths
+
+ # Normalized version of HTTP Method. It also handle API Gateway specific methods like "ANY"
+ method = editor._normalize_method_name(self.Method)
try:
- path_method_body = dict_deep_get(source_body_paths, [self.Path, self.Method]) or {}
+ path_method_body = dict_deep_get(source_body_paths, [self.Path, method]) or {}
except InvalidValueType as e:
raise InvalidResourceException(api_id, f"Property 'DefinitionBody' is invalid: {e!s}") from e
- sam_expect(path_method_body, api_id, f"DefinitionBody.paths.{self.Path}.{self.Method}").to_be_a_map()
+ sam_expect(path_method_body, api_id, f"DefinitionBody.paths.{self.Path}.{method}").to_be_a_map()
- # Normalized version of HTTP Method. It also handle API Gateway specific methods like "ANY"
- method = editor._normalize_method_name(self.Method)
dest_definition_body = editor.swagger
generated_path_method_body = dest_definition_body["paths"][self.Path][method]
# this guarantees that the merged definition use SAM generated value for a conflicting key
merged_path_method_body = {**path_method_body, **generated_path_method_body}
if self.Path not in source_body_paths:
- source_body_paths[self.Path] = {self.Method: merged_path_method_body}
- source_body_paths[self.Path][self.Method] = merged_path_method_body
+ source_body_paths[self.Path] = {}
+ source_body_paths[self.Path][method] = merged_path_method_body
Test changes:
- Regenerate
api_merge_definitions_with_any_method and api_with_merge_definitions_null_paths outputs (all partitions). They will now contain the /proxy path under x-amazon-apigateway-any-method, with new deployment logical IDs.
- Add a transform test (e.g.
api_merge_definitions_with_existing_any_method) where the inline OpenAPI 3.0.1 body already declares x-amazon-apigateway-any-method on the event path plus another path, asserting a single merged x-amazon-apigateway-any-method key that keeps inline fields and uses SAM's integration.
Behavior note: an inline method declared under the (invalid) lowercase key any is no longer matched for merging. Since API Gateway never honored that key, this should not affect working templates.
With the change applied, the full unit test suite passes and the reproduction template yields x-amazon-apigateway-any-method (plus options with Cors) for both the populated and empty paths cases.
Additional environment details
- OS: macOS (transform reproduced locally); deployed via AWS CloudFormation
sam --version: SAM CLI, version 1.166.1 (aws-sam-translator 1.110.0); also reproduced on develop (aws-sam-translator 1.113.0)
- AWS region: eu-west-1
Description
When an
AWS::Serverless::Apihas an inlineDefinitionBodyandMergeDefinitions: true, a functionApievent (underAWS::Serverless::Function) withMethod: ANYis merged into the body under the lowercase keyany. WithoutMergeDefinitions(SAM-generated body), the same event correctly producesx-amazon-apigateway-any-method.anyis neither a valid OpenAPI/Swagger operation nor the API Gateway extension, so API Gateway silently ignores it. The deployment succeeds, but noANYmethod is created on the resource (onlyOPTIONSifCorsis set). Every request returns 403 from API Gateway and never reaches the Lambda:A second, related defect: with
MergeDefinitions: trueandpathsempty ({}),nullor missing inDefinitionBody, the event's path is left out of the output entirely.Steps to reproduce
Resources.MyApi.Properties.Body.paths["/items/{proxy+}"].Observed result
/items/{proxy+}DefinitionBody(SAM-generated),Method: ANYx-amazon-apigateway-any-methodMergeDefinitions: true, OpenAPI 3.0.1,Method: ANYanyMergeDefinitions: true, Swagger 2.0,Method: ANYanyMergeDefinitions: true+Cors,Method: ANYany,optionsMergeDefinitions: true,paths: {},Method: ANYMergeDefinitions: true,Method: GETget(correct)Expected result
With
MergeDefinitions: true, anApievent withMethod: ANYis merged asx-amazon-apigateway-any-method, the same as when SAM generates the body, and the event path is always present in the merged body, including when the inlinepathsis empty,nullor missing.Possible cause
Api.to_cloudformationlowercases the event method (self.Method = self.Method.lower()). InApi._get_merged_definitions(samtranslator/model/eventsources/push.py), the normalized name is computed but only used to read SAM's generated body. The lookup of the inline method and the write-back both use the rawself.Method:self.Methodisany, so the inlinex-amazon-apigateway-any-methodentry is never found (explaining the duplicate keys) and the merged result is written underany.pathsis falsy,or {}creates a new dict that is never stored back onmerged_definition_body, so the merged path is discarded.The existing snapshot tests
api_merge_definitions_with_any_methodandapi_with_merge_definitions_null_pathscurrently lock in the second defect: their expected output contains no event path.Proposed solution
Use the normalized method name for lookup, validation message and write-back, and always store the paths dict on the merged body:
merged_definition_body = source_definition_body.copy() source_body_paths = merged_definition_body.get("paths") or {} + merged_definition_body["paths"] = source_body_paths + + # Normalized version of HTTP Method. It also handle API Gateway specific methods like "ANY" + method = editor._normalize_method_name(self.Method) try: - path_method_body = dict_deep_get(source_body_paths, [self.Path, self.Method]) or {} + path_method_body = dict_deep_get(source_body_paths, [self.Path, method]) or {} except InvalidValueType as e: raise InvalidResourceException(api_id, f"Property 'DefinitionBody' is invalid: {e!s}") from e - sam_expect(path_method_body, api_id, f"DefinitionBody.paths.{self.Path}.{self.Method}").to_be_a_map() + sam_expect(path_method_body, api_id, f"DefinitionBody.paths.{self.Path}.{method}").to_be_a_map() - # Normalized version of HTTP Method. It also handle API Gateway specific methods like "ANY" - method = editor._normalize_method_name(self.Method) dest_definition_body = editor.swagger generated_path_method_body = dest_definition_body["paths"][self.Path][method] # this guarantees that the merged definition use SAM generated value for a conflicting key merged_path_method_body = {**path_method_body, **generated_path_method_body} if self.Path not in source_body_paths: - source_body_paths[self.Path] = {self.Method: merged_path_method_body} - source_body_paths[self.Path][self.Method] = merged_path_method_body + source_body_paths[self.Path] = {} + source_body_paths[self.Path][method] = merged_path_method_bodyTest changes:
api_merge_definitions_with_any_methodandapi_with_merge_definitions_null_pathsoutputs (all partitions). They will now contain the/proxypath underx-amazon-apigateway-any-method, with new deployment logical IDs.api_merge_definitions_with_existing_any_method) where the inline OpenAPI 3.0.1 body already declaresx-amazon-apigateway-any-methodon the event path plus another path, asserting a single mergedx-amazon-apigateway-any-methodkey that keeps inline fields and uses SAM's integration.Behavior note: an inline method declared under the (invalid) lowercase key
anyis no longer matched for merging. Since API Gateway never honored that key, this should not affect working templates.With the change applied, the full unit test suite passes and the reproduction template yields
x-amazon-apigateway-any-method(plusoptionswithCors) for both the populated and emptypathscases.Additional environment details
sam --version: SAM CLI, version 1.166.1 (aws-sam-translator1.110.0); also reproduced ondevelop(aws-sam-translator1.113.0)