1use crate::catcher::Catcher;
2use crate::route::{Route, Segment, RouteUri};
3
4use crate::http::{MediaType, Method};
5
6pub trait Collide<T = Self> {
7 fn collides_with(&self, other: &T) -> bool;
8}
9
10impl Route {
11 pub fn collides_with(&self, other: &Route) -> bool {
90 methods_collide(self, other)
91 && self.rank == other.rank
92 && self.uri.collides_with(&other.uri)
93 && formats_collide(self, other)
94 }
95}
96
97impl Catcher {
98 pub fn collides_with(&self, other: &Self) -> bool {
144 self.code == other.code && self.base().segments().eq(other.base().segments())
145 }
146}
147
148impl Collide for Route {
149 #[inline(always)]
150 fn collides_with(&self, other: &Route) -> bool {
151 Route::collides_with(self, other)
152 }
153}
154
155impl Collide for Catcher {
156 #[inline(always)]
157 fn collides_with(&self, other: &Self) -> bool {
158 Catcher::collides_with(self, other)
159 }
160}
161
162impl Collide for RouteUri<'_> {
163 fn collides_with(&self, other: &Self) -> bool {
164 let a_segments = &self.metadata.uri_segments;
165 let b_segments = &other.metadata.uri_segments;
166 for (seg_a, seg_b) in a_segments.iter().zip(b_segments.iter()) {
167 if seg_a.dynamic_trail || seg_b.dynamic_trail {
168 return true;
169 }
170
171 if !seg_a.collides_with(seg_b) {
172 return false;
173 }
174 }
175
176 a_segments.len() == b_segments.len()
177 }
178}
179
180impl Collide for Segment {
181 fn collides_with(&self, other: &Self) -> bool {
182 self.dynamic || other.dynamic || self.value == other.value
183 }
184}
185
186impl Collide for MediaType {
187 fn collides_with(&self, other: &Self) -> bool {
188 let collide = |a, b| a == "*" || b == "*" || a == b;
189 collide(self.top(), other.top()) && collide(self.sub(), other.sub())
190 }
191}
192
193fn methods_collide(route: &Route, other: &Route) -> bool {
194 match (route.method, other.method) {
195 (Some(a), Some(b)) => a == b,
196 (None, _) | (_, None) => true,
197 }
198}
199
200fn formats_collide(route: &Route, other: &Route) -> bool {
201 let payload_support = |m: &Option<Method>| m.and_then(|m| m.allows_request_body());
202 match (payload_support(&route.method), payload_support(&other.method)) {
203 (Some(true), Some(true)) => match (route.format.as_ref(), other.format.as_ref()) {
207 (Some(a), Some(b)) => a.collides_with(b),
208 _ => true
210 },
211 _ => true,
215 }
216}
217
218#[cfg(test)]
219mod tests {
220 use std::str::FromStr;
221
222 use super::*;
223 use crate::route::dummy_handler;
224 use crate::http::{Method, Method::*};
225
226 fn dummy_route(ranked: bool, method: impl Into<Option<Method>>, uri: &'static str) -> Route {
227 let method = method.into().unwrap_or(Get);
228 Route::ranked((!ranked).then(|| 0), method, uri, dummy_handler)
229 }
230
231 macro_rules! assert_collision {
232 ($ranked:expr, $p1:expr, $p2:expr) => (assert_collision!($ranked, None $p1, None $p2));
233 ($ranked:expr, $m1:ident $p1:expr, $m2:ident $p2:expr) => {
234 let (a, b) = (dummy_route($ranked, $m1, $p1), dummy_route($ranked, $m2, $p2));
235 assert! {
236 a.collides_with(&b),
237 "\nroutes failed to collide:\n{:?} does not collide with {:?}\n", a, b
238 }
239 };
240 (ranked $($t:tt)+) => (assert_collision!(true, $($t)+));
241 ($($t:tt)+) => (assert_collision!(false, $($t)+));
242 }
243
244 macro_rules! assert_no_collision {
245 ($ranked:expr, $p1:expr, $p2:expr) => (assert_no_collision!($ranked, None $p1, None $p2));
246 ($ranked:expr, $m1:ident $p1:expr, $m2:ident $p2:expr) => {
247 let (a, b) = (dummy_route($ranked, $m1, $p1), dummy_route($ranked, $m2, $p2));
248 assert! {
249 !a.collides_with(&b),
250 "\nunexpected collision:\n{:?} collides with {:?}\n", a, b
251 }
252 };
253 (ranked $($t:tt)+) => (assert_no_collision!(true, $($t)+));
254 ($($t:tt)+) => (assert_no_collision!(false, $($t)+));
255 }
256
257 #[test]
258 fn non_collisions() {
259 assert_no_collision!("/a", "/b");
260 assert_no_collision!("/a/b", "/a");
261 assert_no_collision!("/a/b", "/a/c");
262 assert_no_collision!("/a/hello", "/a/c");
263 assert_no_collision!("/hello", "/a/c");
264 assert_no_collision!("/hello/there", "/hello/there/guy");
265 assert_no_collision!("/a/<b>", "/b/<b>");
266 assert_no_collision!("/<a>/b", "/<b>/a");
267 assert_no_collision!("/t", "/test");
268 assert_no_collision!("/a", "/aa");
269 assert_no_collision!("/a", "/aaa");
270 assert_no_collision!("/", "/a");
271
272 assert_no_collision!("/hello", "/hello/");
273 assert_no_collision!("/hello/there", "/hello/there/");
274
275 assert_no_collision!("/a?<b>", "/b");
276 assert_no_collision!("/a/b", "/a?<b>");
277 assert_no_collision!("/a/b/c?<d>", "/a/b/c/d");
278 assert_no_collision!("/a/hello", "/a/?<hello>");
279 assert_no_collision!("/?<a>", "/hi");
280
281 assert_no_collision!(Get "/", Post "/");
282 assert_no_collision!(Post "/", Put "/");
283 assert_no_collision!(Put "/a", Put "/");
284 assert_no_collision!(Post "/a", Put "/");
285 assert_no_collision!(Get "/a", Put "/");
286 assert_no_collision!(Get "/hello", Put "/hello");
287 assert_no_collision!(Get "/<foo..>", Post "/");
288
289 assert_no_collision!("/a", "/b");
290 assert_no_collision!("/a/b", "/a");
291 assert_no_collision!("/a/b", "/a/c");
292 assert_no_collision!("/a/hello", "/a/c");
293 assert_no_collision!("/hello", "/a/c");
294 assert_no_collision!("/hello/there", "/hello/there/guy");
295 assert_no_collision!("/a/<b>", "/b/<b>");
296 assert_no_collision!("/a", "/b");
297 assert_no_collision!("/a/b", "/a");
298 assert_no_collision!("/a/b", "/a/c");
299 assert_no_collision!("/a/hello", "/a/c");
300 assert_no_collision!("/hello", "/a/c");
301 assert_no_collision!("/hello/there", "/hello/there/guy");
302 assert_no_collision!("/a/<b>", "/b/<b>");
303 assert_no_collision!("/a", "/b");
304 assert_no_collision!("/a/b", "/a");
305 assert_no_collision!("/a/b", "/a/c");
306 assert_no_collision!("/a/hello", "/a/c");
307 assert_no_collision!("/hello", "/a/c");
308 assert_no_collision!("/hello/there", "/hello/there/guy");
309 assert_no_collision!("/a/<b>", "/b/<b>");
310 assert_no_collision!("/t", "/test");
311 assert_no_collision!("/a", "/aa");
312 assert_no_collision!("/a", "/aaa");
313 assert_no_collision!("/", "/a");
314
315 assert_no_collision!("/foo", "/foo/");
316 assert_no_collision!("/foo/bar", "/foo/");
317 assert_no_collision!("/foo/bar", "/foo/bar/");
318 assert_no_collision!("/foo/<a>", "/foo/<a>/");
319 assert_no_collision!("/foo/<a>", "/<b>/<a>/");
320 assert_no_collision!("/<b>/<a>", "/<b>/<a>/");
321 assert_no_collision!("/a/", "/<a>/<b>/<c..>");
322
323 assert_no_collision!("/a", "/a/<a..>");
324 assert_no_collision!("/<a>", "/a/<a..>");
325 assert_no_collision!("/a/b", "/<a>/<b>/<c..>");
326 assert_no_collision!("/a/<b>", "/<a>/<b>/<c..>");
327 assert_no_collision!("/<a>/b", "/<a>/<b>/<c..>");
328 assert_no_collision!("/hi/<a..>", "/hi");
329
330 assert_no_collision!(ranked "/<a>", "/");
331 assert_no_collision!(ranked "/a/", "/<a>/");
332 assert_no_collision!(ranked "/hello/<a>", "/hello/");
333 assert_no_collision!(ranked "/", "/?a");
334 assert_no_collision!(ranked "/", "/?<a>");
335 assert_no_collision!(ranked "/a/<b>", "/a/<b>?d");
336 }
337
338 #[test]
339 fn collisions() {
340 assert_collision!("/<a>", "/");
341 assert_collision!("/a", "/a");
342 assert_collision!("/hello", "/hello");
343 assert_collision!("/hello/there/how/ar", "/hello/there/how/ar");
344 assert_collision!("/hello/<a>", "/hello/");
345
346 assert_collision!("/<a>", "/<b>");
347 assert_collision!("/<a>", "/b");
348 assert_collision!("/hello/<name>", "/hello/<person>");
349 assert_collision!("/hello/<name>/hi", "/hello/<person>/hi");
350 assert_collision!("/hello/<name>/hi/there", "/hello/<person>/hi/there");
351 assert_collision!("/<name>/hi/there", "/<person>/hi/there");
352 assert_collision!("/<name>/hi/there", "/dude/<name>/there");
353 assert_collision!("/<name>/<a>/<b>", "/<a>/<b>/<c>");
354 assert_collision!("/<name>/<a>/<b>/", "/<a>/<b>/<c>/");
355 assert_collision!("/<a..>", "/hi");
356 assert_collision!("/<a..>", "/hi/hey");
357 assert_collision!("/<a..>", "/hi/hey/hayo");
358 assert_collision!("/a/<a..>", "/a/hi/hey/hayo");
359 assert_collision!("/a/<b>/<a..>", "/a/hi/hey/hayo");
360 assert_collision!("/a/<b>/<c>/<a..>", "/a/hi/hey/hayo");
361 assert_collision!("/<b>/<c>/<a..>", "/a/hi/hey/hayo");
362 assert_collision!("/<b>/<c>/hey/hayo", "/a/hi/hey/hayo");
363 assert_collision!("/<a..>", "/foo");
364
365 assert_collision!("/", "/<a..>");
366 assert_collision!("/a/", "/a/<a..>");
367 assert_collision!("/<a>/", "/a/<a..>");
368 assert_collision!("/<a>/bar/", "/a/<a..>");
369
370 assert_collision!("/<a>", "/b");
371 assert_collision!("/hello/<name>", "/hello/bob");
372 assert_collision!("/<name>", "//bob");
373
374 assert_collision!("/<a..>", "///a///");
375 assert_collision!("/<a..>", "//a/bcjdklfj//<c>");
376 assert_collision!("/a/<a..>", "//a/bcjdklfj//<c>");
377 assert_collision!("/a/<b>/<c..>", "//a/bcjdklfj//<c>");
378 assert_collision!("/<a..>", "/");
379 assert_collision!("/", "/<_..>");
380 assert_collision!("/a/b/<a..>", "/a/<b..>");
381 assert_collision!("/a/b/<a..>", "/a/<b>/<b..>");
382 assert_collision!("/hi/<a..>", "/hi/");
383 assert_collision!("/<a..>", "//////");
384
385 assert_collision!("/?<a>", "/?<a>");
386 assert_collision!("/a/?<a>", "/a/?<a>");
387 assert_collision!("/a?<a>", "/a?<a>");
388 assert_collision!("/<r>?<a>", "/<r>?<a>");
389 assert_collision!("/a/b/c?<a>", "/a/b/c?<a>");
390 assert_collision!("/<a>/b/c?<d>", "/a/b/<c>?<d>");
391 assert_collision!("/?<a>", "/");
392 assert_collision!("/a?<a>", "/a");
393 assert_collision!("/a?<a>", "/a");
394 assert_collision!("/a/b?<a>", "/a/b");
395 assert_collision!("/a/b", "/a/b?<c>");
396
397 assert_collision!("/a/hi/<a..>", "/a/hi/");
398 assert_collision!("/hi/<a..>", "/hi/");
399 assert_collision!("/<a..>", "/");
400 }
401
402 fn mt_mt_collide(mt1: &str, mt2: &str) -> bool {
403 let mt_a = MediaType::from_str(mt1).expect(mt1);
404 let mt_b = MediaType::from_str(mt2).expect(mt2);
405 mt_a.collides_with(&mt_b)
406 }
407
408 #[test]
409 fn test_content_type_collisions() {
410 assert!(mt_mt_collide("application/json", "application/json"));
411 assert!(mt_mt_collide("*/json", "application/json"));
412 assert!(mt_mt_collide("*/*", "application/json"));
413 assert!(mt_mt_collide("application/*", "application/json"));
414 assert!(mt_mt_collide("application/*", "*/json"));
415 assert!(mt_mt_collide("something/random", "something/random"));
416
417 assert!(!mt_mt_collide("text/*", "application/*"));
418 assert!(!mt_mt_collide("*/text", "*/json"));
419 assert!(!mt_mt_collide("*/text", "application/test"));
420 assert!(!mt_mt_collide("something/random", "something_else/random"));
421 assert!(!mt_mt_collide("something/random", "*/else"));
422 assert!(!mt_mt_collide("*/random", "*/else"));
423 assert!(!mt_mt_collide("something/*", "random/else"));
424 }
425
426 fn r_mt_mt_collide<S1, S2>(m: Method, mt1: S1, mt2: S2) -> bool
427 where S1: Into<Option<&'static str>>, S2: Into<Option<&'static str>>
428 {
429 let mut route_a = Route::new(m, "/", dummy_handler);
430 if let Some(mt_str) = mt1.into() {
431 route_a.format = Some(mt_str.parse::<MediaType>().unwrap());
432 }
433
434 let mut route_b = Route::new(m, "/", dummy_handler);
435 if let Some(mt_str) = mt2.into() {
436 route_b.format = Some(mt_str.parse::<MediaType>().unwrap());
437 }
438
439 route_a.collides_with(&route_b)
440 }
441
442 #[test]
443 fn test_route_content_type_collisions() {
444 assert!(r_mt_mt_collide(Get, "application/json", "application/json"));
446 assert!(r_mt_mt_collide(Get, "*/json", "application/json"));
447 assert!(r_mt_mt_collide(Get, "*/json", "application/*"));
448 assert!(r_mt_mt_collide(Get, "text/html", "text/*"));
449 assert!(r_mt_mt_collide(Get, "any/thing", "*/*"));
450
451 assert!(r_mt_mt_collide(Get, None, "text/*"));
452 assert!(r_mt_mt_collide(Get, None, "text/html"));
453 assert!(r_mt_mt_collide(Get, None, "*/*"));
454 assert!(r_mt_mt_collide(Get, "text/html", None));
455 assert!(r_mt_mt_collide(Get, "*/*", None));
456 assert!(r_mt_mt_collide(Get, "application/json", None));
457
458 assert!(r_mt_mt_collide(Get, "application/*", "text/*"));
459 assert!(r_mt_mt_collide(Get, "application/json", "text/*"));
460 assert!(r_mt_mt_collide(Get, "application/json", "text/html"));
461 assert!(r_mt_mt_collide(Get, "text/html", "text/html"));
462
463 assert!(r_mt_mt_collide(Post, "application/json", "application/json"));
465 assert!(r_mt_mt_collide(Post, "*/json", "application/json"));
466 assert!(r_mt_mt_collide(Post, "*/json", "application/*"));
467 assert!(r_mt_mt_collide(Post, "text/html", "text/*"));
468 assert!(r_mt_mt_collide(Post, "any/thing", "*/*"));
469
470 assert!(r_mt_mt_collide(Post, None, "text/*"));
471 assert!(r_mt_mt_collide(Post, None, "text/html"));
472 assert!(r_mt_mt_collide(Post, None, "*/*"));
473 assert!(r_mt_mt_collide(Post, "text/html", None));
474 assert!(r_mt_mt_collide(Post, "*/*", None));
475 assert!(r_mt_mt_collide(Post, "application/json", None));
476
477 assert!(!r_mt_mt_collide(Post, "text/html", "application/*"));
478 assert!(!r_mt_mt_collide(Post, "application/html", "text/*"));
479 assert!(!r_mt_mt_collide(Post, "*/json", "text/html"));
480 assert!(!r_mt_mt_collide(Post, "text/html", "text/css"));
481 assert!(!r_mt_mt_collide(Post, "other/html", "text/html"));
482 }
483
484 fn catchers_collide<A, B>(a: A, ap: &str, b: B, bp: &str) -> bool
485 where A: Into<Option<u16>>, B: Into<Option<u16>>
486 {
487 use crate::catcher::dummy_handler as handler;
488
489 let a = Catcher::new(a, handler).map_base(|_| ap.into()).unwrap();
490 let b = Catcher::new(b, handler).map_base(|_| bp.into()).unwrap();
491 a.collides_with(&b)
492 }
493
494 #[test]
495 fn catcher_collisions() {
496 for path in &["/a", "/foo", "/a/b/c", "/a/b/c/d/e"] {
497 assert!(catchers_collide(404, path, 404, path));
498 assert!(catchers_collide(500, path, 500, path));
499 assert!(catchers_collide(None, path, None, path));
500 }
501 }
502
503 #[test]
504 fn catcher_non_collisions() {
505 assert!(!catchers_collide(404, "/foo", 405, "/foo"));
506 assert!(!catchers_collide(404, "/", None, "/foo"));
507 assert!(!catchers_collide(404, "/", None, "/"));
508 assert!(!catchers_collide(404, "/a/b", None, "/a/b"));
509 assert!(!catchers_collide(404, "/a/b", 404, "/a/b/c"));
510
511 assert!(!catchers_collide(None, "/a/b", None, "/a/b/c"));
512 assert!(!catchers_collide(None, "/b", None, "/a/b/c"));
513 assert!(!catchers_collide(None, "/", None, "/a/b/c"));
514 }
515}