fn parse_pattern(pattern: &str) -> (Option<&str>, &str) {
let pattern = pattern.trim();
let methods = [
"GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS", "CONNECT", "TRACE",
];
for method in &methods {
if let Some(rest) = pattern.strip_prefix(method) {
if rest.starts_with(' ') || rest.starts_with('\t') {
let path_pattern = rest.trim_start();
return (Some(method), path_pattern);
}
}
}
(None, pattern)
}
pub fn matches_pattern(path: &str, pattern: &str) -> bool {
matches_pattern_with_method(None, path, pattern)
}
pub fn matches_pattern_with_method(method: Option<&str>, path: &str, pattern: &str) -> bool {
let (pattern_method, path_pattern) = parse_pattern(pattern);
if let Some(required_method) = pattern_method {
if let Some(actual_method) = method {
if required_method != actual_method {
return false;
}
} else {
return false;
}
}
matches_path_pattern(path, path_pattern)
}
fn matches_path_pattern(path: &str, pattern: &str) -> bool {
let segments: Vec<&str> = pattern.split('*').collect();
if segments.len() == 1 {
return path == pattern;
}
let mut current_pos = 0;
for (i, segment) in segments.iter().enumerate() {
if i == 0 {
if !segment.is_empty() && !path.starts_with(segment) {
return false;
}
current_pos = segment.len();
} else if i == segments.len() - 1 {
if !segment.is_empty() && !path.ends_with(segment) {
return false;
}
if !segment.is_empty() {
if let Some(pos) = path[current_pos..].find(segment) {
if current_pos + pos + segment.len() != path.len() {
return false;
}
} else {
return false;
}
}
} else {
if let Some(pos) = path[current_pos..].find(segment) {
current_pos += pos + segment.len();
} else {
return false;
}
}
}
true
}
pub fn should_cache_path(
method: &str,
path: &str,
include_paths: &[String],
exclude_paths: &[String],
) -> bool {
if !exclude_paths.is_empty() {
for pattern in exclude_paths {
if matches_pattern_with_method(Some(method), path, pattern) {
return false;
}
}
}
if include_paths.is_empty() {
return true;
}
for pattern in include_paths {
if matches_pattern_with_method(Some(method), path, pattern) {
return true;
}
}
false
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_exact_match() {
assert!(matches_pattern("/api/users", "/api/users"));
assert!(!matches_pattern("/api/users", "/api/posts"));
}
#[test]
fn test_wildcard_at_end() {
assert!(matches_pattern("/api/users", "/api/*"));
assert!(matches_pattern("/api/users/123", "/api/*"));
assert!(!matches_pattern("/apiv2/users", "/api/*"));
}
#[test]
fn test_wildcard_at_start() {
assert!(matches_pattern("/api/users", "*/users"));
assert!(matches_pattern("/v1/api/users", "*/users"));
assert!(!matches_pattern("/api/posts", "*/users"));
}
#[test]
fn test_wildcard_in_middle() {
assert!(matches_pattern("/api/v1/users", "/api/*/users"));
assert!(matches_pattern("/api/v2/users", "/api/*/users"));
assert!(!matches_pattern("/api/v1/posts", "/api/*/users"));
}
#[test]
fn test_multiple_wildcards() {
assert!(matches_pattern("/api/v1/users/123", "/api/*/users/*"));
assert!(matches_pattern("/api/v2/users/456", "/api/*/users/*"));
assert!(!matches_pattern("/api/v1/posts/123", "/api/*/users/*"));
}
#[test]
fn test_wildcard_only() {
assert!(matches_pattern("/anything", "*"));
assert!(matches_pattern("/api/users/123", "*"));
}
#[test]
fn test_should_cache_path_empty_filters() {
assert!(should_cache_path("GET", "/api/users", &[], &[]));
assert!(should_cache_path("POST", "/anything", &[], &[]));
}
#[test]
fn test_should_cache_path_include_only() {
let include = vec!["/api/*".to_string(), "/public/*".to_string()];
let exclude = vec![];
assert!(should_cache_path("GET", "/api/users", &include, &exclude));
assert!(should_cache_path(
"GET",
"/public/index.html",
&include,
&exclude
));
assert!(!should_cache_path(
"GET",
"/private/data",
&include,
&exclude
));
}
#[test]
fn test_should_cache_path_exclude_only() {
let include = vec![];
let exclude = vec!["/admin/*".to_string(), "/private/*".to_string()];
assert!(should_cache_path("GET", "/api/users", &include, &exclude));
assert!(!should_cache_path(
"GET",
"/admin/dashboard",
&include,
&exclude
));
assert!(!should_cache_path(
"GET",
"/private/data",
&include,
&exclude
));
}
#[test]
fn test_should_cache_path_exclude_overrides_include() {
let include = vec!["/api/*".to_string()];
let exclude = vec!["/api/admin/*".to_string()];
assert!(should_cache_path("GET", "/api/users", &include, &exclude));
assert!(!should_cache_path(
"GET",
"/api/admin/users",
&include,
&exclude
));
}
#[test]
fn test_method_pattern_matching() {
assert!(matches_pattern_with_method(
Some("POST"),
"/api/users",
"POST /api/users"
));
assert!(!matches_pattern_with_method(
Some("GET"),
"/api/users",
"POST /api/users"
));
assert!(matches_pattern_with_method(
Some("POST"),
"/api/users",
"POST /api/*"
));
assert!(matches_pattern_with_method(
Some("POST"),
"/api/posts",
"POST /api/*"
));
assert!(!matches_pattern_with_method(
Some("POST"),
"/not-api/posts",
"POST /api/*"
));
assert!(!matches_pattern_with_method(
Some("GET"),
"/api/users",
"POST /api/*"
));
assert!(matches_pattern_with_method(
Some("GET"),
"/api/users",
"/api/*"
));
assert!(matches_pattern_with_method(
Some("POST"),
"/api/users",
"/api/*"
));
assert!(matches_pattern_with_method(
Some("POST"),
"/anything",
"POST *"
));
assert!(matches_pattern_with_method(
Some("POST"),
"/api/users/123",
"POST *"
));
assert!(!matches_pattern_with_method(
Some("GET"),
"/anything",
"POST *"
));
}
#[test]
fn test_should_cache_with_method_filters() {
let include = vec!["/api/*".to_string()];
let exclude = vec!["POST /api/*".to_string(), "PUT /api/*".to_string()];
assert!(should_cache_path("GET", "/api/users", &include, &exclude));
assert!(!should_cache_path("POST", "/api/users", &include, &exclude));
assert!(!should_cache_path("PUT", "/api/users", &include, &exclude));
assert!(should_cache_path(
"DELETE",
"/api/users",
&include,
&exclude
));
}
#[test]
fn test_exclude_all_posts() {
let include = vec![];
let exclude = vec!["POST *".to_string()];
assert!(!should_cache_path("POST", "/api/users", &include, &exclude));
assert!(!should_cache_path("POST", "/anything", &include, &exclude));
assert!(should_cache_path("GET", "/api/users", &include, &exclude));
assert!(should_cache_path("PUT", "/api/users", &include, &exclude));
}
#[test]
fn test_include_only_get_requests() {
let include = vec!["GET *".to_string()];
let exclude = vec![];
assert!(should_cache_path("GET", "/api/users", &include, &exclude));
assert!(should_cache_path("GET", "/anything", &include, &exclude));
assert!(!should_cache_path("POST", "/api/users", &include, &exclude));
assert!(!should_cache_path("PUT", "/api/users", &include, &exclude));
}
}