Skip to content

Commit 0b243af

Browse files
authored
fix: Fix duplicate OPTIONS method error for paths with trailing slash (#3844)
1 parent 80177b8 commit 0b243af

File tree

2 files changed

+93
-0
lines changed

2 files changed

+93
-0
lines changed

samtranslator/model/api/api_generator.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -980,7 +980,20 @@ def _add_cors(self) -> None:
980980
)
981981

982982
editor = SwaggerEditor(self.definition_body)
983+
# Track normalized paths to avoid duplicate OPTIONS methods for paths that differ only by trailing slash
984+
# API Gateway treats /path and /path/ as the same resource, so we normalize before adding CORS
985+
normalized_paths_processed: Set[str] = set()
986+
983987
for path in editor.iter_on_path():
988+
# Normalize path by removing trailing slash (except for root path "/")
989+
normalized_path = path.rstrip("/") if path != "/" else path
990+
991+
# Skip if we've already processed this normalized path to avoid duplicate OPTIONS methods
992+
if normalized_path in normalized_paths_processed:
993+
continue
994+
995+
normalized_paths_processed.add(normalized_path)
996+
984997
try:
985998
editor.add_cors( # type: ignore[no-untyped-call]
986999
path,

tests/model/api/test_api_generator.py

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,3 +49,83 @@ def test_construct_usage_plan_with_invalid_usage_plan_fields(self, AuthPropertie
4949
with self.assertRaises(InvalidResourceException) as cm:
5050
api_generator._construct_usage_plan()
5151
self.assertIn("Invalid property for", str(cm.exception))
52+
53+
def test_add_cors_with_trailing_slash_paths(self):
54+
"""Test that CORS doesn't create duplicate OPTIONS methods for paths with/without trailing slash"""
55+
# Create a simple swagger definition with paths that differ only by trailing slash
56+
definition_body = {
57+
"swagger": "2.0",
58+
"info": {"title": "TestAPI", "version": "1.0"},
59+
"paths": {
60+
"/datasets": {
61+
"post": {
62+
"x-amazon-apigateway-integration": {
63+
"type": "aws_proxy",
64+
"httpMethod": "POST",
65+
"uri": "arn:aws:apigateway:us-east-1:lambda:path/2015-03-31/functions/func/invocations",
66+
}
67+
}
68+
},
69+
"/datasets/": {
70+
"put": {
71+
"x-amazon-apigateway-integration": {
72+
"type": "aws_proxy",
73+
"httpMethod": "POST",
74+
"uri": "arn:aws:apigateway:us-east-1:lambda:path/2015-03-31/functions/func/invocations",
75+
}
76+
}
77+
},
78+
},
79+
}
80+
81+
api_generator = ApiGenerator(
82+
logical_id="TestApi",
83+
cache_cluster_enabled=None,
84+
cache_cluster_size=None,
85+
variables=None,
86+
depends_on=None,
87+
definition_body=definition_body,
88+
definition_uri=None,
89+
name=None,
90+
stage_name="Prod",
91+
shared_api_usage_plan=None, # Added required parameter
92+
template_conditions=None, # Added required parameter
93+
tags=None,
94+
endpoint_configuration=None,
95+
method_settings=None,
96+
binary_media=None,
97+
minimum_compression_size=None,
98+
cors="'*'", # Enable CORS
99+
auth=None,
100+
gateway_responses=None,
101+
access_log_setting=None,
102+
canary_setting=None,
103+
tracing_enabled=None,
104+
resource_attributes=None,
105+
passthrough_resource_attributes=None,
106+
open_api_version=None,
107+
models=None,
108+
domain=None,
109+
fail_on_warnings=None,
110+
description=None,
111+
mode=None,
112+
api_key_source_type=None,
113+
disable_execute_api_endpoint=None,
114+
)
115+
116+
# Call _add_cors which should normalize paths and avoid duplicates
117+
api_generator._add_cors()
118+
119+
# Check that OPTIONS method is not added to both /datasets and /datasets/
120+
# It should only be added once to avoid the duplicate OPTIONS error
121+
paths_with_options = [
122+
path
123+
for path, methods in api_generator.definition_body["paths"].items()
124+
if "options" in methods or "OPTIONS" in methods
125+
]
126+
127+
# We should have only ONE path with OPTIONS method (the normalized one)
128+
# Both /datasets and /datasets/ normalize to /datasets
129+
self.assertEqual(
130+
len(paths_with_options), 1, "CORS should only add OPTIONS to one of the paths that differ by trailing slash"
131+
)

0 commit comments

Comments
 (0)