mirror of https://github.com/rwf2/Rocket.git
Added ranked routing.
This commit is contained in:
parent
69a19ccc7f
commit
416a18abf8
|
@ -1,6 +1,3 @@
|
|||
use std::path::Component;
|
||||
use std::path::Path;
|
||||
|
||||
pub trait Collider<T: ?Sized = Self> {
|
||||
fn collides_with(&self, other: &T) -> bool;
|
||||
}
|
||||
|
@ -33,53 +30,10 @@ fn do_match_until(break_c: char, a: &str, b: &str, dir: bool) -> bool {
|
|||
index_match_until(break_c, a, b, dir).is_some()
|
||||
}
|
||||
|
||||
macro_rules! comp_to_str {
|
||||
($component:expr) => (
|
||||
match $component {
|
||||
&Component::Normal(ref comp) => {
|
||||
if let Some(string) = comp.to_str() { string }
|
||||
else { return true }
|
||||
},
|
||||
_ => return true
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
impl Collider for Path {
|
||||
// TODO: It's expensive to compute the number of components: O(n) per path
|
||||
// where n == number of chars.
|
||||
//
|
||||
// Idea: Create a `CachedPath` type that caches the number of components
|
||||
// similar to the way `Route` does it.
|
||||
fn collides_with(&self, b: &Path) -> bool {
|
||||
if self.components().count() != b.components().count() {
|
||||
return false;
|
||||
}
|
||||
|
||||
let mut a_components = self.components();
|
||||
let mut b_components = b.components();
|
||||
while let Some(ref c1) = a_components.next() {
|
||||
if let Some(ref c2) = b_components.next() {
|
||||
if !c1.collides_with(c2) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Collider for Component<'a> {
|
||||
fn collides_with(&self, other: &Component<'a>) -> bool {
|
||||
let (a, b) = (comp_to_str!(self), comp_to_str!(other));
|
||||
do_match_until('<', a, b, true) && do_match_until('>', a, b, false)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Collider<str> for &'a str {
|
||||
fn collides_with(&self, other: &str) -> bool {
|
||||
Path::new(self).collides_with(Path::new(other))
|
||||
let (a, b) = (self, other);
|
||||
do_match_until('<', a, b, true) && do_match_until('>', a, b, false)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -102,9 +56,9 @@ mod tests {
|
|||
route_a.collides_with(&Route::new(b.0, b.1.to_string(), dummy_handler))
|
||||
}
|
||||
|
||||
fn collide(a: &'static str, b: &'static str) -> bool {
|
||||
let route_a = Route::new(Get, a.to_string(), dummy_handler);
|
||||
route_a.collides_with(&Route::new(Get, b.to_string(), dummy_handler))
|
||||
fn unranked_collide(a: &'static str, b: &'static str) -> bool {
|
||||
let route_a = Route::ranked(0, Get, a.to_string(), dummy_handler);
|
||||
route_a.collides_with(&Route::ranked(0, Get, b.to_string(), dummy_handler))
|
||||
}
|
||||
|
||||
fn s_r_collide(a: &'static str, b: &'static str) -> bool {
|
||||
|
@ -122,57 +76,57 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn simple_collisions() {
|
||||
assert!(collide("a", "a"));
|
||||
assert!(collide("/a", "/a"));
|
||||
assert!(collide("/hello", "/hello"));
|
||||
assert!(collide("/hello", "/hello/"));
|
||||
assert!(collide("/hello/there/how/ar", "/hello/there/how/ar"));
|
||||
assert!(collide("/hello/there", "/hello/there/"));
|
||||
assert!(unranked_collide("a", "a"));
|
||||
assert!(unranked_collide("/a", "/a"));
|
||||
assert!(unranked_collide("/hello", "/hello"));
|
||||
assert!(unranked_collide("/hello", "/hello/"));
|
||||
assert!(unranked_collide("/hello/there/how/ar", "/hello/there/how/ar"));
|
||||
assert!(unranked_collide("/hello/there", "/hello/there/"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn simple_param_collisions() {
|
||||
assert!(collide("/hello/<name>", "/hello/<person>"));
|
||||
assert!(collide("/hello/<name>/hi", "/hello/<person>/hi"));
|
||||
assert!(collide("/hello/<name>/hi/there", "/hello/<person>/hi/there"));
|
||||
assert!(collide("/<name>/hi/there", "/<person>/hi/there"));
|
||||
assert!(collide("/<name>/hi/there", "/dude/<name>/there"));
|
||||
assert!(collide("/<name>/<a>/<b>", "/<a>/<b>/<c>"));
|
||||
assert!(collide("/<name>/<a>/<b>/", "/<a>/<b>/<c>/"));
|
||||
assert!(unranked_collide("/hello/<name>", "/hello/<person>"));
|
||||
assert!(unranked_collide("/hello/<name>/hi", "/hello/<person>/hi"));
|
||||
assert!(unranked_collide("/hello/<name>/hi/there", "/hello/<person>/hi/there"));
|
||||
assert!(unranked_collide("/<name>/hi/there", "/<person>/hi/there"));
|
||||
assert!(unranked_collide("/<name>/hi/there", "/dude/<name>/there"));
|
||||
assert!(unranked_collide("/<name>/<a>/<b>", "/<a>/<b>/<c>"));
|
||||
assert!(unranked_collide("/<name>/<a>/<b>/", "/<a>/<b>/<c>/"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn medium_param_collisions() {
|
||||
assert!(collide("/hello/<name>", "/hello/bob"));
|
||||
assert!(collide("/<name>", "//bob"));
|
||||
assert!(unranked_collide("/hello/<name>", "/hello/bob"));
|
||||
assert!(unranked_collide("/<name>", "//bob"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hard_param_collisions() {
|
||||
assert!(collide("/<name>bob", "/<name>b"));
|
||||
assert!(collide("/a<b>c", "/abc"));
|
||||
assert!(collide("/a<b>c", "/azooc"));
|
||||
assert!(collide("/a<b>", "/a"));
|
||||
assert!(collide("/<b>", "/a"));
|
||||
assert!(collide("/<a>/<b>", "/a/b<c>"));
|
||||
assert!(collide("/<a>/bc<b>", "/a/b<c>"));
|
||||
assert!(collide("/<a>/bc<b>d", "/a/b<c>"));
|
||||
assert!(unranked_collide("/<name>bob", "/<name>b"));
|
||||
assert!(unranked_collide("/a<b>c", "/abc"));
|
||||
assert!(unranked_collide("/a<b>c", "/azooc"));
|
||||
assert!(unranked_collide("/a<b>", "/a"));
|
||||
assert!(unranked_collide("/<b>", "/a"));
|
||||
assert!(unranked_collide("/<a>/<b>", "/a/b<c>"));
|
||||
assert!(unranked_collide("/<a>/bc<b>", "/a/b<c>"));
|
||||
assert!(unranked_collide("/<a>/bc<b>d", "/a/b<c>"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn non_collisions() {
|
||||
assert!(!collide("/a", "/b"));
|
||||
assert!(!collide("/a/b", "/a"));
|
||||
assert!(!collide("/a/b", "/a/c"));
|
||||
assert!(!collide("/a/hello", "/a/c"));
|
||||
assert!(!collide("/hello", "/a/c"));
|
||||
assert!(!collide("/hello/there", "/hello/there/guy"));
|
||||
assert!(!collide("/b<a>/there", "/hi/there"));
|
||||
assert!(!collide("/<a>/<b>c", "/hi/person"));
|
||||
assert!(!collide("/<a>/<b>cd", "/hi/<a>e"));
|
||||
assert!(!collide("/a<a>/<b>", "/b<b>/<a>"));
|
||||
assert!(!collide("/a/<b>", "/b/<b>"));
|
||||
assert!(!collide("/a<a>/<b>", "/b/<b>"));
|
||||
assert!(!unranked_collide("/a", "/b"));
|
||||
assert!(!unranked_collide("/a/b", "/a"));
|
||||
assert!(!unranked_collide("/a/b", "/a/c"));
|
||||
assert!(!unranked_collide("/a/hello", "/a/c"));
|
||||
assert!(!unranked_collide("/hello", "/a/c"));
|
||||
assert!(!unranked_collide("/hello/there", "/hello/there/guy"));
|
||||
assert!(!unranked_collide("/b<a>/there", "/hi/there"));
|
||||
assert!(!unranked_collide("/<a>/<b>c", "/hi/person"));
|
||||
assert!(!unranked_collide("/<a>/<b>cd", "/hi/<a>e"));
|
||||
assert!(!unranked_collide("/a<a>/<b>", "/b<b>/<a>"));
|
||||
assert!(!unranked_collide("/a/<b>", "/b/<b>"));
|
||||
assert!(!unranked_collide("/a<a>/<b>", "/b/<b>"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
@ -19,9 +19,7 @@ pub struct Router {
|
|||
|
||||
impl Router {
|
||||
pub fn new() -> Router {
|
||||
Router {
|
||||
routes: HashMap::new()
|
||||
}
|
||||
Router { routes: HashMap::new() }
|
||||
}
|
||||
|
||||
pub fn add(&mut self, route: Route) {
|
||||
|
@ -33,14 +31,18 @@ impl Router {
|
|||
// struct to something like `RocketRouter`. If that happens, returning a
|
||||
// `Route` structure is inflexible. Have it be an associated type.
|
||||
pub fn route<'b>(&'b self, method: Method, uri: &str) -> Option<&'b Route> {
|
||||
let mut matched_route = None;
|
||||
let mut matched_route: Option<&Route> = None;
|
||||
|
||||
let path = URI::new(uri);
|
||||
let num_segments = path.segment_count();
|
||||
if let Some(routes) = self.routes.get(&(method, num_segments)) {
|
||||
for route in routes.iter().filter(|r| r.collides_with(uri)) {
|
||||
println!("\t=> Matched {} to: {}", uri, route);
|
||||
if let None = matched_route {
|
||||
if let Some(existing_route) = matched_route {
|
||||
if route.rank > existing_route.rank {
|
||||
matched_route = Some(route);
|
||||
}
|
||||
} else {
|
||||
matched_route = Some(route);
|
||||
}
|
||||
}
|
||||
|
@ -89,19 +91,47 @@ mod test {
|
|||
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
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_collisions() {
|
||||
let router = router_with_routes(&["/hello", "/hello"]);
|
||||
let router = router_with_unranked_routes(&["/hello", "/hello"]);
|
||||
assert!(router.has_collisions());
|
||||
|
||||
let router = router_with_unranked_routes(&["/<a>", "/hello"]);
|
||||
assert!(router.has_collisions());
|
||||
|
||||
let router = router_with_unranked_routes(&["/<a>", "/<b>"]);
|
||||
assert!(router.has_collisions());
|
||||
|
||||
let router = router_with_unranked_routes(&["/hello/bob", "/hello/<b>"]);
|
||||
assert!(router.has_collisions());
|
||||
|
||||
let router = router_with_routes(&["/a/b/<c>/d", "/<a>/<b>/c/d"]);
|
||||
assert!(router.has_collisions());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_none_collisions_when_ranked() {
|
||||
let router = router_with_routes(&["/<a>", "/hello"]);
|
||||
assert!(router.has_collisions());
|
||||
|
||||
let router = router_with_routes(&["/<a>", "/<b>"]);
|
||||
assert!(router.has_collisions());
|
||||
assert!(!router.has_collisions());
|
||||
|
||||
let router = router_with_routes(&["/hello/bob", "/hello/<b>"]);
|
||||
assert!(router.has_collisions());
|
||||
assert!(!router.has_collisions());
|
||||
|
||||
let router = router_with_routes(&["/a/b/c/d", "/<a>/<b>/c/d"]);
|
||||
assert!(!router.has_collisions());
|
||||
|
||||
let router = router_with_routes(&["/hi", "/<hi>"]);
|
||||
assert!(!router.has_collisions());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -159,6 +189,29 @@ mod test {
|
|||
assert!(router.route(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 = router.route(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>");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ranking() {
|
||||
// FIXME: Add tests for non-default ranks.
|
||||
}
|
||||
|
||||
fn match_params(router: &Router, path: &str, expected: &[&str]) -> bool {
|
||||
router.route(Get, path).map_or(false, |route| {
|
||||
let params = route.get_params(path);
|
||||
|
|
|
@ -11,14 +11,26 @@ pub struct Route {
|
|||
pub method: Method,
|
||||
pub handler: Handler<'static>,
|
||||
pub path: URIBuf,
|
||||
pub rank: isize
|
||||
}
|
||||
|
||||
impl Route {
|
||||
pub fn new(m: Method, path: String, handler: Handler<'static>) -> Route {
|
||||
pub fn ranked(rank: isize, m: Method, path: String,
|
||||
handler: Handler<'static>) -> Route {
|
||||
Route {
|
||||
method: m,
|
||||
path: URIBuf::new(path),
|
||||
handler: handler,
|
||||
rank: rank
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new(m: Method, path: String, handler: Handler<'static>) -> Route {
|
||||
Route {
|
||||
method: m,
|
||||
handler: handler,
|
||||
rank: (!path.contains("<") as isize),
|
||||
path: URIBuf::new(path),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -53,7 +65,8 @@ impl fmt::Display for Route {
|
|||
impl Collider for Route {
|
||||
fn collides_with(&self, b: &Route) -> bool {
|
||||
if self.path.segment_count() != b.path.segment_count()
|
||||
|| self.method != b.method {
|
||||
|| self.method != b.method
|
||||
|| self.rank != b.rank {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue