From b2fbf0d6bd1e7eafddf21fd185b11ec882c30d40 Mon Sep 17 00:00:00 2001 From: Sergio Benitez Date: Sun, 20 Mar 2016 21:27:17 -0700 Subject: [PATCH] Split up router file into multiple files. Route Collider for strings to Route. --- lib/src/lib.rs | 1 - lib/src/{router.rs => router/collider.rs} | 203 ++++++---------------- lib/src/router/mod.rs | 60 +++++++ lib/src/router/route.rs | 89 ++++++++++ 4 files changed, 203 insertions(+), 150 deletions(-) rename lib/src/{router.rs => router/collider.rs} (51%) create mode 100644 lib/src/router/mod.rs create mode 100644 lib/src/router/route.rs diff --git a/lib/src/lib.rs b/lib/src/lib.rs index 5eb7de4d..cc9721fc 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -36,7 +36,6 @@ pub struct Rocket { port: isize, handler: Option>, // just for testing router: Router - // mounts: HashMap<&'static str, Route<'a>> } impl HypHandler for Rocket { diff --git a/lib/src/router.rs b/lib/src/router/collider.rs similarity index 51% rename from lib/src/router.rs rename to lib/src/router/collider.rs index b49fd186..2f172262 100644 --- a/lib/src/router.rs +++ b/lib/src/router/collider.rs @@ -1,15 +1,32 @@ -use method::Method; -use std::path::{PathBuf, Component}; -use std::collections::hash_map::HashMap; +use std::path::Component; -// FIXME: Split this up into multiple files: Collider, Route, Router, etc. - -// FIXME: Implement the following: -// Collider for &str; -trait Collider { +pub trait Collider { fn collides_with(&self, other: &T) -> bool; } +fn check_match_until(break_c: char, a: &str, b: &str, dir: bool) -> bool { + let (a_len, b_len) = (a.len() as isize, b.len() as isize); + let (mut i, mut j, delta) = if dir { + (0, 0, 1) + } else { + (a_len - 1, b_len - 1, -1) + }; + + while i >= 0 && j >= 0 && i < a_len && j < b_len { + let (c1, c2) = (a.char_at(i as usize), b.char_at(j as usize)); + if c1 == break_c || c2 == break_c { + break; + } else if c1 != c2 { + return false; + } else { + i += delta; + j += delta; + } + } + + return true; +} + macro_rules! comp_to_str { ($component:expr) => ( match $component { @@ -22,29 +39,6 @@ macro_rules! comp_to_str { ) } -fn check_match_until(c: char, a: &str, b: &str, dir: bool) -> bool { - let (a_len, b_len) = (a.len() as isize, b.len() as isize); - let (mut i, mut j, delta) = if dir { - (0, 0, 1) - } else { - (a_len - 1, b_len - 1, -1) - }; - - while i >= 0 && j >= 0 && i < a_len && j < b_len { - let (c1, c2) = (a.char_at(i as usize), b.char_at(j as usize)); - if c1 == c || c2 == c { - break; - } else if c1 != c2 { - return false; - } else { - i += delta; - j += delta; - } - } - - return true; -} - impl<'a> Collider for Component<'a> { fn collides_with(&self, other: &Component<'a>) -> bool { let (a, b) = (comp_to_str!(self), comp_to_str!(other)); @@ -52,129 +46,16 @@ impl<'a> Collider for Component<'a> { } } -#[derive(Debug)] -struct Route { - method: Method, - mount: &'static str, - route: &'static str, - n_components: usize, - path: PathBuf -} - -pub struct Router { - routes: Vec // for now, to check for collisions -} - -impl Route { - fn new(m: Method, mount: &'static str, route: &'static str) -> Route { - let deduped_path = Route::dedup(mount, route); - let path = PathBuf::from(deduped_path); - - Route { - method: m, - mount: mount, - route: route, - n_components: path.components().count(), - path: path - } - } - - 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 Collider for Route { - fn collides_with(&self, b: &Route) -> bool { - if self.n_components != b.n_components || self.method != b.method { - return false; - } - - let mut matches = 0; - let mut a_components = self.path.components(); - let mut b_components = b.path.components(); - while let Some(ref c1) = a_components.next() { - if let Some(ref c2) = b_components.next() { - if c1.collides_with(c2) { - matches += 1; - } - } - } - - // println!("Checked {:?} against {:?}: {}/{}", a, b, matches, n); - matches == self.n_components - } -} - -// TODO: Are /hello and /hello/ the same? Or not? Currently, they're treated as -// such by Path, which is passed on to the collisions stuff. -impl Router { - pub fn new() -> Router { - Router { - routes: Vec::new() - } - } - - // TODO: Use `method` argument - pub fn add_route(&mut self, method: Method, base: &'static str, - route: &'static str) { - let route = Route::new(method, base, route); - println!("Mounted: {:?}", route); - self.routes.push(route); - } - - pub fn has_collisions(&self) -> bool { - let mut map: HashMap> = HashMap::new(); - - for route in &self.routes { - let num_components = route.path.components().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 i in 0..routes.len() { - for j in 0..routes.len() { - if i == j { continue } - - 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); - } - } - } - } - - result - } -} - #[cfg(test)] mod tests { - use super::{Route, Collider}; + use router::Collider; + use router::route::Route; use Method; use Method::*; - fn m_collide(a: (Method, &'static str), b: (Method, &'static str)) -> bool { + type SimpleRoute = (Method, &'static str); + + fn m_collide(a: SimpleRoute, b: SimpleRoute) -> bool { let route_a = Route::new(a.0, "/", a.1); route_a.collides_with(&Route::new(b.0, "/", b.1)) } @@ -184,6 +65,10 @@ mod tests { route_a.collides_with(&Route::new(Get, "/", b)) } + fn s_collide(a: &'static str, b: &'static str) -> bool { + a.collides_with(&Route::new(Get, "/", b)) + } + #[test] fn simple_collisions() { assert!(collide("a", "a")); @@ -248,4 +133,24 @@ mod tests { assert!(!m_collide((Get, "/a"), (Put, "/"))); assert!(!m_collide((Get, "/hello"), (Put, "/hello"))); } + + #[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/")); + } + + fn test_str_collisions() { + // FIXME: Write these. + } } diff --git a/lib/src/router/mod.rs b/lib/src/router/mod.rs new file mode 100644 index 00000000..f3909bb0 --- /dev/null +++ b/lib/src/router/mod.rs @@ -0,0 +1,60 @@ +mod collider; +mod route; + +pub use self::collider::Collider; + +use self::route::Route; +use std::collections::hash_map::HashMap; +use method::Method; + +pub struct Router { + routes: Vec // for now, to check for collisions +} + +impl Router { + pub fn new() -> Router { + Router { + routes: Vec::new() + } + } + + 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); + } + + 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 i in 0..routes.len() { + for j in 0..routes.len() { + if i == j { continue } + + 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); + } + } + } + } + + result + } +} + diff --git a/lib/src/router/route.rs b/lib/src/router/route.rs new file mode 100644 index 00000000..39d6799d --- /dev/null +++ b/lib/src/router/route.rs @@ -0,0 +1,89 @@ +use std::path::{Path, PathBuf}; +use method::Method; +use super::Collider; // :D + +#[derive(Debug)] +pub struct Route { + method: Method, + mount: &'static str, + route: &'static str, + n_components: usize, + path: PathBuf +} + +impl Route { + pub fn new(m: Method, mount: &'static str, route: &'static str) -> Route { + let deduped_path = Route::dedup(mount, route); + let path = PathBuf::from(deduped_path); + + Route { + method: m, + mount: mount, + route: route, + n_components: path.components().count(), + path: path + } + } + + pub fn component_count(&self) -> usize { + self.n_components + } + + 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 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. + // + // Idea: Create a `CachedPath` type that caches the number of components + // similar to the way `Route` does it. + fn collides_with(&self, b: &Path) -> bool { + if self.components().count() != b.components().count() { + return false; + } + + let mut a_components = self.components(); + let mut b_components = b.components(); + while let Some(ref c1) = a_components.next() { + if let Some(ref c2) = b_components.next() { + if !c1.collides_with(c2) { + return false + } + } + } + + true + } +} + +impl Collider for Route { + fn collides_with(&self, b: &Route) -> bool { + if self.n_components != b.n_components || self.method != b.method { + return false; + } + + self.path.collides_with(&b.path) + } +} + +impl<'a> Collider for &'a str { + fn collides_with(&self, other: &Route) -> bool { + let path = Path::new(self); + path.collides_with(&other.path) + } +}