It works! Next steps: clean-up, error handling, docs.

This commit is contained in:
Sergio Benitez 2016-03-21 22:04:39 -07:00
parent 5cdb645fc9
commit 433a9119bd
8 changed files with 249 additions and 72 deletions

View File

@ -3,36 +3,19 @@
extern crate rocket; extern crate rocket;
use rocket::Rocket; use rocket::Rocket;
use std::fs::File; use std::fs::File;
#[route(GET, path = "/")] #[route(GET, path = "/")]
fn simple() -> File { fn root() -> File {
File::open("/tmp/index.html").unwrap() File::open("/tmp/index.html").unwrap()
} }
#[route(GET, path = "/hello/")] #[route(GET, path = "/hello/<name>/<age>")]
fn simple2() -> &'static str { fn hello(name: &str, age: i8) -> String {
"Hello, world!"
}
#[route(GET, path = "/hello")]
fn simple3() -> String {
String::from("Hello, world!")
}
#[route(GET, path = "/<name>/<age>")]
fn simple4(name: &str, age: i8) -> String {
format!("Hello, {} year old named {}!", age, name) format!("Hello, {} year old named {}!", age, name)
} }
#[route(GET, path = "/something")]
fn simple5() -> &'static str {
"hi"
}
fn main() { fn main() {
let mut rocket = Rocket::new("localhost", 8000); let rocket = Rocket::new("localhost", 8000);
rocket.mount("/", routes![simple, simple2, simple3, simple4, simple5]); rocket.mount_and_launch("/", routes![root, hello]);
rocket.mount_and_launch("/hello/", routes![simple, simple3, simple4, simple5]);
} }

View File

@ -1,5 +1,6 @@
pub enum Error { pub enum Error {
BadMethod, BadMethod,
BadParse, BadParse,
NoRoute,
NoKey NoKey
} }

View File

@ -27,15 +27,14 @@ use hyper::Server;
pub type Handler<'a> = fn(Request) -> Response<'a>; pub type Handler<'a> = fn(Request) -> Response<'a>;
#[allow(dead_code)] // TODO: Figure out if having Handler<'static> there is a good idea.
#[derive(Clone)] pub struct Route {
pub struct Route<'a> {
pub method: Method, pub method: Method,
pub path: &'static str, pub path: &'static str,
pub handler: Handler<'a> pub handler: Handler<'static>
} }
impl<'a> fmt::Display for Route<'a> { impl<'a> 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))
} }
@ -54,9 +53,15 @@ impl HypHandler for Rocket {
if let RequestUri::AbsolutePath(uri_string) = req.uri { if let RequestUri::AbsolutePath(uri_string) = req.uri {
if let Some(method) = Method::from_hyp(req.method) { if let Some(method) = Method::from_hyp(req.method) {
println!("Request: {:?}", uri_string); println!("Request: {:?}", uri_string);
self.router.route(method, uri_string.as_str()); let uri_str = uri_string.as_str();
res.send(b"Hello, world!").unwrap(); let route = self.router.route(method, uri_str);
return; let mut response = route.map_or(Response::not_found(), |route| {
let params = route.get_params(uri_str);
let request = Request::new(params, uri_str);
(route.handler)(request)
});
return response.body.respond(res);
} }
} }
@ -73,19 +78,17 @@ impl Rocket {
} }
} }
pub fn mount(&mut self, base: &'static str, routes: &[&Route<'static>]) pub fn mount(&mut self, base: &'static str, routes: &[&Route]) -> &mut Self {
-> &mut Self {
println!("🛰 {} '{}':", Magenta.paint("Mounting"), Blue.paint(base)); println!("🛰 {} '{}':", Magenta.paint("Mounting"), Blue.paint(base));
for route in routes { for route in routes {
println!("\t* {}", route); println!("\t* {}", route);
self.router.add_route(route.method.clone(), base, route.path); self.router.add_route(route.method, base, route.path, route.handler);
} }
self self
} }
pub fn mount_and_launch(mut self, base: &'static str, pub fn mount_and_launch(mut self, base: &'static str, routes: &[&Route]) {
routes: &[&Route<'static>]) {
self.mount(base, routes); self.mount(base, routes);
self.launch(); self.launch();
} }

View File

@ -1,19 +1,32 @@
use error::Error; use error::Error;
use param::FromParam; use param::FromParam;
pub struct Request; pub struct Request<'a> {
params: Vec<&'a str>,
uri: &'a str,
}
impl Request { impl<'a> Request<'a> {
pub fn empty() -> Request { pub fn empty() -> Request<'static> {
Request Request::new(vec![], "")
} }
pub fn get_param_str<'a>(&self, _name: &'a str) -> Result<&'a str, Error> { pub fn new(params: Vec<&'a str>, uri: &'a str) -> Request<'a> {
Request {
params: params,
uri: uri
}
}
pub fn get_uri(&self) -> &'a str {
self.uri
}
pub fn get_param<T: FromParam<'a>>(&'a self, n: usize) -> Result<T, Error> {
if n >= self.params.len() {
Err(Error::NoKey) Err(Error::NoKey)
} else {
T::from_param(self.params[n])
} }
pub fn get_param<'b, T: FromParam<'b>>(&self, name: &'b str)
-> Result<T, Error> {
self.get_param_str(name).and_then(T::from_param)
} }
} }

View File

@ -4,7 +4,8 @@ pub trait Collider<T: ?Sized = Self> {
fn collides_with(&self, other: &T) -> bool; fn collides_with(&self, other: &T) -> bool;
} }
fn check_match_until(break_c: char, a: &str, b: &str, dir: bool) -> bool { pub fn index_match_until(break_c: char, a: &str, b: &str, dir: bool)
-> Option<(isize, isize)> {
let (a_len, b_len) = (a.len() as isize, b.len() as isize); let (a_len, b_len) = (a.len() as isize, b.len() as isize);
let (mut i, mut j, delta) = if dir { let (mut i, mut j, delta) = if dir {
(0, 0, 1) (0, 0, 1)
@ -17,14 +18,18 @@ fn check_match_until(break_c: char, a: &str, b: &str, dir: bool) -> bool {
if c1 == break_c || c2 == break_c { if c1 == break_c || c2 == break_c {
break; break;
} else if c1 != c2 { } else if c1 != c2 {
return false; return None;
} else { } else {
i += delta; i += delta;
j += delta; j += delta;
} }
} }
return true; return Some((i, j));
}
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 { macro_rules! comp_to_str {
@ -42,7 +47,7 @@ macro_rules! comp_to_str {
impl<'a> Collider for Component<'a> { impl<'a> Collider for Component<'a> {
fn collides_with(&self, other: &Component<'a>) -> bool { fn collides_with(&self, other: &Component<'a>) -> bool {
let (a, b) = (comp_to_str!(self), comp_to_str!(other)); let (a, b) = (comp_to_str!(self), comp_to_str!(other));
check_match_until('<', a, b, true) && check_match_until('>', a, b, false) do_match_until('<', a, b, true) && do_match_until('>', a, b, false)
} }
} }
@ -52,25 +57,30 @@ mod tests {
use router::route::Route; use router::route::Route;
use Method; use Method;
use Method::*; use Method::*;
use {Request, Response};
type SimpleRoute = (Method, &'static str); type SimpleRoute = (Method, &'static str);
fn dummy_handler(_req: Request) -> Response<'static> {
Response::empty()
}
fn m_collide(a: SimpleRoute, b: SimpleRoute) -> bool { fn m_collide(a: SimpleRoute, b: SimpleRoute) -> bool {
let route_a = Route::new(a.0, "/", a.1); let route_a = Route::new(a.0, "/", a.1, dummy_handler);
route_a.collides_with(&Route::new(b.0, "/", b.1)) route_a.collides_with(&Route::new(b.0, "/", b.1, dummy_handler))
} }
fn collide(a: &'static str, b: &'static str) -> bool { fn collide(a: &'static str, b: &'static str) -> bool {
let route_a = Route::new(Get, "/", a); let route_a = Route::new(Get, "/", a, dummy_handler);
route_a.collides_with(&Route::new(Get, "/", b)) route_a.collides_with(&Route::new(Get, "/", b, dummy_handler))
} }
fn s_r_collide(a: &'static str, b: &'static str) -> bool { fn s_r_collide(a: &'static str, b: &'static str) -> bool {
a.collides_with(&Route::new(Get, "/", b)) a.collides_with(&Route::new(Get, "/", b, dummy_handler))
} }
fn r_s_collide(a: &'static str, b: &'static str) -> bool { fn r_s_collide(a: &'static str, b: &'static str) -> bool {
let route_a = Route::new(Get, "/", a); let route_a = Route::new(Get, "/", a, dummy_handler);
route_a.collides_with(b) route_a.collides_with(b)
} }

View File

@ -9,6 +9,7 @@ use self::route::Route;
use std::collections::hash_map::HashMap; use std::collections::hash_map::HashMap;
use std::path::Path; use std::path::Path;
use method::Method; use method::Method;
use Handler;
type Selector = (Method, usize); type Selector = (Method, usize);
@ -25,25 +26,28 @@ impl Router {
// FIXME: Take in Handler. // FIXME: Take in Handler.
pub fn add_route(&mut self, method: Method, base: &'static str, pub fn add_route(&mut self, method: Method, base: &'static str,
route: &'static str) { route: &'static str, handler: Handler<'static>) {
let route = Route::new(method, base, route); let route = Route::new(method, base, route, handler);
let selector = (method, route.component_count()); let selector = (method, route.component_count());
self.routes.entry(selector).or_insert(vec![]).push(route); self.routes.entry(selector).or_insert(vec![]).push(route);
} }
// TODO: Make a `Router` trait with this function. Rename this `Router` // TODO: Make a `Router` trait with this function. Rename this `Router`
// struct to something like `RocketRouter`. // struct to something like `RocketRouter`.
// TODO: Return an array of matches to the parameters. pub fn route<'b>(&'b self, method: Method, uri: &str) -> Option<&'b Route> {
pub fn route<'a>(&self, method: Method, uri: &'a str) { let mut matched_route = None;
let path = Path::new(uri); let path = Path::new(uri);
let num_components = path.components().count(); let num_components = path.components().count();
if let Some(routes) = self.routes.get(&(method, num_components)) { if let Some(routes) = self.routes.get(&(method, num_components)) {
for route in routes { for route in routes.iter().filter(|r| r.collides_with(uri)) {
if route.collides_with(uri) {
println!("Matched {} to: {}", uri, route); println!("Matched {} to: {}", uri, route);
if let None = matched_route {
matched_route = Some(route);
} }
} }
} }
matched_route
} }
pub fn has_collisions(&self) -> bool { pub fn has_collisions(&self) -> bool {
@ -65,3 +69,133 @@ impl Router {
} }
} }
#[cfg(test)]
mod test {
use super::Router;
use Method::*;
use {Response, Request};
fn dummy_handler(_req: Request) -> Response<'static> {
Response::empty()
}
fn router_with_routes(routes: &[&'static str]) -> Router {
let mut router = Router::new();
for route in routes {
router.add_route(Get, "/", route, dummy_handler);
}
router
}
#[test]
fn test_collisions() {
let router = router_with_routes(&["/hello", "/hello"]);
assert!(router.has_collisions());
let router = router_with_routes(&["/<a>", "/hello"]);
assert!(router.has_collisions());
let router = router_with_routes(&["/<a>", "/<b>"]);
assert!(router.has_collisions());
let router = router_with_routes(&["/hello/bob", "/hello/<b>"]);
assert!(router.has_collisions());
}
#[test]
fn test_ok_routing() {
let router = router_with_routes(&["/hello"]);
assert!(router.route(Get, "/hello").is_some());
let router = router_with_routes(&["/<a>"]);
assert!(router.route(Get, "/hello").is_some());
assert!(router.route(Get, "/hi").is_some());
assert!(router.route(Get, "/bobbbbbbbbbby").is_some());
assert!(router.route(Get, "/dsfhjasdf").is_some());
let router = router_with_routes(&["/<a>/<b>"]);
assert!(router.route(Get, "/hello/hi").is_some());
assert!(router.route(Get, "/a/b/").is_some());
assert!(router.route(Get, "/i/a").is_some());
assert!(router.route(Get, "/jdlk/asdij").is_some());
let mut router = Router::new();
router.add_route(Put, "/", "/hello", dummy_handler);
router.add_route(Post, "/", "/hello", dummy_handler);
router.add_route(Delete, "/", "/hello", dummy_handler);
assert!(router.route(Put, "/hello").is_some());
assert!(router.route(Post, "/hello").is_some());
assert!(router.route(Delete, "/hello").is_some());
}
#[test]
fn test_err_routing() {
let router = router_with_routes(&["/hello"]);
assert!(router.route(Put, "/hello").is_none());
assert!(router.route(Post, "/hello").is_none());
assert!(router.route(Options, "/hello").is_none());
assert!(router.route(Get, "/hell").is_none());
assert!(router.route(Get, "/hi").is_none());
assert!(router.route(Get, "/hello/there").is_none());
assert!(router.route(Get, "/hello/i").is_none());
assert!(router.route(Get, "/hillo").is_none());
let router = router_with_routes(&["/<a>"]);
assert!(router.route(Put, "/hello").is_none());
assert!(router.route(Post, "/hello").is_none());
assert!(router.route(Options, "/hello").is_none());
assert!(router.route(Get, "/hello/there").is_none());
assert!(router.route(Get, "/hello/i").is_none());
let router = router_with_routes(&["/<a>/<b>"]);
assert!(router.route(Get, "/a/b/c").is_none());
assert!(router.route(Get, "/a").is_none());
assert!(router.route(Get, "/a/").is_none());
assert!(router.route(Get, "/a/b/c/d").is_none());
assert!(router.route(Put, "/hello/hi").is_none());
assert!(router.route(Put, "/a/b").is_none());
assert!(router.route(Put, "/a/b").is_none());
}
fn match_params(router: &Router, path: &str, expected: &[&str]) -> bool {
router.route(Get, path).map_or(false, |route| {
let params = route.get_params(path);
if params.len() != expected.len() {
return false;
}
for i in 0..params.len() {
if params[i] != expected[i] {
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"]));
}
}

View File

@ -4,35 +4,68 @@ use std::path::{Path, PathBuf};
use std::fmt; use std::fmt;
use method::Method; use method::Method;
use super::Collider; // :D use super::Collider; // :D
use std::path::Component;
use Handler;
// FIXME: Take in the handler! Or maybe keep that in `Router`? // FIXME: Take in the handler! Or maybe keep that in `Router`?
#[derive(Debug)]
pub struct Route { pub struct Route {
method: Method, method: Method,
mount: &'static str,
route: &'static str,
n_components: usize, n_components: usize,
pub handler: Handler<'static>,
path: PathBuf path: PathBuf
} }
macro_rules! comp_to_str {
($component:expr) => (
match $component {
&Component::Normal(ref comp) => {
if let Some(string) = comp.to_str() { string }
else { panic!("Whoops, no string!") }
},
&Component::RootDir => "/",
&c@_ => panic!("Whoops, not normal: {:?}!", c)
};
)
}
impl Route { impl Route {
pub fn new(m: Method, mount: &'static str, route: &'static str) -> Route { pub fn new(m: Method, mount: &'static str, route: &'static str,
handler: Handler<'static>) -> Route {
let deduped_path = Route::dedup(mount, route); let deduped_path = Route::dedup(mount, route);
let path = PathBuf::from(deduped_path); let path = PathBuf::from(deduped_path);
Route { Route {
method: m, method: m,
mount: mount,
route: route,
n_components: path.components().count(), n_components: path.components().count(),
path: path handler: handler,
path: path,
} }
} }
#[inline]
pub fn component_count(&self) -> usize { pub fn component_count(&self) -> usize {
self.n_components self.n_components
} }
// FIXME: This is dirty (the comp_to_str and the RootDir thing). Might need
// to have my own wrapper arround path strings.
// FIXME: Decide whether a component has to be fully variable or not. That
// is, whether you can have: /a<a>b/
// TODO: Don't return a Vec...take in an &mut [&'a str] (no alloc!)
pub fn get_params<'a>(&self, uri: &'a str) -> Vec<&'a str> {
let mut result = Vec::with_capacity(self.component_count());
let route_components = self.path.components();
let uri_components = Path::new(uri).components();
for (route_comp, uri_comp) in route_components.zip(uri_components) {
if comp_to_str!(&route_comp).starts_with("<") {
result.push(comp_to_str!(&uri_comp));
}
}
result
}
fn dedup(base: &'static str, route: &'static str) -> String { fn dedup(base: &'static str, route: &'static str) -> String {
let mut deduped = String::with_capacity(base.len() + route.len() + 1); let mut deduped = String::with_capacity(base.len() + route.len() + 1);
@ -56,7 +89,7 @@ impl fmt::Display for Route {
} }
impl Collider for Path { impl Collider for Path {
// FIXME: It's expensive to compute the number of components: O(n) per path // TODO: It's expensive to compute the number of components: O(n) per path
// where n == number of chars. // where n == number of chars.
// //
// Idea: Create a `CachedPath` type that caches the number of components // Idea: Create a `CachedPath` type that caches the number of components

View File

@ -189,10 +189,10 @@ pub fn route_decorator(ecx: &mut ExtCtxt, sp: Span, meta_item: &MetaItem,
debug!("Function Declaration: {:?}", fn_decl); debug!("Function Declaration: {:?}", fn_decl);
let mut fn_param_exprs = vec![]; let mut fn_param_exprs = vec![];
for param in &fn_params { for (i, param) in fn_params.iter().enumerate() {
let param_ident = str_to_ident(param.as_str()); let param_ident = str_to_ident(param.as_str());
let param_fn_item = quote_stmt!(ecx, let param_fn_item = quote_stmt!(ecx,
let $param_ident = match _req.get_param($param) { let $param_ident = match _req.get_param($i) {
Ok(v) => v, Ok(v) => v,
Err(_) => return rocket::Response::not_found() Err(_) => return rocket::Response::not_found()
}; };
@ -230,7 +230,7 @@ pub fn route_decorator(ecx: &mut ExtCtxt, sp: Span, meta_item: &MetaItem,
let method = method_variant_to_expr(ecx, route_params.method); let method = method_variant_to_expr(ecx, route_params.method);
push(Annotatable::Item(quote_item!(ecx, push(Annotatable::Item(quote_item!(ecx,
#[allow(non_upper_case_globals)] #[allow(non_upper_case_globals)]
pub static $struct_name: rocket::Route<'static> = rocket::Route { pub static $struct_name: rocket::Route = rocket::Route {
method: $method, method: $method,
path: $path, path: $path,
handler: $route_fn_name handler: $route_fn_name