Repository navigation
MergeDefinitions: true merges Method: ANY API events as any instead of x-amazon-apigateway-any-method #4000
Description
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.Methodisany, so the inlinex-amazon-apigateway-any-methodentry is never found (explaining the duplicate keys) and the merged result is written underany. - Dropped path: when
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_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_bodyTest changes:
- Regenerate
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. - Add a transform test (e.g.
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 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-translator1.110.0); also reproduced ondevelop(aws-sam-translator1.113.0)- AWS region: eu-west-1
Activity
proton0210 commentedon Oct 4, 2026
Thanks for the detailed reproduction and proposed fix, @tiagohconte. I've opened #4004 to address both the
ANYmethod key and the dropped routes whenpathsis empty, null or missing.The PR adds regression coverage and updates the transform snapshots across all three partitions. Local
make prchecks passed, including all 4,672 tests.