use middleware::{Middleware, MiddlewareResult};
use request::Request;
use response::Response;
use router::HttpRouter;
use hyper::method::Method;
use hyper::status::StatusCode;
use router::{Matcher, FORMAT_PARAM};
pub struct Route<D=()> {
pub method: Method,
pub handler: Box<Middleware<D> + Send + Sync + 'static>,
matcher: Matcher
}
pub struct RouteResult<'mw, D: 'mw = ()> {
pub route: &'mw Route<D>,
params: Vec<(String, String)>
}
impl<'mw, D> RouteResult<'mw, D> {
pub fn param(&self, key: &str) -> Option<&str> {
for &(ref k, ref v) in &self.params {
if k == &key {
return Some(&v[..])
}
}
if key == FORMAT_PARAM {
Some("")
} else {
None
}
}
}
pub struct Router<D=()> {
routes: Vec<Route<D>>,
}
impl<D> Router<D> {
pub fn new() -> Router<D> {
Router {
routes: Vec::new()
}
}
pub fn match_route<'mw>(&'mw self, method: &Method, path: &str) -> Option<RouteResult<'mw, D>> {
self.routes
.iter()
.find(|item| item.method == *method && item.matcher.is_match(path))
.map(|route|
RouteResult {
params: extract_params(route, path),
route: route
}
)
}
}
fn extract_params<D>(route: &Route<D>, path: &str) -> Vec<(String, String)> {
let captures = match route.matcher.captures(path) {
Some(cap) => cap,
None => { return vec![]; },
};
route.matcher.capture_names()
.filter_map(|n| {
let name = if let Some(name) = n {
name
} else {
return None;
};
let capture = if let Some(capture) = captures.name(name) {
capture
} else {
return None;
};
Some((name.to_string(), capture.as_str().to_string()))
})
.collect()
}
impl<D> HttpRouter<D> for Router<D> {
fn add_route<M: Into<Matcher>, H: Middleware<D>>(&mut self, method: Method, matcher: M, handler: H) -> &mut Self {
let route = Route {
matcher: matcher.into(),
method: method,
handler: Box::new(handler),
};
self.routes.push(route);
self
}
}
impl<D: 'static> Middleware<D> for Router<D> {
fn invoke<'mw, 'conn>(&'mw self, req: &mut Request<'mw, 'conn, D>, mut res: Response<'mw, D>)
-> MiddlewareResult<'mw, D> {
debug!("Router::invoke for '{:?}'", req.origin.uri);
let route_result = req.path_without_query()
.and_then(|path| self.match_route(&req.origin.method, path));
debug!("route_result.route.path: {:?}", route_result.as_ref().map(|r| r.route.matcher.path()));
match route_result {
Some(route_result) => {
res.set(StatusCode::Ok);
let handler = &route_result.route.handler;
req.route_result = Some(route_result);
handler.invoke(req, res)
},
None => res.next_middleware()
}
}
}
#[test]
fn creates_regex_with_captures () {
let matcher: Matcher = "foo/:uid/bar/:groupid".into();
let caps = matcher.captures("foo/4711/bar/5490").unwrap();
assert_eq!(matcher.path(), "foo/:uid/bar/:groupid(\\.:format)?");
assert_eq!(caps.get(1).unwrap().as_str(), "4711");
assert_eq!(caps.get(2).unwrap().as_str(), "5490");
let matcher: Matcher = "foo/*/:uid/bar/:groupid".into();
let caps = matcher.captures("foo/test/4711/bar/5490").unwrap();
assert_eq!(matcher.path(), "foo/*/:uid/bar/:groupid(\\.:format)?");
assert_eq!(caps.get(1).unwrap().as_str(), "4711");
assert_eq!(caps.get(2).unwrap().as_str(), "5490");
let matcher: Matcher = "foo/**/:uid/bar/:groupid".into();
let caps = matcher.captures("foo/test/another/4711/bar/5490").unwrap();
assert_eq!(matcher.path(), "foo/**/:uid/bar/:groupid(\\.:format)?");
assert_eq!(caps.get(1).unwrap().as_str(), "4711");
assert_eq!(caps.get(2).unwrap().as_str(), "5490");
let matcher: Matcher = "foo/**/:format/bar/:groupid".into();
let caps = matcher.captures("foo/test/another/4711/bar/5490").unwrap();
assert_eq!(matcher.path(), "foo/**/:format/bar/:groupid");
assert_eq!(caps.name("format").unwrap().as_str(), "4711");
assert_eq!(caps.name("groupid").unwrap().as_str(), "5490");
}
#[test]
fn creates_valid_regex_for_routes () {
let multi_params: Matcher = "foo/:uid/bar/:groupid".into();
assert!(multi_params.is_match("foo/4711/bar/5490"));
assert!(multi_params.is_match("foo/4711/bar/5490?foo=true&bar=false"));
assert!(!multi_params.is_match("foo/4711/bar"));
assert!(!multi_params.is_match("foo/4711/bar?foo=true&bar=false"));
assert!(multi_params.is_match("foo/4711/bar/test%20spacing"));
assert!(multi_params.is_match("foo/4711/bar/5281?foo=test%20spacing&bar=false"));
assert!(multi_params.is_match("foo/alice/bar/bob"));
assert!(multi_params.is_match("foo/4711/bar/5490,1234"));
assert!(multi_params.is_match("foo/4711/bar/5490,1234?foo=true&bar=false"));
assert!(!multi_params.is_match("foo/4711/bar"));
assert!(!multi_params.is_match("foo/4711/bar?foo=1,2,3&bar=false"));
assert!(multi_params.is_match("foo/alice-anne/bar/bob-gates"));
let single_asterisk: Matcher = "foo/*/bar".into();
assert!(single_asterisk.is_match("foo/4711/bar"));
assert!(!single_asterisk.is_match("foo/4711/barr"));
assert!(single_asterisk.is_match("foo/4711/bar?foo=true&bar=false"));
assert!(!single_asterisk.is_match("foo/4711/4712/bar"));
assert!(!single_asterisk.is_match("foo/4711/4712/bar?foo=true&bar=false"));
assert!(!single_asterisk.is_match("foo/alice/bar/bob"));
assert!(single_asterisk.is_match("foo/alice/bar"));
let double_asterisk: Matcher = "foo/**/bar".into();
assert!(double_asterisk.is_match("foo/4711/bar"));
assert!(double_asterisk.is_match("foo/4711/bar?foo=true&bar=false"));
assert!(double_asterisk.is_match("foo/4711/4712/bar"));
assert!(double_asterisk.is_match("foo/4711/4712/bar?foo=true&bar=false"));
assert!(!double_asterisk.is_match("foo/alice/bar/bob"));
}
#[test]
fn can_match_var_routes () {
let route_store = &mut Router::<()>::new();
route_store.add_route(Method::Get, "/foo/:userid", middleware! { "hello from foo" });
route_store.add_route(Method::Get, "/bar", middleware! { "hello from foo" });
route_store.add_route(Method::Get, "/file/:format/:file", middleware! { "hello from foo" });
let route_result = route_store.match_route(&Method::Get, "/foo/4711").unwrap();
assert_eq!(route_result.param("userid"), Some("4711"));
let route_result = route_store.match_route(&Method::Get, "/bar/4711");
assert!(route_result.is_none());
let route_result = route_store.match_route(&Method::Get, "/foo");
assert!(route_result.is_none());
let route_result = route_store.match_route(&Method::Get, "/foo/123,456");
assert!(route_result.is_some());
let route_result = route_result.unwrap();
assert_eq!(route_result.param("userid"), Some("123,456"));
let route_result = route_store.match_route(&Method::Get, "/foo/John%20Doe");
assert!(route_result.is_some());
let route_result = route_result.unwrap();
assert_eq!(route_result.param("userid"), Some("John%20Doe"));
let route_result = route_store.match_route(&Method::Get, "/foo/John%20Doe.json");
assert!(route_result.is_some());
let route_result = route_result.unwrap();
assert_eq!(route_result.param("userid"), Some("John%20Doe"));
assert_eq!(route_result.param("format"), Some("json"));
let route_result = route_store.match_route(&Method::Get,
"/foo/5490,1234.csv?foo=true&bar=false");
assert!(route_result.is_some());
let route_result = route_result.unwrap();
assert_eq!(route_result.param("userid"), Some("5490,1234"));
assert_eq!(route_result.param("format"), Some("csv"));
let route_result = route_store.match_route(&Method::Get,
"/foo/5490,1234?foo=true&bar=false").unwrap();
assert_eq!(route_result.param("format"), Some(""));
let route_result = route_store.match_route(&Method::Get, "/file/markdown/something?foo=true");
assert!(route_result.is_some());
let route_result = route_result.unwrap();
assert_eq!(route_result.param("file"), Some("something"));
assert_eq!(route_result.param("format"), Some("markdown"));
}
#[test]
fn params_lifetime() {
let route_store = &mut Router::<()>::new();
let handler = middleware! { "hello from foo" };
route_store.add_route(Method::Get, "/file/:format/:file", handler);
let route_result = route_store.match_route(&Method::Get, "/file/txt/manual");
assert!(route_result.is_some());
let route_result = route_result.unwrap();
let format = route_result.param("format");
let file = route_result.param("file");
assert_eq!(format, Some("txt"));
assert_eq!(file, Some("manual"));
}
#[test]
fn regex_path() {
use regex::Regex;
let route_store = &mut Router::<()>::new();
let regex = Regex::new("/(foo|bar)").unwrap();
route_store.add_route(Method::Get, regex, middleware! { "hello from foo" });
let route_result = route_store.match_route(&Method::Get, "/foo");
assert!(route_result.is_some());
let route_result = route_store.match_route(&Method::Get, "/bar");
assert!(route_result.is_some());
let route_result = route_store.match_route(&Method::Get, "/bar?foo");
assert!(route_result.is_some());
let route_result = route_store.match_route(&Method::Get, "/baz");
assert!(route_result.is_none());
}
#[test]
fn regex_path_named() {
use regex::Regex;
let route_store = &mut Router::<()>::new();
let regex = Regex::new("/(?P<a>foo|bar)/b").unwrap();
route_store.add_route(Method::Get, regex, middleware! { "hello from foo" });
let route_result = route_store.match_route(&Method::Get, "/foo/b");
assert!(route_result.is_some());
let route_result = route_result.unwrap();
assert_eq!(route_result.param("a"), Some("foo"));
let route_result = route_store.match_route(&Method::Get, "/bar/b");
assert!(route_result.is_some());
let route_result = route_result.unwrap();
assert_eq!(route_result.param("a"), Some("bar"));
let route_result = route_store.match_route(&Method::Get, "/baz/b");
assert!(route_result.is_none());
}
#[test]
fn ignores_querystring() {
use regex::Regex;
let route_store = &mut Router::<()>::new();
let regex = Regex::new("/(?P<a>foo|bar)/b").unwrap();
route_store.add_route(Method::Get, regex, middleware! { "hello from foo" });
route_store.add_route(Method::Get, "/:foo", middleware! { "hello from foo" });
let route_result = route_store.match_route(&Method::Get, "/moo?foo");
assert!(route_result.is_some());
let route_result = route_result.unwrap();
assert_eq!(route_result.param("foo"), Some("moo"));
let route_result = route_store.match_route(&Method::Get, "/bar/b?foo");
assert!(route_result.is_some());
let route_result = route_result.unwrap();
assert_eq!(route_result.param("a"), Some("bar"));
}