Skip to content

Commit 8eb5dc5

Browse files
authored
fix(route53,cloudfront): route trailing-slash collection paths to List (#1796)
2 parents bf44e6a + 3f9d20d commit 8eb5dc5

2 files changed

Lines changed: 33 additions & 10 deletions

File tree

crates/fakecloud-cloudfront/src/router.rs

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -52,11 +52,11 @@ impl Route {
5252
pub fn route(method: &Method, path: &str, raw_query: &str) -> Option<Route> {
5353
let path = path.strip_prefix("/2020-05-31").unwrap_or(path);
5454
let path = path.trim_start_matches('/');
55-
let segs: Vec<&str> = if path.is_empty() {
56-
Vec::new()
57-
} else {
58-
path.split('/').collect()
59-
};
55+
// Filter out empty segments so a trailing slash (`/distribution/`, which
56+
// botocore/AWS CLI < 1.40 and curl emit for a collection root) routes to the
57+
// List op, not GetDistribution(id="") -> NoSuchDistribution (the #1645
58+
// shape; bug-audit 2026-06-20, 1.1).
59+
let segs: Vec<&str> = path.split('/').filter(|s| !s.is_empty()).collect();
6060
let q = QueryFlags::parse(raw_query);
6161
match (method, segs.as_slice()) {
6262
// ─── Distributions ──────────────────────────────────────────
@@ -576,6 +576,14 @@ mod tests {
576576
assert_eq!(r.id.as_deref(), Some("EDFDVBD632BHDS5"));
577577
}
578578

579+
#[test]
580+
fn trailing_slash_collection_routes_to_list() {
581+
// A trailing slash on the collection root must list, not
582+
// GetDistribution(id="") -> NoSuchDistribution (1.1).
583+
let r = route(&Method::GET, "/2020-05-31/distribution/", "").unwrap();
584+
assert_eq!(r.action, "ListDistributions");
585+
}
586+
579587
#[test]
580588
fn create_invalidation() {
581589
let r = route(

crates/fakecloud-route53/src/router.rs

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -44,11 +44,11 @@ pub fn route(method: &Method, path: &str, _raw_query: &str) -> Option<Route> {
4444
return None;
4545
}
4646
let path = path.trim_start_matches('/');
47-
let segs: Vec<&str> = if path.is_empty() {
48-
Vec::new()
49-
} else {
50-
path.split('/').collect()
51-
};
47+
// Filter out empty segments so a trailing slash (`/hostedzone/`, which
48+
// botocore/AWS CLI < 1.40 and curl emit for a collection root) routes to the
49+
// List op, not GetHostedZone(id="") -> NoSuchHostedZone (the #1645 shape;
50+
// bug-audit 2026-06-20, 1.1).
51+
let segs: Vec<&str> = path.split('/').filter(|s| !s.is_empty()).collect();
5252

5353
match (method, segs.as_slice()) {
5454
// ─── Hosted Zones ────────────────────────────────────────────
@@ -265,4 +265,19 @@ mod tests {
265265
Some(Route::with_id("ChangeResourceRecordSets", "Z123"))
266266
);
267267
}
268+
269+
#[test]
270+
fn trailing_slash_collection_routes_to_list() {
271+
// botocore/AWS CLI < 1.40 and curl append `/` to a bare collection URI;
272+
// it must route to the List op, not GetHostedZone(id="") (1.1).
273+
assert_eq!(
274+
route(&Method::GET, "/2013-04-01/hostedzone/", ""),
275+
Some(Route::just("ListHostedZones"))
276+
);
277+
// Without a trailing slash still lists.
278+
assert_eq!(
279+
route(&Method::GET, "/2013-04-01/hostedzone", ""),
280+
Some(Route::just("ListHostedZones"))
281+
);
282+
}
268283
}

0 commit comments

Comments
 (0)