/// Match a URL path against a pattern supporting wildcards. /// /// Supported patterns: /// - `/api/*` matches `/api/anything` (single level) /// - `/api/**` matches `/api/any/depth/here` /// - `/exact/path` exact match /// - `/prefix*` prefix match pub fn path_matches(pattern: &str, path: &str) -> bool { // Exact match if pattern == path { return true; } // Double-star: match any depth if pattern.ends_with("/**") { let prefix = &pattern[..pattern.len() - 3]; return path == prefix || path.starts_with(&format!("{}/", prefix)); } // Single-star at end: match single path segment if pattern.ends_with("/*") { let prefix = &pattern[..pattern.len() - 2]; if path == prefix { return true; } if path.starts_with(&format!("{}/", prefix)) { let rest = &path[prefix.len() + 1..]; // Single level means no more slashes return !rest.contains('/'); } return false; } // Star anywhere: use glob matching if pattern.contains('*') { return glob_match::glob_match(pattern, path); } false } #[cfg(test)] mod tests { use super::*; #[test] fn test_exact_path() { assert!(path_matches("/api/users", "/api/users")); assert!(!path_matches("/api/users", "/api/posts")); } #[test] fn test_single_wildcard() { assert!(path_matches("/api/*", "/api/users")); assert!(path_matches("/api/*", "/api/posts")); assert!(!path_matches("/api/*", "/api/users/123")); } #[test] fn test_double_wildcard() { assert!(path_matches("/api/**", "/api/users")); assert!(path_matches("/api/**", "/api/users/123")); assert!(path_matches("/api/**", "/api/users/123/posts")); } }