diff --git a/lib/src/lib.rs b/lib/src/lib.rs index b3a2457c..941aba54 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -9,16 +9,21 @@ mod error; mod param; mod router; mod rocket; -mod route; pub mod request; pub mod response; +pub mod handler { + use super::{Request, Response}; + + pub type Handler<'a> = fn(Request) -> Response<'a>; +} + pub use request::Request; pub use method::Method; pub use response::{Response, Responder}; pub use error::Error; pub use param::FromParam; -pub use router::Router; -pub use route::{Route, Handler}; +pub use router::{Router, Route}; pub use rocket::Rocket; +pub use handler::Handler; diff --git a/lib/src/rocket.rs b/lib/src/rocket.rs index 699c9b45..408bae1e 100644 --- a/lib/src/rocket.rs +++ b/lib/src/rocket.rs @@ -56,17 +56,20 @@ impl Rocket { } } - pub fn mount(&mut self, base: &'static str, routes: &[&Route]) -> &mut Self { + pub fn mount(&mut self, base: &'static str, routes: Vec) -> &mut Self { println!("🛰 {} '{}':", Magenta.paint("Mounting"), Blue.paint(base)); - for route in routes { + for mut route in routes { + let path = format!("{}/{}", base, route.path.as_str()); + route.set_path(path); + println!("\t* {}", route); - self.router.add_route(route.method, base, route.path, route.handler); + self.router.add(route); } self } - pub fn mount_and_launch(mut self, base: &'static str, routes: &[&Route]) { + pub fn mount_and_launch(mut self, base: &'static str, routes: Vec) { self.mount(base, routes); self.launch(); } diff --git a/lib/src/route.rs b/lib/src/route.rs deleted file mode 100644 index d38ab175..00000000 --- a/lib/src/route.rs +++ /dev/null @@ -1,35 +0,0 @@ -use request::Request; -use response::Response; -use method::Method; - -use std::fmt; -use term_painter::Color::*; -use term_painter::ToStyle; - -pub type Handler<'a> = fn(Request) -> Response<'a>; - -// TODO: Figure out if using 'static for Handler is a good idea. -// TODO: Merge this `Route` and route::Route, somewhow. -pub struct Route { - pub method: Method, - pub path: &'static str, - pub handler: Handler<'static> -} - -impl Route { - pub fn new(method: Method, path: &'static str, handler: Handler<'static>) - -> Route { - Route { - method: method, - path: path, - handler: handler - } - } -} - -impl<'a> fmt::Display for Route { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{} {:?}", Green.paint(&self.method), Blue.paint(&self.path)) - } -} - diff --git a/lib/src/router/collider.rs b/lib/src/router/collider.rs index c0105e2f..e2f92d46 100644 --- a/lib/src/router/collider.rs +++ b/lib/src/router/collider.rs @@ -98,21 +98,21 @@ mod tests { } fn m_collide(a: SimpleRoute, b: SimpleRoute) -> bool { - let route_a = Route::new(a.0, "/", a.1, dummy_handler); - route_a.collides_with(&Route::new(b.0, "/", b.1, dummy_handler)) + let route_a = Route::new(a.0, a.1.to_string(), dummy_handler); + 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, dummy_handler); - route_a.collides_with(&Route::new(Get, "/", b, dummy_handler)) + let route_a = Route::new(Get, a.to_string(), dummy_handler); + route_a.collides_with(&Route::new(Get, b.to_string(), dummy_handler)) } fn s_r_collide(a: &'static str, b: &'static str) -> bool { - a.collides_with(&Route::new(Get, "/", b, dummy_handler)) + a.collides_with(&Route::new(Get, b.to_string(), dummy_handler)) } fn r_s_collide(a: &'static str, b: &'static str) -> bool { - let route_a = Route::new(Get, "/", a, dummy_handler); + let route_a = Route::new(Get, a.to_string(), dummy_handler); route_a.collides_with(b) } diff --git a/lib/src/router/mod.rs b/lib/src/router/mod.rs index e23378b3..46b83bb4 100644 --- a/lib/src/router/mod.rs +++ b/lib/src/router/mod.rs @@ -1,15 +1,15 @@ mod collider; mod route; +mod uri; pub use self::collider::Collider; +pub use self::uri::{URI, URIBuf}; +pub use self::route::Route; 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; -use route::Handler; type Selector = (Method, usize); @@ -24,11 +24,8 @@ impl Router { } } - // FIXME: Take in Handler. - pub fn add_route(&mut self, method: Method, base: &'static str, - route: &'static str, handler: Handler<'static>) { - let route = Route::new(method, base, route, handler); - let selector = (method, route.component_count()); + pub fn add(&mut self, route: Route) { + let selector = (route.method, route.path.segment_count()); self.routes.entry(selector).or_insert(vec![]).push(route); } @@ -38,9 +35,9 @@ impl Router { pub fn route<'b>(&'b self, method: Method, uri: &str) -> Option<&'b Route> { let mut matched_route = None; - let path = Path::new(uri); - let num_components = path.components().count(); - if let Some(routes) = self.routes.get(&(method, num_components)) { + 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 { @@ -73,8 +70,8 @@ impl Router { #[cfg(test)] mod test { - use super::Router; use Method::*; + use super::{Router, Route}; use {Response, Request}; fn dummy_handler(_req: Request) -> Response<'static> { @@ -84,7 +81,8 @@ mod test { fn router_with_routes(routes: &[&'static str]) -> Router { let mut router = Router::new(); for route in routes { - router.add_route(Get, "/", route, dummy_handler); + let route = Route::new(Get, route.to_string(), dummy_handler); + router.add(route); } router @@ -123,9 +121,9 @@ mod test { 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); + router.add(Route::new(Put, "/hello".to_string(), dummy_handler)); + router.add(Route::new(Post, "/hello".to_string(), dummy_handler)); + router.add(Route::new(Delete, "/hello".to_string(), dummy_handler)); assert!(router.route(Put, "/hello").is_some()); assert!(router.route(Post, "/hello").is_some()); assert!(router.route(Delete, "/hello").is_some()); diff --git a/lib/src/router/route.rs b/lib/src/router/route.rs index 69b6d90e..dcf39915 100644 --- a/lib/src/router/route.rs +++ b/lib/src/router/route.rs @@ -1,86 +1,47 @@ use term_painter::ToStyle; use term_painter::Color::*; -use std::path::{Path, PathBuf}; use std::fmt; use method::Method; -use super::Collider; // :D -use std::path::Component; -use route::Handler; +use super::{Collider, URI, URIBuf}; // :D +use handler::Handler; // TODO: Add ranking to routes. Give static routes higher rank by default. // FIXME: Take in the handler! Or maybe keep that in `Router`? pub struct Route { - method: Method, - n_components: usize, + pub method: Method, pub handler: Handler<'static>, - 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) - }; - ) + pub path: URIBuf, } impl Route { - pub fn new(m: Method, mount: &'static str, route: &'static str, - handler: Handler<'static>) -> Route { - let deduped_path = Route::dedup(mount, route); - let path = PathBuf::from(deduped_path); - + pub fn new(m: Method, path: String, handler: Handler<'static>) -> Route { Route { method: m, - n_components: path.components().count(), + path: URIBuf::new(path), handler: handler, - path: path, } } - #[inline] - pub fn component_count(&self) -> usize { - self.n_components + pub fn set_path(&mut self, path: String) { + self.path = URIBuf::new(path); } - // 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: /ab/ or even /:/ // 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(); + let route_components = self.path.segments(); + let uri_components = URI::new(uri).segments(); - 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)); + let mut result = Vec::with_capacity(self.path.segment_count()); + for (route_seg, uri_seg) in route_components.zip(uri_components) { + if route_seg.starts_with("<") { // FIXME: Here. + result.push(uri_seg); } } result } - - fn dedup(base: &'static str, route: &'static str) -> String { - let mut deduped = String::with_capacity(base.len() + route.len() + 1); - - let mut last = '\0'; - for c in base.chars().chain("/".chars()).chain(route.chars()) { - if !(last == '/' && c == '/') { - deduped.push(c); - } - - last = c; - } - - deduped - } } impl fmt::Display for Route { @@ -91,7 +52,8 @@ impl fmt::Display for Route { impl Collider for Route { fn collides_with(&self, b: &Route) -> bool { - if self.n_components != b.n_components || self.method != b.method { + if self.path.segment_count() != b.path.segment_count() + || self.method != b.method { return false; } @@ -101,7 +63,7 @@ impl Collider for Route { impl<'a> Collider for &'a str { fn collides_with(&self, other: &Route) -> bool { - let path = Path::new(self); + let path = URI::new(self); path.collides_with(&other.path) } } diff --git a/lib/src/router/uri.rs b/lib/src/router/uri.rs new file mode 100644 index 00000000..1eef24a2 --- /dev/null +++ b/lib/src/router/uri.rs @@ -0,0 +1,266 @@ +use std::cell::Cell; +use super::Collider; + +#[derive(Debug, Clone)] +pub struct URI<'a> { + path: &'a str, + segment_count: Cell> +} + +impl<'a> URI<'a> { + pub fn new + ?Sized>(path: &'a T) -> URI<'a> { + URI { + segment_count: Cell::new(None), + path: path.as_ref(), + } + } + + pub fn segment_count(&self) -> usize { + self.segment_count.get().unwrap_or_else(|| { + let count = self.segments().count(); + self.segment_count.set(Some(count)); + count + }) + } + + pub fn segments(&self) -> Segments<'a> { + Segments(self.path) + } + + pub fn as_str(&self) -> &'a str { + self.path + } +} + +unsafe impl<'a> Sync for URI<'a> { /* It's safe! */ } + +#[derive(Debug, Clone)] +pub struct URIBuf { + path: String, + segment_count: Cell> +} + +// I don't like repeating all of this stuff. Is there a better way? +impl URIBuf { + pub fn new(path: String) -> URIBuf { + URIBuf { + segment_count: Cell::new(None), + path: path, + } + } + + pub fn segment_count(&self) -> usize { + self.segment_count.get().unwrap_or_else(|| { + println!("Computing!"); + let count = self.segments().count(); + self.segment_count.set(Some(count)); + count + }) + } + + pub fn segments<'a>(&'a self) -> Segments<'a> { + Segments(self.path.as_str()) + } + + pub fn as_uri<'a>(&'a self) -> URI<'a> { + let mut uri = URI::new(self.path.as_str()); + uri.segment_count = self.segment_count.clone(); + uri + } + + pub fn as_str<'a>(&'a self) -> &'a str { + self.path.as_str() + } + + pub fn to_string(&self) -> String { + self.path.clone() + } +} + +unsafe impl Sync for URIBuf { /* It's safe! */ } + +impl<'a> Collider for URI<'a> { + fn collides_with(&self, other: &URI) -> bool { + if self.segment_count() != other.segment_count() { + return false; + } + + for (seg_a, seg_b) in self.segments().zip(other.segments()) { + if !seg_a.collides_with(seg_b) { + return false; + } + } + + return true; + } +} + +impl Collider for URIBuf { + fn collides_with(&self, other: &URIBuf) -> bool { + self.as_uri().collides_with(&other.as_uri()) + } +} + +impl<'a> Collider> for URIBuf { + fn collides_with(&self, other: &URI<'a>) -> bool { + self.as_uri().collides_with(other) + } +} + +impl<'a> Collider for URI<'a> { + fn collides_with(&self, other: &URIBuf) -> bool { + other.as_uri().collides_with(self) + } +} + +pub struct Segments<'a>(&'a str); + +impl<'a> Iterator for Segments<'a> { + type Item = &'a str; + + fn next(&mut self) -> Option { + // Find the start of the next segment (first that's not '/'). + let s = self.0.chars().enumerate().skip_while(|&(_, c)| c == '/').next(); + if s.is_none() { + return None; + } + + // i is the index of the first character that's not '/' + let (i, _) = s.unwrap(); + + // Get the index of the first character that _is_ a '/' after start. + // j = index of first character after i (hence the i +) that's not a '/' + let rest = &self.0[i..]; + let mut end_iter = rest.chars().enumerate().skip_while(|&(_, c)| c != '/'); + let j = end_iter.next().map_or(self.0.len(), |(j, _)| i + j); + + // Save the result, update the iterator, and return! + let result = Some(&self.0[i..j]); + self.0 = &self.0[j..]; + result + } + + // TODO: Potentially take a second parameter with Option and + // return it here if it's Some. The downside is that a decision has to be + // made about -when- to compute and cache that count. A place to do it is in + // the segments() method. But this means that the count will always be + // computed regardless of whether it's needed. Maybe this is ok. We'll see. + // fn count(self) -> usize where Self: Sized { + // self.1.unwrap_or_else(self.fold(0, |cnt, _| cnt + 1)) + // } +} + +#[cfg(test)] +mod tests { + use super::{URI, URIBuf}; + + fn seg_count(path: &str, expected: usize) -> bool { + let actual = URI::new(path).segment_count(); + let actual_buf = URIBuf::new(path.to_string()).segment_count(); + if actual != expected || actual_buf != expected { + println!("Count mismatch: expected {}, got {}.", expected, actual); + println!("{}", if actual != expected { "lifetime" } else { "buf" }); + println!("Segments (for {}):", path); + for (i, segment) in URI::new(path).segments().enumerate() { + println!("\t{}: {}", i, segment); + } + } + + actual == expected && actual_buf == expected + } + + fn eq_segments(path: &str, expected: &[&str]) -> bool { + let uri = URI::new(path); + let actual: Vec<&str> = uri.segments().collect(); + + let uri_buf = URIBuf::new(path.to_string()); + let actual_buf: Vec<&str> = uri_buf.segments().collect(); + + actual == expected && actual_buf == expected + } + + #[test] + fn simple_segment_count() { + assert!(seg_count("", 0)); + assert!(seg_count("/", 0)); + assert!(seg_count("a", 1)); + assert!(seg_count("/a", 1)); + assert!(seg_count("a/", 1)); + assert!(seg_count("/a/", 1)); + assert!(seg_count("/a/b", 2)); + assert!(seg_count("/a/b/", 2)); + assert!(seg_count("a/b/", 2)); + assert!(seg_count("ab/", 1)); + } + + #[test] + fn segment_count() { + assert!(seg_count("////", 0)); + assert!(seg_count("//a//", 1)); + assert!(seg_count("//abc//", 1)); + assert!(seg_count("//abc/def/", 2)); + assert!(seg_count("//////abc///def//////////", 2)); + assert!(seg_count("a/b/c/d/e/f/g", 7)); + assert!(seg_count("/a/b/c/d/e/f/g", 7)); + assert!(seg_count("/a/b/c/d/e/f/g/", 7)); + assert!(seg_count("/a/b/cdjflk/d/e/f/g", 7)); + assert!(seg_count("//aaflja/b/cdjflk/d/e/f/g", 7)); + assert!(seg_count("/a /b", 2)); + } + + #[test] + fn single_segments_match() { + assert!(eq_segments("", &[])); + assert!(eq_segments("a", &["a"])); + assert!(eq_segments("/a", &["a"])); + assert!(eq_segments("/a/", &["a"])); + assert!(eq_segments("a/", &["a"])); + assert!(eq_segments("///a/", &["a"])); + assert!(eq_segments("///a///////", &["a"])); + assert!(eq_segments("a///////", &["a"])); + assert!(eq_segments("//a", &["a"])); + assert!(eq_segments("", &[])); + assert!(eq_segments("abc", &["abc"])); + assert!(eq_segments("/a", &["a"])); + assert!(eq_segments("/abc/", &["abc"])); + assert!(eq_segments("abc/", &["abc"])); + assert!(eq_segments("///abc/", &["abc"])); + assert!(eq_segments("///abc///////", &["abc"])); + assert!(eq_segments("abc///////", &["abc"])); + assert!(eq_segments("//abc", &["abc"])); + } + + #[test] + fn multi_segments_match() { + assert!(eq_segments("a/b/c", &["a", "b", "c"])); + assert!(eq_segments("/a/b", &["a", "b"])); + assert!(eq_segments("/a///b", &["a", "b"])); + assert!(eq_segments("a/b/c/d", &["a", "b", "c", "d"])); + assert!(eq_segments("///a///////d////c", &["a", "d", "c"])); + assert!(eq_segments("abc/abc", &["abc", "abc"])); + assert!(eq_segments("abc/abc/", &["abc", "abc"])); + assert!(eq_segments("///abc///////a", &["abc", "a"])); + assert!(eq_segments("/////abc/b", &["abc", "b"])); + assert!(eq_segments("//abc//c////////d", &["abc", "c", "d"])); + } + + #[test] + fn multi_segments_match_funky_chars() { + assert!(eq_segments("a/b/c!!!", &["a", "b", "c!!!"])); + assert!(eq_segments("a /b", &["a ", "b"])); + assert!(eq_segments(" a/b", &[" a", "b"])); + assert!(eq_segments(" a/b ", &[" a", "b "])); + assert!(eq_segments(" a///b ", &[" a", "b "])); + assert!(eq_segments(" ab ", &[" ab "])); + } + + #[test] + fn segment_mismatch() { + assert!(!eq_segments("", &["a"])); + assert!(!eq_segments("a", &[])); + assert!(!eq_segments("/a/a", &["a"])); + assert!(!eq_segments("/a/b", &["b", "a"])); + assert!(!eq_segments("/a/a/b", &["a", "b"])); + assert!(!eq_segments("///a/", &[])); + } +}