#![allow(dead_code)]
#[must_use]
pub fn normalize_cache_key(url: &str) -> String {
if url.is_empty() {
return String::new();
}
let no_fragment = match url.find('#') {
Some(pos) => &url[..pos],
None => url,
};
let (path_part, query_part) = match no_fragment.find('?') {
Some(pos) => (&no_fragment[..pos], Some(&no_fragment[pos + 1..])),
None => (no_fragment, None),
};
let mut path_lower = path_part.to_lowercase();
let is_root_path = if let Some(after_scheme) = path_lower.find("://") {
let after_host_start = after_scheme + 3;
let after_host = &path_lower[after_host_start..];
after_host
.find('/')
.map_or(false, |p| after_host[p..].len() == 1)
} else {
path_lower == "/"
};
if path_lower.ends_with('/') && !is_root_path {
path_lower.pop();
}
match query_part {
None | Some("") => path_lower,
Some(query) => {
let mut params: Vec<String> = query
.split('&')
.filter(|s| !s.is_empty())
.map(|p| p.to_lowercase())
.collect();
params.sort_unstable();
let sorted_query = params.join("&");
if sorted_query.is_empty() {
path_lower
} else {
format!("{path_lower}?{sorted_query}")
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_lowercase_host_and_path() {
let key = normalize_cache_key("https://CDN.Example.COM/Video/Clip.mp4");
assert_eq!(key, "https://cdn.example.com/video/clip.mp4");
}
#[test]
fn test_strip_trailing_slash() {
let key = normalize_cache_key("https://example.com/path/");
assert_eq!(key, "https://example.com/path");
}
#[test]
fn test_preserve_root_slash() {
let key = normalize_cache_key("https://example.com/");
assert_eq!(key, "https://example.com/");
}
#[test]
fn test_sort_query_params() {
let key = normalize_cache_key("https://example.com/v?b=2&a=1");
assert_eq!(key, "https://example.com/v?a=1&b=2");
}
#[test]
fn test_strip_fragment() {
let key = normalize_cache_key("https://example.com/page#section");
assert_eq!(key, "https://example.com/page");
}
#[test]
fn test_fragment_and_query_and_trailing_slash() {
let key = normalize_cache_key("https://CDN.Example.com/Video/Clip.mp4/?b=2&a=1#frag");
assert_eq!(key, "https://cdn.example.com/video/clip.mp4?a=1&b=2");
}
#[test]
fn test_no_query_no_fragment() {
let key = normalize_cache_key("https://example.com/asset.m4s");
assert_eq!(key, "https://example.com/asset.m4s");
}
#[test]
fn test_empty_url_returns_empty() {
let key = normalize_cache_key("");
assert_eq!(key, "");
}
#[test]
fn test_empty_query_string_omitted() {
let key = normalize_cache_key("https://example.com/path?");
assert!(!key.contains('?'), "Got: {key}");
}
#[test]
fn test_multiple_query_params_sorted() {
let key = normalize_cache_key("http://cdn.test/v?z=9&m=3&a=1");
assert_eq!(key, "http://cdn.test/v?a=1&m=3&z=9");
}
#[test]
fn test_already_normalised_is_idempotent() {
let url = "https://example.com/path?a=1&b=2";
let once = normalize_cache_key(url);
let twice = normalize_cache_key(&once);
assert_eq!(once, twice);
}
#[test]
fn test_path_without_scheme() {
let key = normalize_cache_key("/PATH/TO/FILE?Z=1&A=2");
assert_eq!(key, "/path/to/file?a=2&z=1");
}
}