mod collider;
mod route;
use std::collections::hash_map::HashMap;
use self::collider::Collider;
pub use self::route::Route;
use request::Request;
use http::Method;
type Selector = Method;
#[derive(Default)]
pub struct Router {
routes: HashMap<Selector, Vec<Route>>, }
impl Router {
pub fn new() -> Router {
Router { routes: HashMap::new() }
}
pub fn add(&mut self, route: Route) {
let selector = route.method;
let entries = self.routes.entry(selector).or_insert_with(|| vec![]);
entries.push(route);
entries.sort_by(|a, b| a.rank.cmp(&b.rank));
}
pub fn route<'b>(&'b self, req: &Request) -> Vec<&'b Route> {
let matches = self.routes.get(&req.method()).map_or(vec![], |routes| {
routes.iter()
.filter(|r| r.collides_with(req))
.collect()
});
trace_!("Routing the request: {}", req);
trace_!("All matches: {:?}", matches);
matches
}
pub fn has_collisions(&self) -> bool {
let mut result = false;
for routes in self.routes.values() {
for (i, a_route) in routes.iter().enumerate() {
for b_route in routes.iter().skip(i + 1) {
if a_route.collides_with(b_route) {
result = true;
warn!("{} and {} collide!", a_route, b_route);
}
}
}
}
result
}
#[inline]
pub fn routes<'a>(&'a self) -> impl Iterator<Item=&'a Route> + 'a {
self.routes.values().flat_map(|v| v.iter())
}
}
#[cfg(test)]
mod test {
use super::{Router, Route};
use http::Method;
use http::Method::*;
use http::uri::URI;
use request::Request;
use data::Data;
use handler::Outcome;
fn dummy_handler(_req: &Request, _: Data) -> Outcome<'static> {
Outcome::of("hi")
}
fn router_with_routes(routes: &[&'static str]) -> Router {
let mut router = Router::new();
for route in routes {
let route = Route::new(Get, route.to_string(), dummy_handler);
router.add(route);
}
router
}
fn router_with_ranked_routes(routes: &[(isize, &'static str)]) -> Router {
let mut router = Router::new();
for &(rank, route) in routes {
let route = Route::ranked(rank, Get, route.to_string(), dummy_handler);
router.add(route);
}
router
}
fn router_with_unranked_routes(routes: &[&'static str]) -> Router {
let mut router = Router::new();
for route in routes {
let route = Route::ranked(0, Get, route.to_string(), dummy_handler);
router.add(route);
}
router
}
fn unranked_route_collisions(routes: &[&'static str]) -> bool {
let router = router_with_unranked_routes(routes);
router.has_collisions()
}
fn default_rank_route_collisions(routes: &[&'static str]) -> bool {
let router = router_with_routes(routes);
router.has_collisions()
}
#[test]
fn test_collisions() {
assert!(unranked_route_collisions(&["/hello", "/hello"]));
assert!(unranked_route_collisions(&["/<a>", "/hello"]));
assert!(unranked_route_collisions(&["/<a>", "/<b>"]));
assert!(unranked_route_collisions(&["/hello/bob", "/hello/<b>"]));
assert!(unranked_route_collisions(&["/a/b/<c>/d", "/<a>/<b>/c/d"]));
assert!(unranked_route_collisions(&["/a/b", "/<a..>"]));
assert!(unranked_route_collisions(&["/a/b/c", "/a/<a..>"]));
assert!(unranked_route_collisions(&["/<a>/b", "/a/<a..>"]));
assert!(unranked_route_collisions(&["/a/<b>", "/a/<a..>"]));
assert!(unranked_route_collisions(&["/a/b/<c>", "/a/<a..>"]));
assert!(unranked_route_collisions(&["<a..>", "/a/<a..>"]));
assert!(unranked_route_collisions(&["/a/<a..>", "/a/<a..>"]));
assert!(unranked_route_collisions(&["/a/b/<a..>", "/a/<a..>"]));
assert!(unranked_route_collisions(&["/a/b/c/d", "/a/<a..>"]));
}
#[test]
fn test_no_collisions() {
assert!(!unranked_route_collisions(&["/<a>", "/a/<a..>"]));
assert!(!unranked_route_collisions(&["/a/b", "/a/b/c"]));
assert!(!unranked_route_collisions(&["/a/b/c/d", "/a/b/c/<d>/e"]));
assert!(!unranked_route_collisions(&["/a/d/<b..>", "/a/b/c"]));
assert!(!unranked_route_collisions(&["/a/d/<b..>", "/a/d"]));
}
#[test]
fn test_no_collision_when_ranked() {
assert!(!default_rank_route_collisions(&["/<a>", "/hello"]));
assert!(!default_rank_route_collisions(&["/hello/bob", "/hello/<b>"]));
assert!(!default_rank_route_collisions(&["/a/b/c/d", "/<a>/<b>/c/d"]));
assert!(!default_rank_route_collisions(&["/hi", "/<hi>"]));
assert!(!default_rank_route_collisions(&["/hi", "/<hi>"]));
assert!(!default_rank_route_collisions(&["/a/b", "/a/b/<c..>"]));
}
fn route<'a>(router: &'a Router, method: Method, uri: &str) -> Option<&'a Route> {
let request = Request::new(method, URI::new(uri));
let matches = router.route(&request);
if matches.len() > 0 {
Some(matches[0])
} else {
None
}
}
fn matches<'a>(router: &'a Router, method: Method, uri: &str) -> Vec<&'a Route> {
let request = Request::new(method, URI::new(uri));
router.route(&request)
}
#[test]
fn test_ok_routing() {
let router = router_with_routes(&["/hello"]);
assert!(route(&router, Get, "/hello").is_some());
let router = router_with_routes(&["/<a>"]);
assert!(route(&router, Get, "/hello").is_some());
assert!(route(&router, Get, "/hi").is_some());
assert!(route(&router, Get, "/bobbbbbbbbbby").is_some());
assert!(route(&router, Get, "/dsfhjasdf").is_some());
let router = router_with_routes(&["/<a>/<b>"]);
assert!(route(&router, Get, "/hello/hi").is_some());
assert!(route(&router, Get, "/a/b/").is_some());
assert!(route(&router, Get, "/i/a").is_some());
assert!(route(&router, Get, "/jdlk/asdij").is_some());
let mut router = Router::new();
router.add(Route::new(Put, "/hello".to_string(), dummy_handler));
router.add(Route::new(Post, "/hello".to_string(), dummy_handler));
router.add(Route::new(Delete, "/hello".to_string(), dummy_handler));
assert!(route(&router, Put, "/hello").is_some());
assert!(route(&router, Post, "/hello").is_some());
assert!(route(&router, Delete, "/hello").is_some());
let router = router_with_routes(&["/<a..>"]);
assert!(route(&router, Get, "/hello/hi").is_some());
assert!(route(&router, Get, "/a/b/").is_some());
assert!(route(&router, Get, "/i/a").is_some());
assert!(route(&router, Get, "/a/b/c/d/e/f").is_some());
}
#[test]
fn test_err_routing() {
let router = router_with_routes(&["/hello"]);
assert!(route(&router, Put, "/hello").is_none());
assert!(route(&router, Post, "/hello").is_none());
assert!(route(&router, Options, "/hello").is_none());
assert!(route(&router, Get, "/hell").is_none());
assert!(route(&router, Get, "/hi").is_none());
assert!(route(&router, Get, "/hello/there").is_none());
assert!(route(&router, Get, "/hello/i").is_none());
assert!(route(&router, Get, "/hillo").is_none());
let router = router_with_routes(&["/<a>"]);
assert!(route(&router, Put, "/hello").is_none());
assert!(route(&router, Post, "/hello").is_none());
assert!(route(&router, Options, "/hello").is_none());
assert!(route(&router, Get, "/hello/there").is_none());
assert!(route(&router, Get, "/hello/i").is_none());
let router = router_with_routes(&["/<a>/<b>"]);
assert!(route(&router, Get, "/a/b/c").is_none());
assert!(route(&router, Get, "/a").is_none());
assert!(route(&router, Get, "/a/").is_none());
assert!(route(&router, Get, "/a/b/c/d").is_none());
assert!(route(&router, Put, "/hello/hi").is_none());
assert!(route(&router, Put, "/a/b").is_none());
assert!(route(&router, Put, "/a/b").is_none());
}
macro_rules! assert_ranked_routes {
($routes:expr, $to:expr, $want:expr) => ({
let router = router_with_routes($routes);
let route_path = route(&router, Get, $to).unwrap().path.as_str();
assert_eq!(route_path as &str, $want as &str);
})
}
#[test]
fn test_default_ranking() {
assert_ranked_routes!(&["/hello", "/<name>"], "/hello", "/hello");
assert_ranked_routes!(&["/<name>", "/hello"], "/hello", "/hello");
assert_ranked_routes!(&["/<a>", "/hi", "/<b>"], "/hi", "/hi");
assert_ranked_routes!(&["/<a>/b", "/hi/c"], "/hi/c", "/hi/c");
assert_ranked_routes!(&["/<a>/<b>", "/hi/a"], "/hi/c", "/<a>/<b>");
assert_ranked_routes!(&["/hi/a", "/hi/<c>"], "/hi/c", "/hi/<c>");
}
fn ranked_collisions(routes: &[(isize, &'static str)]) -> bool {
let router = router_with_ranked_routes(routes);
router.has_collisions()
}
#[test]
fn test_no_manual_ranked_collisions() {
assert!(!ranked_collisions(&[(1, "a/<b>"), (2, "a/<b>")]));
assert!(!ranked_collisions(&[(0, "a/<b>"), (2, "a/<b>")]));
assert!(!ranked_collisions(&[(5, "a/<b>"), (2, "a/<b>")]));
assert!(!ranked_collisions(&[(1, "a/<b>"), (1, "b/<b>")]));
assert!(!ranked_collisions(&[(1, "a/<b..>"), (2, "a/<b..>")]));
assert!(!ranked_collisions(&[(0, "a/<b..>"), (2, "a/<b..>")]));
assert!(!ranked_collisions(&[(5, "a/<b..>"), (2, "a/<b..>")]));
assert!(!ranked_collisions(&[(1, "<a..>"), (2, "<a..>")]));
}
#[test]
fn test_ranked_collisions() {
assert!(ranked_collisions(&[(2, "a/<b..>"), (2, "a/<b..>")]));
assert!(ranked_collisions(&[(2, "a/c/<b..>"), (2, "a/<b..>")]));
assert!(ranked_collisions(&[(2, "<b..>"), (2, "a/<b..>")]));
}
macro_rules! assert_ranked_routing {
(to: $to:expr, with: $routes:expr, expect: $($want:expr),+) => ({
let router = router_with_ranked_routes(&$routes);
let routed_to = matches(&router, Get, $to);
let expected = &[$($want),+];
assert!(routed_to.len() == expected.len());
for (got, expected) in routed_to.iter().zip(expected.iter()) {
assert_eq!(got.rank, expected.0);
assert_eq!(got.path.as_str() as &str, expected.1);
}
})
}
#[test]
fn test_ranked_routing() {
assert_ranked_routing!(
to: "a/b",
with: [(1, "a/<b>"), (2, "a/<b>")],
expect: (1, "a/<b>"), (2, "a/<b>")
);
assert_ranked_routing!(
to: "b/b",
with: [(1, "a/<b>"), (2, "b/<b>"), (3, "b/b")],
expect: (2, "b/<b>"), (3, "b/b")
);
assert_ranked_routing!(
to: "b/b",
with: [(2, "b/<b>"), (1, "a/<b>"), (3, "b/b")],
expect: (2, "b/<b>"), (3, "b/b")
);
assert_ranked_routing!(
to: "b/b",
with: [(3, "b/b"), (2, "b/<b>"), (1, "a/<b>")],
expect: (2, "b/<b>"), (3, "b/b")
);
assert_ranked_routing!(
to: "b/b",
with: [(1, "a/<b>"), (2, "b/<b>"), (0, "b/b")],
expect: (0, "b/b"), (2, "b/<b>")
);
assert_ranked_routing!(
to: "/profile/sergio/edit",
with: [(1, "/<a>/<b>/edit"), (2, "/profile/<d>"), (0, "/<a>/<b>/<c>")],
expect: (0, "/<a>/<b>/<c>"), (1, "/<a>/<b>/edit")
);
assert_ranked_routing!(
to: "/profile/sergio/edit",
with: [(0, "/<a>/<b>/edit"), (2, "/profile/<d>"), (5, "/<a>/<b>/<c>")],
expect: (0, "/<a>/<b>/edit"), (5, "/<a>/<b>/<c>")
);
assert_ranked_routing!(
to: "/a/b",
with: [(0, "/a/b"), (1, "/a/<b..>")],
expect: (0, "/a/b"), (1, "/a/<b..>")
);
assert_ranked_routing!(
to: "/a/b/c/d/e/f",
with: [(1, "/a/<b..>"), (2, "/a/b/<c..>")],
expect: (1, "/a/<b..>"), (2, "/a/b/<c..>")
);
}
fn match_params(router: &Router, path: &str, expected: &[&str]) -> bool {
println!("Testing: {} (expect: {:?})", path, expected);
route(router, Get, path).map_or(false, |route| {
let params = route.get_param_indexes(&URI::new(path));
if params.len() != expected.len() {
return false;
}
for (k, (i, j)) in params.into_iter().enumerate() {
if &path[i..j] != expected[k] {
return false;
}
}
true
})
}
#[test]
fn test_params() {
let router = router_with_routes(&["/<a>"]);
assert!(match_params(&router, "/hello", &["hello"]));
assert!(match_params(&router, "/hi", &["hi"]));
assert!(match_params(&router, "/bob", &["bob"]));
assert!(match_params(&router, "/i", &["i"]));
let router = router_with_routes(&["/hello"]);
assert!(match_params(&router, "/hello", &[]));
let router = router_with_routes(&["/<a>/<b>"]);
assert!(match_params(&router, "/a/b", &["a", "b"]));
assert!(match_params(&router, "/912/sas", &["912", "sas"]));
let router = router_with_routes(&["/hello/<b>"]);
assert!(match_params(&router, "/hello/b", &["b"]));
assert!(match_params(&router, "/hello/sergio", &["sergio"]));
let router = router_with_routes(&["/hello/<b>/age"]);
assert!(match_params(&router, "/hello/sergio/age", &["sergio"]));
assert!(match_params(&router, "/hello/you/age", &["you"]));
}
}