rocket/router/
collider.rs

1use crate::catcher::Catcher;
2use crate::route::{Route, Color};
3
4use crate::http::{MediaType, Status};
5use crate::request::Request;
6
7pub trait Collide<T = Self> {
8    fn collides_with(&self, other: &T) -> bool;
9}
10
11impl<'a, 'b, T: Collide> Collide<&T> for &T {
12    fn collides_with(&self, other: &&T) -> bool {
13        T::collides_with(*self, *other)
14    }
15}
16
17impl Collide for MediaType {
18    fn collides_with(&self, other: &Self) -> bool {
19        let collide = |a, b| a == "*" || b == "*" || a == b;
20        collide(self.top(), other.top()) && collide(self.sub(), other.sub())
21    }
22}
23
24fn paths_collide(route: &Route, other: &Route) -> bool {
25    let a_segments = &route.uri.metadata.path_segs;
26    let b_segments = &other.uri.metadata.path_segs;
27    for (seg_a, seg_b) in a_segments.iter().zip(b_segments.iter()) {
28        if seg_a.trailing || seg_b.trailing {
29            return true;
30        }
31
32        if seg_a.dynamic || seg_b.dynamic {
33            continue;
34        }
35
36        if seg_a.value != seg_b.value {
37            return false;
38        }
39    }
40
41    a_segments.get(b_segments.len()).map_or(false, |s| s.trailing)
42        || b_segments.get(a_segments.len()).map_or(false, |s| s.trailing)
43        || a_segments.len() == b_segments.len()
44}
45
46fn formats_collide(route: &Route, other: &Route) -> bool {
47    // When matching against the `Accept` header, the client can always provide
48    // a media type that will cause a collision through non-specificity, i.e,
49    // `*/*` matches everything.
50    if !route.method.supports_payload() {
51        return true;
52    }
53
54    // When matching against the `Content-Type` header, we'll only consider
55    // requests as having a `Content-Type` if they're fully specified. If a
56    // route doesn't have a `format`, it accepts all `Content-Type`s. If a
57    // request doesn't have a format, it only matches routes without a format.
58    match (route.format.as_ref(), other.format.as_ref()) {
59        (Some(a), Some(b)) => a.collides_with(b),
60        _ => true
61    }
62}
63
64impl Collide for Route {
65    /// Determines if two routes can match against some request. That is, if two
66    /// routes `collide`, there exists a request that can match against both
67    /// routes.
68    ///
69    /// This implementation is used at initialization to check if two user
70    /// routes collide before launching. Format collisions works like this:
71    ///
72    ///   * If route specifies a format, it only gets requests for that format.
73    ///   * If route doesn't specify a format, it gets requests for any format.
74    ///
75    /// Because query parsing is lenient, and dynamic query parameters can be
76    /// missing, queries do not impact whether two routes collide.
77    fn collides_with(&self, other: &Route) -> bool {
78        self.method == other.method
79            && self.rank == other.rank
80            && paths_collide(self, other)
81            && formats_collide(self, other)
82    }
83}
84
85impl Route {
86    /// Determines if this route matches against the given request.
87    ///
88    /// This means that:
89    ///
90    ///   * The route's method matches that of the incoming request.
91    ///   * The route's format (if any) matches that of the incoming request.
92    ///     - If route specifies format, it only gets requests for that format.
93    ///     - If route doesn't specify format, it gets requests for any format.
94    ///   * All static components in the route's path match the corresponding
95    ///     components in the same position in the incoming request.
96    ///   * All static components in the route's query string are also in the
97    ///     request query string, though in any position. If there is no query
98    ///     in the route, requests with/without queries match.
99    pub(crate) fn matches(&self, req: &Request<'_>) -> bool {
100        self.method == req.method()
101            && paths_match(self, req)
102            && queries_match(self, req)
103            && formats_match(self, req)
104    }
105}
106
107fn paths_match(route: &Route, req: &Request<'_>) -> bool {
108    let route_segments = &route.uri.metadata.path_segs;
109    let req_segments = req.uri().path().segments();
110
111    if route.uri.metadata.trailing_path {
112        // The last route segment can be trailing, which is allowed to be empty.
113        // So we can have one more segment in `route` than in `req` and match.
114        // ok if: req_segments.len() >= route_segments.len() - 1
115        if req_segments.len() + 1 < route_segments.len() {
116            return false;
117        }
118    } else if route_segments.len() != req_segments.len() {
119        return false;
120    }
121
122    if route.uri.metadata.path_color == Color::Wild {
123        return true;
124    }
125
126    for (route_seg, req_seg) in route_segments.iter().zip(req_segments) {
127        if route_seg.trailing {
128            return true;
129        }
130
131        if !(route_seg.dynamic || route_seg.value == req_seg) {
132            return false;
133        }
134    }
135
136    true
137}
138
139fn queries_match(route: &Route, req: &Request<'_>) -> bool {
140    if matches!(route.uri.metadata.query_color, None | Some(Color::Wild)) {
141        return true;
142    }
143
144    let route_query_fields = route.uri.metadata.static_query_fields.iter()
145        .map(|(k, v)| (k.as_str(), v.as_str()));
146
147    for route_seg in route_query_fields {
148        if let Some(query) = req.uri().query() {
149            if !query.segments().any(|req_seg| req_seg == route_seg) {
150                trace_!("request {} missing static query {:?}", req, route_seg);
151                return false;
152            }
153        } else {
154            trace_!("query-less request {} missing static query {:?}", req, route_seg);
155            return false;
156        }
157    }
158
159    true
160}
161
162fn formats_match(route: &Route, request: &Request<'_>) -> bool {
163    if !route.method.supports_payload() {
164        route.format.as_ref()
165            .and_then(|a| request.format().map(|b| (a, b)))
166            .map(|(a, b)| a.collides_with(b))
167            .unwrap_or(true)
168    } else {
169        match route.format.as_ref() {
170            Some(a) => match request.format() {
171                Some(b) if b.specificity() == 2 => a.collides_with(b),
172                _ => false
173            }
174            None => true
175        }
176    }
177}
178
179
180impl Collide for Catcher {
181    /// Determines if two catchers are in conflict: there exists a request for
182    /// which there exist no rule to determine _which_ of the two catchers to
183    /// use. This means that the catchers:
184    ///
185    ///  * Have the same base.
186    ///  * Have the same status code or are both defaults.
187    fn collides_with(&self, other: &Self) -> bool {
188        self.code == other.code
189            && self.base.path().segments().eq(other.base.path().segments())
190    }
191}
192
193impl Catcher {
194    /// Determines if this catcher is responsible for handling the error with
195    /// `status` that occurred during request `req`. A catcher matches if:
196    ///
197    ///  * It is a default catcher _or_ has a code of `status`.
198    ///  * Its base is a prefix of the normalized/decoded `req.path()`.
199    pub(crate) fn matches(&self, status: Status, req: &Request<'_>) -> bool {
200        self.code.map_or(true, |code| code == status.code)
201            && self.base.path().segments().prefix_of(req.uri().path().segments())
202    }
203}
204
205#[cfg(test)]
206mod tests {
207    use std::str::FromStr;
208
209    use super::*;
210    use crate::route::{Route, dummy_handler};
211    use crate::local::blocking::Client;
212    use crate::http::{Method, Method::*, MediaType, ContentType, Accept};
213    use crate::http::uri::Origin;
214
215    type SimpleRoute = (Method, &'static str);
216
217    fn m_collide(a: SimpleRoute, b: SimpleRoute) -> bool {
218        let route_a = Route::new(a.0, a.1, dummy_handler);
219        route_a.collides_with(&Route::new(b.0, b.1, dummy_handler))
220    }
221
222    fn unranked_collide(a: &'static str, b: &'static str) -> bool {
223        let route_a = Route::ranked(0, Get, a, dummy_handler);
224        let route_b = Route::ranked(0, Get, b, dummy_handler);
225        eprintln!("Checking {} against {}.", route_a, route_b);
226        route_a.collides_with(&route_b)
227    }
228
229    fn s_s_collide(a: &'static str, b: &'static str) -> bool {
230        let a = Route::new(Get, a, dummy_handler);
231        let b = Route::new(Get, b, dummy_handler);
232        paths_collide(&a, &b)
233    }
234
235    #[test]
236    fn simple_collisions() {
237        assert!(unranked_collide("/a", "/a"));
238        assert!(unranked_collide("/hello", "/hello"));
239        assert!(unranked_collide("/hello", "/hello/"));
240        assert!(unranked_collide("/hello/there/how/ar", "/hello/there/how/ar"));
241        assert!(unranked_collide("/hello/there", "/hello/there/"));
242    }
243
244    #[test]
245    fn simple_param_collisions() {
246        assert!(unranked_collide("/<a>", "/<b>"));
247        assert!(unranked_collide("/<a>", "/b"));
248        assert!(unranked_collide("/hello/<name>", "/hello/<person>"));
249        assert!(unranked_collide("/hello/<name>/hi", "/hello/<person>/hi"));
250        assert!(unranked_collide("/hello/<name>/hi/there", "/hello/<person>/hi/there"));
251        assert!(unranked_collide("/<name>/hi/there", "/<person>/hi/there"));
252        assert!(unranked_collide("/<name>/hi/there", "/dude/<name>/there"));
253        assert!(unranked_collide("/<name>/<a>/<b>", "/<a>/<b>/<c>"));
254        assert!(unranked_collide("/<name>/<a>/<b>/", "/<a>/<b>/<c>/"));
255        assert!(unranked_collide("/<a..>", "/hi"));
256        assert!(unranked_collide("/<a..>", "/hi/hey"));
257        assert!(unranked_collide("/<a..>", "/hi/hey/hayo"));
258        assert!(unranked_collide("/a/<a..>", "/a/hi/hey/hayo"));
259        assert!(unranked_collide("/a/<b>/<a..>", "/a/hi/hey/hayo"));
260        assert!(unranked_collide("/a/<b>/<c>/<a..>", "/a/hi/hey/hayo"));
261        assert!(unranked_collide("/<b>/<c>/<a..>", "/a/hi/hey/hayo"));
262        assert!(unranked_collide("/<b>/<c>/hey/hayo", "/a/hi/hey/hayo"));
263        assert!(unranked_collide("/<a..>", "/foo"));
264    }
265
266    #[test]
267    fn medium_param_collisions() {
268        assert!(unranked_collide("/<a>", "/b"));
269        assert!(unranked_collide("/hello/<name>", "/hello/bob"));
270        assert!(unranked_collide("/<name>", "//bob"));
271    }
272
273    #[test]
274    fn hard_param_collisions() {
275        assert!(unranked_collide("/<a..>", "///a///"));
276        assert!(unranked_collide("/<a..>", "//a/bcjdklfj//<c>"));
277        assert!(unranked_collide("/a/<a..>", "//a/bcjdklfj//<c>"));
278        assert!(unranked_collide("/a/<b>/<c..>", "//a/bcjdklfj//<c>"));
279        assert!(unranked_collide("/<a..>", "/"));
280        assert!(unranked_collide("/", "/<_..>"));
281        assert!(unranked_collide("/a/b/<a..>", "/a/<b..>"));
282        assert!(unranked_collide("/a/b/<a..>", "/a/<b>/<b..>"));
283        assert!(unranked_collide("/hi/<a..>", "/hi"));
284        assert!(unranked_collide("/hi/<a..>", "/hi/"));
285        assert!(unranked_collide("/<a..>", "//////"));
286    }
287
288    #[test]
289    fn query_collisions() {
290        assert!(unranked_collide("/?<a>", "/?<a>"));
291        assert!(unranked_collide("/a/?<a>", "/a/?<a>"));
292        assert!(unranked_collide("/a?<a>", "/a?<a>"));
293        assert!(unranked_collide("/<r>?<a>", "/<r>?<a>"));
294        assert!(unranked_collide("/a/b/c?<a>", "/a/b/c?<a>"));
295        assert!(unranked_collide("/<a>/b/c?<d>", "/a/b/<c>?<d>"));
296        assert!(unranked_collide("/?<a>", "/"));
297        assert!(unranked_collide("/a?<a>", "/a"));
298        assert!(unranked_collide("/a?<a>", "/a"));
299        assert!(unranked_collide("/a/b?<a>", "/a/b"));
300        assert!(unranked_collide("/a/b", "/a/b?<c>"));
301    }
302
303    #[test]
304    fn non_collisions() {
305        assert!(!unranked_collide("/<a>", "/"));
306        assert!(!unranked_collide("/a", "/b"));
307        assert!(!unranked_collide("/a/b", "/a"));
308        assert!(!unranked_collide("/a/b", "/a/c"));
309        assert!(!unranked_collide("/a/hello", "/a/c"));
310        assert!(!unranked_collide("/hello", "/a/c"));
311        assert!(!unranked_collide("/hello/there", "/hello/there/guy"));
312        assert!(!unranked_collide("/a/<b>", "/b/<b>"));
313        assert!(!unranked_collide("/t", "/test"));
314        assert!(!unranked_collide("/a", "/aa"));
315        assert!(!unranked_collide("/a", "/aaa"));
316        assert!(!unranked_collide("/", "/a"));
317    }
318
319    #[test]
320    fn query_non_collisions() {
321        assert!(!unranked_collide("/a?<b>", "/b"));
322        assert!(!unranked_collide("/a/b", "/a?<b>"));
323        assert!(!unranked_collide("/a/b/c?<d>", "/a/b/c/d"));
324        assert!(!unranked_collide("/a/hello", "/a/?<hello>"));
325        assert!(!unranked_collide("/?<a>", "/hi"));
326    }
327
328    #[test]
329    fn method_dependent_non_collisions() {
330        assert!(!m_collide((Get, "/"), (Post, "/")));
331        assert!(!m_collide((Post, "/"), (Put, "/")));
332        assert!(!m_collide((Put, "/a"), (Put, "/")));
333        assert!(!m_collide((Post, "/a"), (Put, "/")));
334        assert!(!m_collide((Get, "/a"), (Put, "/")));
335        assert!(!m_collide((Get, "/hello"), (Put, "/hello")));
336        assert!(!m_collide((Get, "/<foo..>"), (Post, "/")));
337    }
338
339    #[test]
340    fn query_dependent_non_collisions() {
341        assert!(!m_collide((Get, "/"), (Get, "/?a")));
342        assert!(!m_collide((Get, "/"), (Get, "/?<a>")));
343        assert!(!m_collide((Get, "/a/<b>"), (Get, "/a/<b>?d")));
344    }
345
346    #[test]
347    fn test_str_non_collisions() {
348        assert!(!s_s_collide("/a", "/b"));
349        assert!(!s_s_collide("/a/b", "/a"));
350        assert!(!s_s_collide("/a/b", "/a/c"));
351        assert!(!s_s_collide("/a/hello", "/a/c"));
352        assert!(!s_s_collide("/hello", "/a/c"));
353        assert!(!s_s_collide("/hello/there", "/hello/there/guy"));
354        assert!(!s_s_collide("/a/<b>", "/b/<b>"));
355        assert!(!s_s_collide("/a", "/b"));
356        assert!(!s_s_collide("/a/b", "/a"));
357        assert!(!s_s_collide("/a/b", "/a/c"));
358        assert!(!s_s_collide("/a/hello", "/a/c"));
359        assert!(!s_s_collide("/hello", "/a/c"));
360        assert!(!s_s_collide("/hello/there", "/hello/there/guy"));
361        assert!(!s_s_collide("/a/<b>", "/b/<b>"));
362        assert!(!s_s_collide("/a", "/b"));
363        assert!(!s_s_collide("/a/b", "/a"));
364        assert!(!s_s_collide("/a/b", "/a/c"));
365        assert!(!s_s_collide("/a/hello", "/a/c"));
366        assert!(!s_s_collide("/hello", "/a/c"));
367        assert!(!s_s_collide("/hello/there", "/hello/there/guy"));
368        assert!(!s_s_collide("/a/<b>", "/b/<b>"));
369        assert!(!s_s_collide("/t", "/test"));
370        assert!(!s_s_collide("/a", "/aa"));
371        assert!(!s_s_collide("/a", "/aaa"));
372        assert!(!s_s_collide("/", "/a"));
373
374        assert!(s_s_collide("/a/hi/<a..>", "/a/hi/"));
375        assert!(s_s_collide("/hi/<a..>", "/hi/"));
376        assert!(s_s_collide("/<a..>", "/"));
377    }
378
379    fn mt_mt_collide(mt1: &str, mt2: &str) -> bool {
380        let mt_a = MediaType::from_str(mt1).expect(mt1);
381        let mt_b = MediaType::from_str(mt2).expect(mt2);
382        mt_a.collides_with(&mt_b)
383    }
384
385    #[test]
386    fn test_content_type_collisions() {
387        assert!(mt_mt_collide("application/json", "application/json"));
388        assert!(mt_mt_collide("*/json", "application/json"));
389        assert!(mt_mt_collide("*/*", "application/json"));
390        assert!(mt_mt_collide("application/*", "application/json"));
391        assert!(mt_mt_collide("application/*", "*/json"));
392        assert!(mt_mt_collide("something/random", "something/random"));
393
394        assert!(!mt_mt_collide("text/*", "application/*"));
395        assert!(!mt_mt_collide("*/text", "*/json"));
396        assert!(!mt_mt_collide("*/text", "application/test"));
397        assert!(!mt_mt_collide("something/random", "something_else/random"));
398        assert!(!mt_mt_collide("something/random", "*/else"));
399        assert!(!mt_mt_collide("*/random", "*/else"));
400        assert!(!mt_mt_collide("something/*", "random/else"));
401    }
402
403    fn r_mt_mt_collide<S1, S2>(m: Method, mt1: S1, mt2: S2) -> bool
404        where S1: Into<Option<&'static str>>, S2: Into<Option<&'static str>>
405    {
406        let mut route_a = Route::new(m, "/", dummy_handler);
407        if let Some(mt_str) = mt1.into() {
408            route_a.format = Some(mt_str.parse::<MediaType>().unwrap());
409        }
410
411        let mut route_b = Route::new(m, "/", dummy_handler);
412        if let Some(mt_str) = mt2.into() {
413            route_b.format = Some(mt_str.parse::<MediaType>().unwrap());
414        }
415
416        route_a.collides_with(&route_b)
417    }
418
419    #[test]
420    fn test_route_content_type_collisions() {
421        // non-payload bearing routes always collide
422        assert!(r_mt_mt_collide(Get, "application/json", "application/json"));
423        assert!(r_mt_mt_collide(Get, "*/json", "application/json"));
424        assert!(r_mt_mt_collide(Get, "*/json", "application/*"));
425        assert!(r_mt_mt_collide(Get, "text/html", "text/*"));
426        assert!(r_mt_mt_collide(Get, "any/thing", "*/*"));
427
428        assert!(r_mt_mt_collide(Get, None, "text/*"));
429        assert!(r_mt_mt_collide(Get, None, "text/html"));
430        assert!(r_mt_mt_collide(Get, None, "*/*"));
431        assert!(r_mt_mt_collide(Get, "text/html", None));
432        assert!(r_mt_mt_collide(Get, "*/*", None));
433        assert!(r_mt_mt_collide(Get, "application/json", None));
434
435        assert!(r_mt_mt_collide(Get, "application/*", "text/*"));
436        assert!(r_mt_mt_collide(Get, "application/json", "text/*"));
437        assert!(r_mt_mt_collide(Get, "application/json", "text/html"));
438        assert!(r_mt_mt_collide(Get, "text/html", "text/html"));
439
440        // payload bearing routes collide if the media types collide
441        assert!(r_mt_mt_collide(Post, "application/json", "application/json"));
442        assert!(r_mt_mt_collide(Post, "*/json", "application/json"));
443        assert!(r_mt_mt_collide(Post, "*/json", "application/*"));
444        assert!(r_mt_mt_collide(Post, "text/html", "text/*"));
445        assert!(r_mt_mt_collide(Post, "any/thing", "*/*"));
446
447        assert!(r_mt_mt_collide(Post, None, "text/*"));
448        assert!(r_mt_mt_collide(Post, None, "text/html"));
449        assert!(r_mt_mt_collide(Post, None, "*/*"));
450        assert!(r_mt_mt_collide(Post, "text/html", None));
451        assert!(r_mt_mt_collide(Post, "*/*", None));
452        assert!(r_mt_mt_collide(Post, "application/json", None));
453
454        assert!(!r_mt_mt_collide(Post, "text/html", "application/*"));
455        assert!(!r_mt_mt_collide(Post, "application/html", "text/*"));
456        assert!(!r_mt_mt_collide(Post, "*/json", "text/html"));
457        assert!(!r_mt_mt_collide(Post, "text/html", "text/css"));
458        assert!(!r_mt_mt_collide(Post, "other/html", "text/html"));
459    }
460
461    fn req_route_mt_collide<S1, S2>(m: Method, mt1: S1, mt2: S2) -> bool
462        where S1: Into<Option<&'static str>>, S2: Into<Option<&'static str>>
463    {
464        let client = Client::debug_with(vec![]).expect("client");
465        let mut req = client.req(m, "/");
466        if let Some(mt_str) = mt1.into() {
467            if m.supports_payload() {
468                req.replace_header(mt_str.parse::<ContentType>().unwrap());
469            } else {
470                req.replace_header(mt_str.parse::<Accept>().unwrap());
471            }
472        }
473
474        let mut route = Route::new(m, "/", dummy_handler);
475        if let Some(mt_str) = mt2.into() {
476            route.format = Some(mt_str.parse::<MediaType>().unwrap());
477        }
478
479        route.matches(&req)
480    }
481
482    #[test]
483    fn test_req_route_mt_collisions() {
484        assert!(req_route_mt_collide(Post, "application/json", "application/json"));
485        assert!(req_route_mt_collide(Post, "application/json", "application/*"));
486        assert!(req_route_mt_collide(Post, "application/json", "*/json"));
487        assert!(req_route_mt_collide(Post, "text/html", "*/*"));
488
489        assert!(req_route_mt_collide(Get, "application/json", "application/json"));
490        assert!(req_route_mt_collide(Get, "text/html", "text/html"));
491        assert!(req_route_mt_collide(Get, "text/html", "*/*"));
492        assert!(req_route_mt_collide(Get, None, "*/*"));
493        assert!(req_route_mt_collide(Get, None, "text/*"));
494        assert!(req_route_mt_collide(Get, None, "text/html"));
495        assert!(req_route_mt_collide(Get, None, "application/json"));
496
497        assert!(req_route_mt_collide(Post, "text/html", None));
498        assert!(req_route_mt_collide(Post, "application/json", None));
499        assert!(req_route_mt_collide(Post, "x-custom/anything", None));
500        assert!(req_route_mt_collide(Post, None, None));
501
502        assert!(req_route_mt_collide(Get, "text/html", None));
503        assert!(req_route_mt_collide(Get, "application/json", None));
504        assert!(req_route_mt_collide(Get, "x-custom/anything", None));
505        assert!(req_route_mt_collide(Get, None, None));
506        assert!(req_route_mt_collide(Get, None, "text/html"));
507        assert!(req_route_mt_collide(Get, None, "application/json"));
508
509        assert!(req_route_mt_collide(Get, "text/html, text/plain", "text/html"));
510        assert!(req_route_mt_collide(Get, "text/html; q=0.5, text/xml", "text/xml"));
511
512        assert!(!req_route_mt_collide(Post, None, "text/html"));
513        assert!(!req_route_mt_collide(Post, None, "text/*"));
514        assert!(!req_route_mt_collide(Post, None, "*/text"));
515        assert!(!req_route_mt_collide(Post, None, "*/*"));
516        assert!(!req_route_mt_collide(Post, None, "text/html"));
517        assert!(!req_route_mt_collide(Post, None, "application/json"));
518
519        assert!(!req_route_mt_collide(Post, "application/json", "text/html"));
520        assert!(!req_route_mt_collide(Post, "application/json", "text/*"));
521        assert!(!req_route_mt_collide(Post, "application/json", "*/xml"));
522        assert!(!req_route_mt_collide(Get, "application/json", "text/html"));
523        assert!(!req_route_mt_collide(Get, "application/json", "text/*"));
524        assert!(!req_route_mt_collide(Get, "application/json", "*/xml"));
525
526        assert!(!req_route_mt_collide(Post, None, "text/html"));
527        assert!(!req_route_mt_collide(Post, None, "application/json"));
528    }
529
530    fn req_route_path_match(a: &'static str, b: &'static str) -> bool {
531        let client = Client::debug_with(vec![]).expect("client");
532        let req = client.get(Origin::parse(a).expect("valid URI"));
533        let route = Route::ranked(0, Get, b, dummy_handler);
534        route.matches(&req)
535    }
536
537    #[test]
538    fn test_req_route_query_collisions() {
539        assert!(req_route_path_match("/a/b?a=b", "/a/b?<c>"));
540        assert!(req_route_path_match("/a/b?a=b", "/<a>/b?<c>"));
541        assert!(req_route_path_match("/a/b?a=b", "/<a>/<b>?<c>"));
542        assert!(req_route_path_match("/a/b?a=b", "/a/<b>?<c>"));
543        assert!(req_route_path_match("/?b=c", "/?<b>"));
544
545        assert!(req_route_path_match("/a/b?a=b", "/a/b"));
546        assert!(req_route_path_match("/a/b", "/a/b"));
547        assert!(req_route_path_match("/a/b/c/d?", "/a/b/c/d"));
548        assert!(req_route_path_match("/a/b/c/d?v=1&v=2", "/a/b/c/d"));
549
550        assert!(req_route_path_match("/a/b", "/a/b?<c>"));
551        assert!(req_route_path_match("/a/b", "/a/b?<c..>"));
552        assert!(req_route_path_match("/a/b?c", "/a/b?c"));
553        assert!(req_route_path_match("/a/b?c", "/a/b?<c>"));
554        assert!(req_route_path_match("/a/b?c=foo&d=z", "/a/b?<c>"));
555        assert!(req_route_path_match("/a/b?c=foo&d=z", "/a/b?<c..>"));
556
557        assert!(req_route_path_match("/a/b?c=foo&d=z", "/a/b?c=foo&<c..>"));
558        assert!(req_route_path_match("/a/b?c=foo&d=z", "/a/b?d=z&<c..>"));
559
560        assert!(!req_route_path_match("/a/b/c", "/a/b?<c>"));
561        assert!(!req_route_path_match("/a?b=c", "/a/b?<c>"));
562        assert!(!req_route_path_match("/?b=c", "/a/b?<c>"));
563        assert!(!req_route_path_match("/?b=c", "/a?<c>"));
564
565        assert!(!req_route_path_match("/a/b?c=foo&d=z", "/a/b?a=b&<c..>"));
566        assert!(!req_route_path_match("/a/b?c=foo&d=z", "/a/b?d=b&<c..>"));
567        assert!(!req_route_path_match("/a/b", "/a/b?c"));
568        assert!(!req_route_path_match("/a/b", "/a/b?foo"));
569        assert!(!req_route_path_match("/a/b", "/a/b?foo&<rest..>"));
570        assert!(!req_route_path_match("/a/b", "/a/b?<a>&b&<rest..>"));
571    }
572
573
574    fn catchers_collide<A, B>(a: A, ap: &str, b: B, bp: &str) -> bool
575        where A: Into<Option<u16>>, B: Into<Option<u16>>
576    {
577        use crate::catcher::dummy_handler as handler;
578
579        let a = Catcher::new(a, handler).map_base(|_| ap.into()).unwrap();
580        let b = Catcher::new(b, handler).map_base(|_| bp.into()).unwrap();
581        a.collides_with(&b)
582    }
583
584    #[test]
585    fn catcher_collisions() {
586        for path in &["/a", "/foo", "/a/b/c", "/a/b/c/d/e"] {
587            assert!(catchers_collide(404, path, 404, path));
588            assert!(catchers_collide(500, path, 500, path));
589            assert!(catchers_collide(None, path, None, path));
590        }
591    }
592
593    #[test]
594    fn catcher_non_collisions() {
595        assert!(!catchers_collide(404, "/foo", 405, "/foo"));
596        assert!(!catchers_collide(404, "/", None, "/foo"));
597        assert!(!catchers_collide(404, "/", None, "/"));
598        assert!(!catchers_collide(404, "/a/b", None, "/a/b"));
599        assert!(!catchers_collide(404, "/a/b", 404, "/a/b/c"));
600
601        assert!(!catchers_collide(None, "/a/b", None, "/a/b/c"));
602        assert!(!catchers_collide(None, "/b", None, "/a/b/c"));
603        assert!(!catchers_collide(None, "/", None, "/a/b/c"));
604    }
605}