1fn parse_pattern(pattern: &str) -> (Option<&str>, &str) {
12 let pattern = pattern.trim();
13
14 let methods = ["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS", "CONNECT", "TRACE"];
16
17 for method in &methods {
18 if let Some(rest) = pattern.strip_prefix(method) {
19 if rest.starts_with(' ') || rest.starts_with('\t') {
21 let path_pattern = rest.trim_start();
22 return (Some(method), path_pattern);
23 }
24 }
25 }
26
27 (None, pattern)
28}
29
30pub fn matches_pattern(path: &str, pattern: &str) -> bool {
34 matches_pattern_with_method(None, path, pattern)
35}
36
37pub fn matches_pattern_with_method(method: Option<&str>, path: &str, pattern: &str) -> bool {
44 let (pattern_method, path_pattern) = parse_pattern(pattern);
45
46 if let Some(required_method) = pattern_method {
48 if let Some(actual_method) = method {
49 if required_method != actual_method {
50 return false;
51 }
52 } else {
53 return false;
55 }
56 }
57
58 matches_path_pattern(path, path_pattern)
60}
61
62fn matches_path_pattern(path: &str, pattern: &str) -> bool {
64 let segments: Vec<&str> = pattern.split('*').collect();
66
67 if segments.len() == 1 {
68 return path == pattern;
70 }
71
72 let mut current_pos = 0;
73
74 for (i, segment) in segments.iter().enumerate() {
75 if i == 0 {
76 if !segment.is_empty() && !path.starts_with(segment) {
78 return false;
79 }
80 current_pos = segment.len();
81 } else if i == segments.len() - 1 {
82 if !segment.is_empty() && !path.ends_with(segment) {
84 return false;
85 }
86 if !segment.is_empty() {
88 if let Some(pos) = path[current_pos..].find(segment) {
89 if current_pos + pos + segment.len() != path.len() {
90 return false;
91 }
92 } else {
93 return false;
94 }
95 }
96 } else {
97 if let Some(pos) = path[current_pos..].find(segment) {
99 current_pos += pos + segment.len();
100 } else {
101 return false;
102 }
103 }
104 }
105
106 true
107}
108
109pub fn should_cache_path(
115 method: &str,
116 path: &str,
117 include_paths: &[String],
118 exclude_paths: &[String],
119) -> bool {
120 if !exclude_paths.is_empty() {
122 for pattern in exclude_paths {
123 if matches_pattern_with_method(Some(method), path, pattern) {
124 return false;
125 }
126 }
127 }
128
129 if include_paths.is_empty() {
131 return true;
132 }
133
134 for pattern in include_paths {
136 if matches_pattern_with_method(Some(method), path, pattern) {
137 return true;
138 }
139 }
140
141 false
142}
143
144#[cfg(test)]
145mod tests {
146 use super::*;
147
148 #[test]
149 fn test_exact_match() {
150 assert!(matches_pattern("/api/users", "/api/users"));
151 assert!(!matches_pattern("/api/users", "/api/posts"));
152 }
153
154 #[test]
155 fn test_wildcard_at_end() {
156 assert!(matches_pattern("/api/users", "/api/*"));
157 assert!(matches_pattern("/api/users/123", "/api/*"));
158 assert!(!matches_pattern("/apiv2/users", "/api/*"));
159 }
160
161 #[test]
162 fn test_wildcard_at_start() {
163 assert!(matches_pattern("/api/users", "*/users"));
164 assert!(matches_pattern("/v1/api/users", "*/users"));
165 assert!(!matches_pattern("/api/posts", "*/users"));
166 }
167
168 #[test]
169 fn test_wildcard_in_middle() {
170 assert!(matches_pattern("/api/v1/users", "/api/*/users"));
171 assert!(matches_pattern("/api/v2/users", "/api/*/users"));
172 assert!(!matches_pattern("/api/v1/posts", "/api/*/users"));
173 }
174
175 #[test]
176 fn test_multiple_wildcards() {
177 assert!(matches_pattern("/api/v1/users/123", "/api/*/users/*"));
178 assert!(matches_pattern("/api/v2/users/456", "/api/*/users/*"));
179 assert!(!matches_pattern("/api/v1/posts/123", "/api/*/users/*"));
180 }
181
182 #[test]
183 fn test_wildcard_only() {
184 assert!(matches_pattern("/anything", "*"));
185 assert!(matches_pattern("/api/users/123", "*"));
186 }
187
188 #[test]
189 fn test_should_cache_path_empty_filters() {
190 assert!(should_cache_path("GET", "/api/users", &[], &[]));
192 assert!(should_cache_path("POST", "/anything", &[], &[]));
193 }
194
195 #[test]
196 fn test_should_cache_path_include_only() {
197 let include = vec!["/api/*".to_string(), "/public/*".to_string()];
198 let exclude = vec![];
199
200 assert!(should_cache_path("GET", "/api/users", &include, &exclude));
201 assert!(should_cache_path("GET", "/public/index.html", &include, &exclude));
202 assert!(!should_cache_path("GET", "/private/data", &include, &exclude));
203 }
204
205 #[test]
206 fn test_should_cache_path_exclude_only() {
207 let include = vec![];
208 let exclude = vec!["/admin/*".to_string(), "/private/*".to_string()];
209
210 assert!(should_cache_path("GET", "/api/users", &include, &exclude));
211 assert!(!should_cache_path("GET", "/admin/dashboard", &include, &exclude));
212 assert!(!should_cache_path("GET", "/private/data", &include, &exclude));
213 }
214
215 #[test]
216 fn test_should_cache_path_exclude_overrides_include() {
217 let include = vec!["/api/*".to_string()];
218 let exclude = vec!["/api/admin/*".to_string()];
219
220 assert!(should_cache_path("GET", "/api/users", &include, &exclude));
221 assert!(!should_cache_path("GET", "/api/admin/users", &include, &exclude));
222 }
223
224 #[test]
225 fn test_method_pattern_matching() {
226 assert!(matches_pattern_with_method(Some("POST"), "/api/users", "POST /api/users"));
228 assert!(!matches_pattern_with_method(Some("GET"), "/api/users", "POST /api/users"));
229
230 assert!(matches_pattern_with_method(Some("POST"), "/api/users", "POST /api/*"));
232 assert!(matches_pattern_with_method(Some("POST"), "/api/posts", "POST /api/*"));
233 assert!(!matches_pattern_with_method(Some("POST"), "/not-api/posts", "POST /api/*"));
234 assert!(!matches_pattern_with_method(Some("GET"), "/api/users", "POST /api/*"));
235
236 assert!(matches_pattern_with_method(Some("GET"), "/api/users", "/api/*"));
238 assert!(matches_pattern_with_method(Some("POST"), "/api/users", "/api/*"));
239
240 assert!(matches_pattern_with_method(Some("POST"), "/anything", "POST *"));
242 assert!(matches_pattern_with_method(Some("POST"), "/api/users/123", "POST *"));
243 assert!(!matches_pattern_with_method(Some("GET"), "/anything", "POST *"));
244 }
245
246 #[test]
247 fn test_should_cache_with_method_filters() {
248 let include = vec!["/api/*".to_string()];
249 let exclude = vec!["POST /api/*".to_string(), "PUT /api/*".to_string()];
250
251 assert!(should_cache_path("GET", "/api/users", &include, &exclude));
253 assert!(!should_cache_path("POST", "/api/users", &include, &exclude));
255 assert!(!should_cache_path("PUT", "/api/users", &include, &exclude));
257 assert!(should_cache_path("DELETE", "/api/users", &include, &exclude));
259 }
260
261 #[test]
262 fn test_exclude_all_posts() {
263 let include = vec![];
264 let exclude = vec!["POST *".to_string()];
265
266 assert!(!should_cache_path("POST", "/api/users", &include, &exclude));
268 assert!(!should_cache_path("POST", "/anything", &include, &exclude));
269
270 assert!(should_cache_path("GET", "/api/users", &include, &exclude));
272 assert!(should_cache_path("PUT", "/api/users", &include, &exclude));
273 }
274
275 #[test]
276 fn test_include_only_get_requests() {
277 let include = vec!["GET *".to_string()];
278 let exclude = vec![];
279
280 assert!(should_cache_path("GET", "/api/users", &include, &exclude));
282 assert!(should_cache_path("GET", "/anything", &include, &exclude));
283
284 assert!(!should_cache_path("POST", "/api/users", &include, &exclude));
286 assert!(!should_cache_path("PUT", "/api/users", &include, &exclude));
287 }
288}