diff --git a/lib/Cargo.toml b/lib/Cargo.toml index 7c75dbcc..50e3b3f5 100644 --- a/lib/Cargo.toml +++ b/lib/Cargo.toml @@ -5,3 +5,4 @@ authors = ["Sergio Benitez "] [dependencies] hyper = "*" +term-painter = "*" diff --git a/lib/src/lib.rs b/lib/src/lib.rs index cc9721fc..8dd77b0c 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -1,5 +1,6 @@ #![feature(str_char)] +extern crate term_painter; extern crate hyper; pub mod method; @@ -16,6 +17,10 @@ pub use request::Request; pub use param::FromParam; pub use router::Router; +use std::fmt; +use term_painter::ToStyle; +use term_painter::Color::*; +use hyper::uri::RequestUri; use hyper::server::Handler as HypHandler; use hyper::server::Request as HypRequest; use hyper::Server; @@ -30,23 +35,32 @@ pub struct Route<'a> { pub handler: Handler<'a> } +impl<'a> fmt::Display for Route<'a> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{} {:?}", Green.paint(&self.method), Blue.paint(&self.path)) + } +} + #[allow(dead_code)] pub struct Rocket { address: &'static str, port: isize, - handler: Option>, // just for testing router: Router } impl HypHandler for Rocket { fn handle<'a, 'k>(&'a self, req: HypRequest<'a, 'k>, res: HypResponse<'a, HypFresh>) { - println!("Request: {:?}", req.uri); - if self.handler.is_some() { - let handler = self.handler.as_ref(); - let mut response = (handler.unwrap().handler)(Request::empty()); - response.body.respond(res); + if let RequestUri::AbsolutePath(uri_string) = req.uri { + if let Some(method) = Method::from_hyp(req.method) { + println!("Request: {:?}", uri_string); + self.router.route(method, uri_string.as_str()); + res.send(b"Hello, world!").unwrap(); + return; + } } + + Response::not_found().body.respond(res); } } @@ -55,21 +69,15 @@ impl Rocket { Rocket { address: address, port: port, - handler: None, router: Router::new() } } pub fn mount(&mut self, base: &'static str, routes: &[&Route<'static>]) -> &mut Self { - println!("🛰 Mounting '{}':", base); + println!("🛰 {} '{}':", Magenta.paint("Mounting"), Blue.paint(base)); for route in routes { - if self.handler.is_none() { - println!("\t* INSTALLED: {} '{}'", route.method, route.path); - self.handler = Some((*route).clone()); - } - - println!("\t* {} '{}'", route.method, route.path); + println!("\t* {}", route); self.router.add_route(route.method.clone(), base, route.path); } @@ -84,11 +92,12 @@ impl Rocket { pub fn launch(self) { if self.router.has_collisions() { - println!("Warning: route collisions detected!"); + println!("{}", Yellow.paint("Warning: route collisions detected!")); } let full_addr = format!("{}:{}", self.address, self.port); - println!("🚀 Rocket has launched from {}...", full_addr); + println!("🚀 {} {}...", White.paint("Rocket has launched from"), + White.bold().paint(&full_addr)); let _ = Server::http(full_addr.as_str()).unwrap().handle(self); } } diff --git a/lib/src/method.rs b/lib/src/method.rs index 80680141..d3d1f41a 100644 --- a/lib/src/method.rs +++ b/lib/src/method.rs @@ -2,8 +2,9 @@ use self::Method::*; use std::str::FromStr; use std::fmt::{self, Display}; use error::Error; +use hyper::method::Method as HypMethod; -#[derive(Clone, PartialEq, Eq, Hash, Debug)] +#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] pub enum Method { Get, Put, @@ -16,16 +17,25 @@ pub enum Method { Patch } +impl Method { + pub fn from_hyp(method: HypMethod) -> Option { + match method { + HypMethod::Get => Some(Get), + _ => None + } + } +} + impl FromStr for Method { type Err = Error; fn from_str(s: &str) -> Result { match s { - "OPTIONS" => Ok(Options), "GET" => Ok(Get), - "POST" => Ok(Post), "PUT" => Ok(Put), + "POST" => Ok(Post), "DELETE" => Ok(Delete), + "OPTIONS" => Ok(Options), "HEAD" => Ok(Head), "TRACE" => Ok(Trace), "CONNECT" => Ok(Connect), @@ -38,11 +48,11 @@ impl FromStr for Method { impl Display for Method { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { fmt.write_str(match *self { - Options => "OPTIONS", Get => "GET", - Post => "POST", Put => "PUT", + Post => "POST", Delete => "DELETE", + Options => "OPTIONS", Head => "HEAD", Trace => "TRACE", Connect => "CONNECT", diff --git a/lib/src/response.rs b/lib/src/response.rs index c9be8a3a..9c9c51a0 100644 --- a/lib/src/response.rs +++ b/lib/src/response.rs @@ -89,10 +89,9 @@ impl Responder for File { } } +// TODO: Allow streamed responses. // const CHUNK_SIZE: u32 = 4096; - // pub struct Stream(T); - // impl Responder for Stream { // fn respond<'a>(&self, mut r: HypResponse<'a, HypFresh>) { // r.headers_mut().set(header::TransferEncoding(vec![Encoding::Chunked])); @@ -107,41 +106,3 @@ impl Responder for File { // } // } // } - -// macro_rules! impl_from_lengthed { -// ($name:ident, $T:ty) => ( -// impl<'a> From<$T> for Response<'a> { -// fn from(s: $T) -> Self { -// Response { -// status: StatusCode::Ok, -// headers: Headers::new(), -// body: Body::$name(s) -// } -// } -// } -// ) -// } - -// impl_from_lengthed!(Str, &'a str); -// impl_from_lengthed!(String, String); -// impl_from_lengthed!(Bytes, &'a [u8]); -// impl_from_lengthed!(File, File); - -// macro_rules! impl_from_reader { -// ($T:ty) => ( -// impl<'a> From<&'a $T> for Response<'a> { -// fn from(r: &'a $T) -> Self { -// let mut headers = Headers::new(); -// headers.set(header::TransferEncoding(vec![Encoding::Chunked])); -// Response { -// status: StatusCode::Ok, -// headers: headers, -// body: Body::Stream(r) -// } -// } -// } -// ) -// } - -// impl_from_reader!(File); -// impl_from_reader!(&'a [u8]); diff --git a/lib/src/router/collider.rs b/lib/src/router/collider.rs index 2f172262..25ac9470 100644 --- a/lib/src/router/collider.rs +++ b/lib/src/router/collider.rs @@ -65,10 +65,15 @@ mod tests { route_a.collides_with(&Route::new(Get, "/", b)) } - fn s_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)) } + fn r_s_collide(a: &'static str, b: &'static str) -> bool { + let route_a = Route::new(Get, "/", a); + route_a.collides_with(b) + } + #[test] fn simple_collisions() { assert!(collide("a", "a")); @@ -136,18 +141,30 @@ mod tests { #[test] fn test_str_non_collisions() { - assert!(!s_collide("/a", "/b")); - assert!(!s_collide("/a/b", "/a")); - assert!(!s_collide("/a/b", "/a/c")); - assert!(!s_collide("/a/hello", "/a/c")); - assert!(!s_collide("/hello", "/a/c")); - assert!(!s_collide("/hello/there", "/hello/there/guy")); - assert!(!s_collide("/b/there", "/hi/there")); - assert!(!s_collide("//c", "/hi/person")); - assert!(!s_collide("//cd", "/hi/e")); - assert!(!s_collide("/a/", "/b/")); - assert!(!s_collide("/a/", "/b/")); - assert!(!s_collide("/a/", "/b/")); + assert!(!s_r_collide("/a", "/b")); + assert!(!s_r_collide("/a/b", "/a")); + assert!(!s_r_collide("/a/b", "/a/c")); + assert!(!s_r_collide("/a/hello", "/a/c")); + assert!(!s_r_collide("/hello", "/a/c")); + assert!(!s_r_collide("/hello/there", "/hello/there/guy")); + assert!(!s_r_collide("/b/there", "/hi/there")); + assert!(!s_r_collide("//c", "/hi/person")); + assert!(!s_r_collide("//cd", "/hi/e")); + assert!(!s_r_collide("/a/", "/b/")); + assert!(!s_r_collide("/a/", "/b/")); + assert!(!s_r_collide("/a/", "/b/")); + assert!(!r_s_collide("/a", "/b")); + assert!(!r_s_collide("/a/b", "/a")); + assert!(!r_s_collide("/a/b", "/a/c")); + assert!(!r_s_collide("/a/hello", "/a/c")); + assert!(!r_s_collide("/hello", "/a/c")); + assert!(!r_s_collide("/hello/there", "/hello/there/guy")); + assert!(!r_s_collide("/b/there", "/hi/there")); + assert!(!r_s_collide("//c", "/hi/person")); + assert!(!r_s_collide("//cd", "/hi/e")); + assert!(!r_s_collide("/a/", "/b/")); + assert!(!r_s_collide("/a/", "/b/")); + assert!(!r_s_collide("/a/", "/b/")); } fn test_str_collisions() { diff --git a/lib/src/router/mod.rs b/lib/src/router/mod.rs index f3909bb0..69b2f3de 100644 --- a/lib/src/router/mod.rs +++ b/lib/src/router/mod.rs @@ -3,52 +3,59 @@ mod route; pub use self::collider::Collider; +use term_painter::ToStyle; +use term_painter::Color::*; use self::route::Route; use std::collections::hash_map::HashMap; +use std::path::Path; use method::Method; +type Selector = (Method, usize); + pub struct Router { - routes: Vec // for now, to check for collisions + routes: HashMap> // for now } impl Router { pub fn new() -> Router { Router { - routes: Vec::new() + routes: HashMap::new() } } + // FIXME: Take in Handler. pub fn add_route(&mut self, method: Method, base: &'static str, route: &'static str) { let route = Route::new(method, base, route); - self.routes.push(route); + let selector = (method, route.component_count()); + self.routes.entry(selector).or_insert(vec![]).push(route); + } + + // TODO: Make a `Router` trait with this function. Rename this `Router` + // struct to something like `RocketRouter`. + // TODO: Return an array of matches to the parameters. + pub fn route<'a>(&self, method: Method, uri: &'a str) { + let path = Path::new(uri); + let num_components = path.components().count(); + if let Some(routes) = self.routes.get(&(method, num_components)) { + for route in routes { + if route.collides_with(uri) { + println!("Matched {} to: {}", uri, route); + } + } + } } pub fn has_collisions(&self) -> bool { - let mut map: HashMap> = HashMap::new(); - - for route in &self.routes { - let num_components = route.component_count(); - let mut list = if map.contains_key(&num_components) { - map.get_mut(&num_components).unwrap() - } else { - map.insert(num_components, Vec::new()); - map.get_mut(&num_components).unwrap() - }; - - list.push(&route); - } - let mut result = false; - for (_, routes) in map { + for (_, routes) in &self.routes { for i in 0..routes.len() { - for j in 0..routes.len() { - if i == j { continue } - + for j in (i + 1)..routes.len() { let (a_route, b_route) = (&routes[i], &routes[j]); if a_route.collides_with(b_route) { result = true; - println!("{:?} and {:?} collide!", a_route, b_route); + println!("{} {} and {} collide!", + Yellow.bold().paint("Warning:"), a_route, b_route); } } } diff --git a/lib/src/router/route.rs b/lib/src/router/route.rs index 39d6799d..830b7475 100644 --- a/lib/src/router/route.rs +++ b/lib/src/router/route.rs @@ -1,7 +1,11 @@ +use term_painter::ToStyle; +use term_painter::Color::*; use std::path::{Path, PathBuf}; +use std::fmt; use method::Method; use super::Collider; // :D +// FIXME: Take in the handler! Or maybe keep that in `Router`? #[derive(Debug)] pub struct Route { method: Method, @@ -45,10 +49,15 @@ impl Route { } } +impl fmt::Display for Route { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{} {:?}", Green.paint(&self.method), Blue.paint(&self.path)) + } +} + impl Collider for Path { - // FIXME: This assume that a and b have the same number of componenets. - // This is because it's expensive to compute the number of componenets: O(n) - // per path where n == number of chars. + // FIXME: 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. @@ -87,3 +96,9 @@ impl<'a> Collider for &'a str { path.collides_with(&other.path) } } + +impl Collider for Route { + fn collides_with(&self, other: &str) -> bool { + other.collides_with(self) + } +}