mirror of https://github.com/rwf2/Rocket.git
Output all matching routes, not just first ranked.
This commit is contained in:
parent
860b302793
commit
a34374d913
|
@ -14,4 +14,5 @@ members = [
|
||||||
"examples/static_files",
|
"examples/static_files",
|
||||||
"examples/todo",
|
"examples/todo",
|
||||||
"examples/content_types",
|
"examples/content_types",
|
||||||
|
"examples/hello_ranks",
|
||||||
]
|
]
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
[package]
|
||||||
|
name = "hello_ranks"
|
||||||
|
version = "0.0.1"
|
||||||
|
authors = ["Sergio Benitez <sb@sergio.bz>"]
|
||||||
|
workspace = "../../"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
rocket = { path = "../../lib" }
|
||||||
|
rocket_macros = { path = "../../macros" }
|
|
@ -0,0 +1,20 @@
|
||||||
|
#![feature(plugin)]
|
||||||
|
#![plugin(rocket_macros)]
|
||||||
|
|
||||||
|
extern crate rocket;
|
||||||
|
use rocket::Rocket;
|
||||||
|
|
||||||
|
#[GET(path = "/hello/<name>/<age>")]
|
||||||
|
fn hello(name: &str, age: i8) -> String {
|
||||||
|
format!("Hello, {} year old named {}!", age, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME: Add 'rank = 2'.
|
||||||
|
#[GET(path = "/hello/<name>/<age>")]
|
||||||
|
fn hi(name: &str, age: &str) -> String {
|
||||||
|
format!("Hi {}! You age ({}) is kind of funky.", name, age)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
Rocket::new("localhost", 8000).mount_and_launch("/", routes![hello, hi]);
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
use super::*;
|
use super::*;
|
||||||
use response::FreshHyperResponse;
|
use response::{FreshHyperResponse, Outcome};
|
||||||
use request::HyperRequest;
|
use request::HyperRequest;
|
||||||
use catcher;
|
use catcher;
|
||||||
|
|
||||||
|
@ -29,7 +29,7 @@ impl HyperHandler for Rocket {
|
||||||
|
|
||||||
impl Rocket {
|
impl Rocket {
|
||||||
fn dispatch<'h, 'k>(&self, hyp_req: HyperRequest<'h, 'k>,
|
fn dispatch<'h, 'k>(&self, hyp_req: HyperRequest<'h, 'k>,
|
||||||
res: FreshHyperResponse<'h>) {
|
mut res: FreshHyperResponse<'h>) {
|
||||||
// Get a copy of the URI for later use.
|
// Get a copy of the URI for later use.
|
||||||
let uri = hyp_req.uri.to_string();
|
let uri = hyp_req.uri.to_string();
|
||||||
|
|
||||||
|
@ -43,27 +43,25 @@ impl Rocket {
|
||||||
};
|
};
|
||||||
|
|
||||||
info!("{}:", request);
|
info!("{}:", request);
|
||||||
let route = self.router.route(&request);
|
let matches = self.router.route(&request);
|
||||||
if let Some(ref route) = route {
|
trace_!("Found {} matches.", matches.len());
|
||||||
|
for route in matches {
|
||||||
// Retrieve and set the requests parameters.
|
// Retrieve and set the requests parameters.
|
||||||
|
info_!("Matched: {}", route);
|
||||||
request.set_params(route);
|
request.set_params(route);
|
||||||
|
|
||||||
// Here's the magic: dispatch the request to the handler.
|
// Here's the magic: dispatch the request to the handler.
|
||||||
let outcome = (route.handler)(&request).respond(res);
|
let outcome = (route.handler)(&request).respond(res);
|
||||||
info_!("{} {}", White.paint("Outcome:"), outcome);
|
info_!("{} {}", White.paint("Outcome:"), outcome);
|
||||||
|
|
||||||
// TODO: keep trying lower ranked routes before dispatching a not
|
res = match outcome {
|
||||||
// found error.
|
Outcome::Complete => return,
|
||||||
outcome.map_forward(|res| {
|
Outcome::FailStop => return,
|
||||||
error_!("No further matching routes.");
|
Outcome::FailForward(r) => r
|
||||||
// TODO: Have some way to know why this was failed forward. Use that
|
};
|
||||||
// instead of always using an unchained error.
|
|
||||||
self.handle_not_found(&request, res);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
error_!("No matching routes.");
|
|
||||||
self.handle_not_found(&request, res);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.handle_not_found(&request, res);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Call on internal server error.
|
// Call on internal server error.
|
||||||
|
@ -95,6 +93,7 @@ impl Rocket {
|
||||||
|
|
||||||
pub fn mount(&mut self, base: &'static str, routes: Vec<Route>)
|
pub fn mount(&mut self, base: &'static str, routes: Vec<Route>)
|
||||||
-> &mut Self {
|
-> &mut Self {
|
||||||
|
self.enable_normal_logging_if_disabled();
|
||||||
info!("🛰 {} '{}':", Magenta.paint("Mounting"), base);
|
info!("🛰 {} '{}':", Magenta.paint("Mounting"), base);
|
||||||
for mut route in routes {
|
for mut route in routes {
|
||||||
let path = format!("{}/{}", base, route.path.as_str());
|
let path = format!("{}/{}", base, route.path.as_str());
|
||||||
|
@ -108,12 +107,13 @@ impl Rocket {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn catch(&mut self, catchers: Vec<Catcher>) -> &mut Self {
|
pub fn catch(&mut self, catchers: Vec<Catcher>) -> &mut Self {
|
||||||
|
self.enable_normal_logging_if_disabled();
|
||||||
info!("👾 {}:", Magenta.paint("Catchers"));
|
info!("👾 {}:", Magenta.paint("Catchers"));
|
||||||
for c in catchers {
|
for c in catchers {
|
||||||
if self.catchers.contains_key(&c.code) &&
|
if self.catchers.contains_key(&c.code) &&
|
||||||
!self.catchers.get(&c.code).unwrap().is_default() {
|
!self.catchers.get(&c.code).unwrap().is_default() {
|
||||||
let msg = format!("warning: overrides {} catcher!", c.code);
|
let msg = format!("warning: overrides {} catcher!", c.code);
|
||||||
info_!("{} ({})", c, Yellow.paint(msg.as_str()));
|
warn!("{} ({})", c, Yellow.paint(msg.as_str()));
|
||||||
} else {
|
} else {
|
||||||
info_!("{}", c);
|
info_!("{}", c);
|
||||||
}
|
}
|
||||||
|
@ -124,6 +124,13 @@ impl Rocket {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn enable_normal_logging_if_disabled(&mut self) {
|
||||||
|
if !self.log_set {
|
||||||
|
logger::init(LoggingLevel::Normal);
|
||||||
|
self.log_set = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn log(&mut self, level: LoggingLevel) {
|
pub fn log(&mut self, level: LoggingLevel) {
|
||||||
if self.log_set {
|
if self.log_set {
|
||||||
warn!("Log level already set! Not overriding.");
|
warn!("Log level already set! Not overriding.");
|
||||||
|
@ -134,14 +141,11 @@ impl Rocket {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn launch(mut self) {
|
pub fn launch(mut self) {
|
||||||
|
self.enable_normal_logging_if_disabled();
|
||||||
if self.router.has_collisions() {
|
if self.router.has_collisions() {
|
||||||
warn!("Route collisions detected!");
|
warn!("Route collisions detected!");
|
||||||
}
|
}
|
||||||
|
|
||||||
if !self.log_set {
|
|
||||||
self.log(LoggingLevel::Normal)
|
|
||||||
}
|
|
||||||
|
|
||||||
let full_addr = format!("{}:{}", self.address, self.port);
|
let full_addr = format!("{}:{}", self.address, self.port);
|
||||||
info!("🚀 {} {}...", White.paint("Rocket has launched from"),
|
info!("🚀 {} {}...", White.paint("Rocket has launched from"),
|
||||||
White.bold().paint(&full_addr));
|
White.bold().paint(&full_addr));
|
||||||
|
|
|
@ -45,7 +45,7 @@ mod tests {
|
||||||
use Method;
|
use Method;
|
||||||
use Method::*;
|
use Method::*;
|
||||||
use {Request, Response};
|
use {Request, Response};
|
||||||
use content_type::{ContentType, TopLevel, SubLevel};
|
use content_type::ContentType;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
type SimpleRoute = (Method, &'static str);
|
type SimpleRoute = (Method, &'static str);
|
||||||
|
|
|
@ -32,27 +32,16 @@ impl Router {
|
||||||
// `Route` structure is inflexible. Have it be an associated type.
|
// `Route` structure is inflexible. Have it be an associated type.
|
||||||
// FIXME: Figure out a way to get more than one route, i.e., to correctly
|
// FIXME: Figure out a way to get more than one route, i.e., to correctly
|
||||||
// handle ranking.
|
// handle ranking.
|
||||||
// TODO: Should the Selector include the content-type? If it does, can't
|
pub fn route<'b>(&'b self, req: &Request) -> Vec<&'b Route> {
|
||||||
// warn the user that a match was found for the wrong content-type. It
|
|
||||||
// doesn't, can, but this method is slower.
|
|
||||||
pub fn route<'b>(&'b self, req: &Request) -> Option<&'b Route> {
|
|
||||||
let num_segments = req.uri.segment_count();
|
let num_segments = req.uri.segment_count();
|
||||||
|
self.routes.get(&(req.method, num_segments)).map_or(vec![], |routes| {
|
||||||
|
let mut matches: Vec<&'b Route> = routes.iter().filter(|r| {
|
||||||
|
r.collides_with(req)
|
||||||
|
}).collect();
|
||||||
|
|
||||||
let mut matched_route: Option<&Route> = None;
|
matches.sort_by(|a, b| a.rank.cmp(&b.rank));
|
||||||
if let Some(routes) = self.routes.get(&(req.method, num_segments)) {
|
matches
|
||||||
for route in routes.iter().filter(|r| r.collides_with(req)) {
|
})
|
||||||
info_!("Matched: {}", route);
|
|
||||||
if let Some(existing_route) = matched_route {
|
|
||||||
if route.rank < existing_route.rank {
|
|
||||||
matched_route = Some(route);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
matched_route = Some(route);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
matched_route
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn has_collisions(&self) -> bool {
|
pub fn has_collisions(&self) -> bool {
|
||||||
|
@ -94,6 +83,16 @@ mod test {
|
||||||
router
|
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 {
|
fn router_with_unranked_routes(routes: &[&'static str]) -> Router {
|
||||||
let mut router = Router::new();
|
let mut router = Router::new();
|
||||||
for route in routes {
|
for route in routes {
|
||||||
|
@ -104,42 +103,41 @@ mod test {
|
||||||
router
|
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]
|
#[test]
|
||||||
fn test_collisions() {
|
fn test_collisions() {
|
||||||
let router = router_with_unranked_routes(&["/hello", "/hello"]);
|
assert!(unranked_route_collisions(&["/hello", "/hello"]));
|
||||||
assert!(router.has_collisions());
|
assert!(unranked_route_collisions(&["/<a>", "/hello"]));
|
||||||
|
assert!(unranked_route_collisions(&["/<a>", "/<b>"]));
|
||||||
let router = router_with_unranked_routes(&["/<a>", "/hello"]);
|
assert!(unranked_route_collisions(&["/hello/bob", "/hello/<b>"]));
|
||||||
assert!(router.has_collisions());
|
assert!(unranked_route_collisions(&["/a/b/<c>/d", "/<a>/<b>/c/d"]));
|
||||||
|
|
||||||
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]
|
#[test]
|
||||||
fn test_none_collisions_when_ranked() {
|
fn test_none_collisions_when_ranked() {
|
||||||
let router = router_with_routes(&["/<a>", "/hello"]);
|
assert!(!default_rank_route_collisions(&["/<a>", "/hello"]));
|
||||||
assert!(!router.has_collisions());
|
assert!(!default_rank_route_collisions(&["/hello/bob", "/hello/<b>"]));
|
||||||
|
assert!(!default_rank_route_collisions(&["/a/b/c/d", "/<a>/<b>/c/d"]));
|
||||||
let router = router_with_routes(&["/hello/bob", "/hello/<b>"]);
|
assert!(!default_rank_route_collisions(&["/hi", "/<hi>"]));
|
||||||
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());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn route<'a>(router: &'a Router, method: Method, uri: &str) -> Option<&'a Route> {
|
fn route<'a>(router: &'a Router, method: Method, uri: &str) -> Option<&'a Route> {
|
||||||
let request = Request::mock(method, uri);
|
let request = Request::mock(method, uri);
|
||||||
router.route(&request)
|
let matches = router.route(&request);
|
||||||
|
if matches.len() > 0 {
|
||||||
|
Some(matches[0])
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -215,11 +213,47 @@ mod test {
|
||||||
assert_ranked_routes!(&["/hi/a", "/hi/<c>"], "/hi/c", "/hi/<c>");
|
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]
|
#[test]
|
||||||
fn test_ranking() {
|
fn test_no_manual_ranked_collisions() {
|
||||||
let a = Route::ranked(1, Get, "a/<b>", dummy_handler);
|
assert!(!ranked_collisions(&[(1, "a/<b>"), (2, "a/<b>")]));
|
||||||
let b = Route::ranked(2, Get, "a/<b>", dummy_handler);
|
assert!(!ranked_collisions(&[(0, "a/<b>"), (2, "a/<b>")]));
|
||||||
// FIXME: Add tests for non-default ranks.
|
assert!(!ranked_collisions(&[(5, "a/<b>"), (2, "a/<b>")]));
|
||||||
|
assert!(!ranked_collisions(&[(1, "a/<b>"), (1, "b/<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 = route(&router, Get, $to).unwrap();
|
||||||
|
assert_eq!(routed_to.path.as_str() as &str, $want.1);
|
||||||
|
assert_eq!(routed_to.rank, $want.0);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_ranked_routing() {
|
||||||
|
assert_ranked_routing!(
|
||||||
|
to: "a/b",
|
||||||
|
with: [(1, "a/<b>"), (2, "a/<b>")],
|
||||||
|
expect: (1, "a/<b>")
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_ranked_routing!(
|
||||||
|
to: "b/b",
|
||||||
|
with: [(1, "a/<b>"), (2, "b/<b>"), (3, "b/b")],
|
||||||
|
expect: (2, "b/<b>")
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_ranked_routing!(
|
||||||
|
to: "b/b",
|
||||||
|
with: [(1, "a/<b>"), (2, "b/<b>"), (0, "b/b")],
|
||||||
|
expect: (0, "b/b")
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn match_params(router: &Router, path: &str, expected: &[&str]) -> bool {
|
fn match_params(router: &Router, path: &str, expected: &[&str]) -> bool {
|
||||||
|
|
|
@ -69,10 +69,10 @@ impl Route {
|
||||||
|
|
||||||
impl fmt::Display for Route {
|
impl fmt::Display for Route {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
write!(f, "{} {} ", Green.paint(&self.method), Blue.paint(&self.path))?;
|
write!(f, "{} {}", Green.paint(&self.method), Blue.paint(&self.path))?;
|
||||||
|
|
||||||
if !self.content_type.is_any() {
|
if !self.content_type.is_any() {
|
||||||
write!(f, "{}", Yellow.paint(&self.content_type))
|
write!(f, " {}", Yellow.paint(&self.content_type))
|
||||||
} else {
|
} else {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue