rocket_community/router/
collider.rs

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    /// Returns `true` if `self` collides with `other`.
12    ///
13    /// A [_collision_](Route#collisions) between two routes occurs when there
14    /// exists a request that could [match](Route::matches()) either route. That
15    /// is, a routing ambiguity would ensue if both routes were made available
16    /// to the router.
17    ///
18    /// Specifically, a collision occurs when two routes `a` and `b`:
19    ///
20    ///  * Have the same [method](Route::method).
21    ///  * Have the same [rank](Route#default-ranking).
22    ///  * The routes' methods don't support a payload _or_ the routes'
23    ///    methods support a payload and the formats overlap. Formats overlap
24    ///    when:
25    ///    - The top-level type of either is `*` or the top-level types are
26    ///      equivalent.
27    ///    - The sub-level type of either is `*` or the sub-level types are
28    ///      equivalent.
29    ///  * Have overlapping route URIs. This means that either:
30    ///    - The URIs have the same number of segments `n`, and for `i` in
31    ///      `0..n`, either `a.uri[i]` is dynamic _or_ `b.uri[i]` is dynamic
32    ///      _or_ they're both static with the same value.
33    ///    - One URI has fewer segments _and_ ends with a trailing dynamic
34    ///      parameter _and_ the preceding segments in both routes match the
35    ///      conditions above.
36    ///
37    /// Collisions are symmetric: for any routes `a` and `b`,
38    /// `a.collides_with(b) => b.collides_with(a)`.
39    ///
40    /// # Example
41    ///
42    /// ```rust
43    /// # extern crate rocket_community as rocket;
44    ///
45    /// use rocket::Route;
46    /// use rocket::http::{Method, MediaType};
47    /// # use rocket::route::dummy_handler as handler;
48    ///
49    /// // Two routes with the same method, rank, URI, and formats collide.
50    /// let a = Route::new(Method::Get, "/", handler);
51    /// let b = Route::new(Method::Get, "/", handler);
52    /// assert!(a.collides_with(&b));
53    ///
54    /// // Two routes with the same method, rank, URI, and overlapping formats.
55    /// let mut a = Route::new(Method::Post, "/", handler);
56    /// a.format = Some(MediaType::new("*", "custom"));
57    /// let mut b = Route::new(Method::Post, "/", handler);
58    /// b.format = Some(MediaType::new("text", "*"));
59    /// assert!(a.collides_with(&b));
60    ///
61    /// // Two routes with different ranks don't collide.
62    /// let a = Route::ranked(1, Method::Get, "/", handler);
63    /// let b = Route::ranked(2, Method::Get, "/", handler);
64    /// assert!(!a.collides_with(&b));
65    ///
66    /// // Two routes with different methods don't collide.
67    /// let a = Route::new(Method::Put, "/", handler);
68    /// let b = Route::new(Method::Post, "/", handler);
69    /// assert!(!a.collides_with(&b));
70    ///
71    /// // Two routes with non-overlapping URIs do not collide.
72    /// let a = Route::new(Method::Get, "/foo", handler);
73    /// let b = Route::new(Method::Get, "/bar/<baz>", handler);
74    /// assert!(!a.collides_with(&b));
75    ///
76    /// // Two payload-supporting routes with non-overlapping formats.
77    /// let mut a = Route::new(Method::Post, "/", handler);
78    /// a.format = Some(MediaType::HTML);
79    /// let mut b = Route::new(Method::Post, "/", handler);
80    /// b.format = Some(MediaType::JSON);
81    /// assert!(!a.collides_with(&b));
82    ///
83    /// // Two non payload-supporting routes with non-overlapping formats
84    /// // collide. A request with `Accept: */*` matches both.
85    /// let mut a = Route::new(Method::Get, "/", handler);
86    /// a.format = Some(MediaType::HTML);
87    /// let mut b = Route::new(Method::Get, "/", handler);
88    /// b.format = Some(MediaType::JSON);
89    /// assert!(a.collides_with(&b));
90    /// ```
91    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    /// Returns `true` if `self` collides with `other`.
101    ///
102    /// A [_collision_](Catcher#collisions) between two catchers occurs when
103    /// there exists a request and ensuing error that could
104    /// [match](Catcher::matches()) both catchers. That is, a routing ambiguity
105    /// would ensue if both catchers were made available to the router.
106    ///
107    /// Specifically, a collision occurs when two catchers:
108    ///
109    ///  * Have the same [base](Catcher::base()).
110    ///  * Have the same status [code](Catcher::code) or are both `default`.
111    ///
112    /// Collisions are symmetric: for any catchers `a` and `b`,
113    /// `a.collides_with(b) => b.collides_with(a)`.
114    ///
115    /// # Example
116    ///
117    /// ```rust
118    /// # extern crate rocket_community as rocket;
119    ///
120    /// use rocket::Catcher;
121    /// # use rocket::catcher::dummy_handler as handler;
122    ///
123    /// // Two catchers with the same status code and base collide.
124    /// let a = Catcher::new(404, handler).map_base(|_| format!("/foo")).unwrap();
125    /// let b = Catcher::new(404, handler).map_base(|_| format!("/foo")).unwrap();
126    /// assert!(a.collides_with(&b));
127    ///
128    /// // Two catchers with a different base _do not_ collide.
129    /// let a = Catcher::new(404, handler);
130    /// let b = a.clone().map_base(|_| format!("/bar")).unwrap();
131    /// assert_eq!(a.base(), "/");
132    /// assert_eq!(b.base(), "/bar");
133    /// assert!(!a.collides_with(&b));
134    ///
135    /// // Two catchers with a different codes _do not_ collide.
136    /// let a = Catcher::new(404, handler);
137    /// let b = Catcher::new(500, handler);
138    /// assert_eq!(a.base(), "/");
139    /// assert_eq!(b.base(), "/");
140    /// assert!(!a.collides_with(&b));
141    ///
142    /// // A catcher _with_ a status code and one _without_ do not collide.
143    /// let a = Catcher::new(404, handler);
144    /// let b = Catcher::new(None, handler);
145    /// assert!(!a.collides_with(&b));
146    /// ```
147    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        // Payload supporting methods match against `Content-Type` which must be
211        // fully specified, so the request cannot contain a format that matches
212        // more than one route format as long as those formats don't collide.
213        (Some(true), Some(true)) => match (route.format.as_ref(), other.format.as_ref()) {
214            (Some(a), Some(b)) => a.collides_with(b),
215            // A route without a `format` accepts all `Content-Type`s.
216            _ => true,
217        },
218        // When a request method may not support a payload, the `Accept` header
219        // is considered during matching. The header can always be `*/*`, which
220        // would match any format. Thus two such routes would always collide.
221        _ => 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        // non-payload bearing routes always collide
454        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        // payload bearing routes collide if the media types collide
473        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}