rocket/router/
collider.rs

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    /// 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    /// use rocket::Route;
44    /// use rocket::http::{Method, MediaType};
45    /// # use rocket::route::dummy_handler as handler;
46    ///
47    /// // Two routes with the same method, rank, URI, and formats collide.
48    /// let a = Route::new(Method::Get, "/", handler);
49    /// let b = Route::new(Method::Get, "/", handler);
50    /// assert!(a.collides_with(&b));
51    ///
52    /// // Two routes with the same method, rank, URI, and overlapping formats.
53    /// let mut a = Route::new(Method::Post, "/", handler);
54    /// a.format = Some(MediaType::new("*", "custom"));
55    /// let mut b = Route::new(Method::Post, "/", handler);
56    /// b.format = Some(MediaType::new("text", "*"));
57    /// assert!(a.collides_with(&b));
58    ///
59    /// // Two routes with different ranks don't collide.
60    /// let a = Route::ranked(1, Method::Get, "/", handler);
61    /// let b = Route::ranked(2, Method::Get, "/", handler);
62    /// assert!(!a.collides_with(&b));
63    ///
64    /// // Two routes with different methods don't collide.
65    /// let a = Route::new(Method::Put, "/", handler);
66    /// let b = Route::new(Method::Post, "/", handler);
67    /// assert!(!a.collides_with(&b));
68    ///
69    /// // Two routes with non-overlapping URIs do not collide.
70    /// let a = Route::new(Method::Get, "/foo", handler);
71    /// let b = Route::new(Method::Get, "/bar/<baz>", handler);
72    /// assert!(!a.collides_with(&b));
73    ///
74    /// // Two payload-supporting routes with non-overlapping formats.
75    /// let mut a = Route::new(Method::Post, "/", handler);
76    /// a.format = Some(MediaType::HTML);
77    /// let mut b = Route::new(Method::Post, "/", handler);
78    /// b.format = Some(MediaType::JSON);
79    /// assert!(!a.collides_with(&b));
80    ///
81    /// // Two non payload-supporting routes with non-overlapping formats
82    /// // collide. A request with `Accept: */*` matches both.
83    /// let mut a = Route::new(Method::Get, "/", handler);
84    /// a.format = Some(MediaType::HTML);
85    /// let mut b = Route::new(Method::Get, "/", handler);
86    /// b.format = Some(MediaType::JSON);
87    /// assert!(a.collides_with(&b));
88    /// ```
89    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    /// Returns `true` if `self` collides with `other`.
99    ///
100    /// A [_collision_](Catcher#collisions) between two catchers occurs when
101    /// there exists a request and ensuing error that could
102    /// [match](Catcher::matches()) both catchers. That is, a routing ambiguity
103    /// would ensue if both catchers were made available to the router.
104    ///
105    /// Specifically, a collision occurs when two catchers:
106    ///
107    ///  * Have the same [base](Catcher::base()).
108    ///  * Have the same status [code](Catcher::code) or are both `default`.
109    ///
110    /// Collisions are symmetric: for any catchers `a` and `b`,
111    /// `a.collides_with(b) => b.collides_with(a)`.
112    ///
113    /// # Example
114    ///
115    /// ```rust
116    /// use rocket::Catcher;
117    /// # use rocket::catcher::dummy_handler as handler;
118    ///
119    /// // Two catchers with the same status code and base collide.
120    /// let a = Catcher::new(404, handler).map_base(|_| format!("/foo")).unwrap();
121    /// let b = Catcher::new(404, handler).map_base(|_| format!("/foo")).unwrap();
122    /// assert!(a.collides_with(&b));
123    ///
124    /// // Two catchers with a different base _do not_ collide.
125    /// let a = Catcher::new(404, handler);
126    /// let b = a.clone().map_base(|_| format!("/bar")).unwrap();
127    /// assert_eq!(a.base(), "/");
128    /// assert_eq!(b.base(), "/bar");
129    /// assert!(!a.collides_with(&b));
130    ///
131    /// // Two catchers with a different codes _do not_ collide.
132    /// let a = Catcher::new(404, handler);
133    /// let b = Catcher::new(500, handler);
134    /// assert_eq!(a.base(), "/");
135    /// assert_eq!(b.base(), "/");
136    /// assert!(!a.collides_with(&b));
137    ///
138    /// // A catcher _with_ a status code and one _without_ do not collide.
139    /// let a = Catcher::new(404, handler);
140    /// let b = Catcher::new(None, handler);
141    /// assert!(!a.collides_with(&b));
142    /// ```
143    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        // Payload supporting methods match against `Content-Type` which must be
204        // fully specified, so the request cannot contain a format that matches
205        // more than one route format as long as those formats don't collide.
206        (Some(true), Some(true)) => match (route.format.as_ref(), other.format.as_ref()) {
207            (Some(a), Some(b)) => a.collides_with(b),
208            // A route without a `format` accepts all `Content-Type`s.
209            _ => true
210        },
211        // When a request method may not support a payload, the `Accept` header
212        // is considered during matching. The header can always be `*/*`, which
213        // would match any format. Thus two such routes would always collide.
214        _ => 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        // non-payload bearing routes always collide
445        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        // payload bearing routes collide if the media types collide
464        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}