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