Major refactoring. Merged two `Route` structure. Created `URI` and `URIBuf`.

This commit is contained in:
Sergio Benitez 2016-04-02 00:51:40 -07:00
parent 50bc0d6999
commit 5fb12485e8
7 changed files with 318 additions and 119 deletions

View File

@ -9,16 +9,21 @@ mod error;
mod param; mod param;
mod router; mod router;
mod rocket; mod rocket;
mod route;
pub mod request; pub mod request;
pub mod response; pub mod response;
pub mod handler {
use super::{Request, Response};
pub type Handler<'a> = fn(Request) -> Response<'a>;
}
pub use request::Request; pub use request::Request;
pub use method::Method; pub use method::Method;
pub use response::{Response, Responder}; pub use response::{Response, Responder};
pub use error::Error; pub use error::Error;
pub use param::FromParam; pub use param::FromParam;
pub use router::Router; pub use router::{Router, Route};
pub use route::{Route, Handler};
pub use rocket::Rocket; pub use rocket::Rocket;
pub use handler::Handler;

View File

@ -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<Route>) -> &mut Self {
println!("🛰 {} '{}':", Magenta.paint("Mounting"), Blue.paint(base)); 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); println!("\t* {}", route);
self.router.add_route(route.method, base, route.path, route.handler); self.router.add(route);
} }
self 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<Route>) {
self.mount(base, routes); self.mount(base, routes);
self.launch(); self.launch();
} }

View File

@ -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))
}
}

View File

@ -98,21 +98,21 @@ mod tests {
} }
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, 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, 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 { fn 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(&Route::new(Get, "/", b, 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 { 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 { 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) route_a.collides_with(b)
} }

View File

@ -1,15 +1,15 @@
mod collider; mod collider;
mod route; mod route;
mod uri;
pub use self::collider::Collider; pub use self::collider::Collider;
pub use self::uri::{URI, URIBuf};
pub use self::route::Route;
use term_painter::ToStyle; use term_painter::ToStyle;
use term_painter::Color::*; use term_painter::Color::*;
use self::route::Route;
use std::collections::hash_map::HashMap; use std::collections::hash_map::HashMap;
use std::path::Path;
use method::Method; use method::Method;
use route::Handler;
type Selector = (Method, usize); type Selector = (Method, usize);
@ -24,11 +24,8 @@ impl Router {
} }
} }
// FIXME: Take in Handler. pub fn add(&mut self, route: Route) {
pub fn add_route(&mut self, method: Method, base: &'static str, let selector = (route.method, route.path.segment_count());
route: &'static str, handler: Handler<'static>) {
let route = Route::new(method, base, route, handler);
let selector = (method, route.component_count());
self.routes.entry(selector).or_insert(vec![]).push(route); 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> { pub fn route<'b>(&'b self, method: Method, uri: &str) -> Option<&'b Route> {
let mut matched_route = None; let mut matched_route = None;
let path = Path::new(uri); let path = URI::new(uri);
let num_components = path.components().count(); let num_segments = path.segment_count();
if let Some(routes) = self.routes.get(&(method, num_components)) { if let Some(routes) = self.routes.get(&(method, num_segments)) {
for route in routes.iter().filter(|r| r.collides_with(uri)) { for route in routes.iter().filter(|r| r.collides_with(uri)) {
println!("\t=> Matched {} to: {}", uri, route); println!("\t=> Matched {} to: {}", uri, route);
if let None = matched_route { if let None = matched_route {
@ -73,8 +70,8 @@ impl Router {
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use super::Router;
use Method::*; use Method::*;
use super::{Router, Route};
use {Response, Request}; use {Response, Request};
fn dummy_handler(_req: Request) -> Response<'static> { fn dummy_handler(_req: Request) -> Response<'static> {
@ -84,7 +81,8 @@ mod test {
fn router_with_routes(routes: &[&'static str]) -> Router { fn router_with_routes(routes: &[&'static str]) -> Router {
let mut router = Router::new(); let mut router = Router::new();
for route in routes { 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 router
@ -123,9 +121,9 @@ mod test {
assert!(router.route(Get, "/jdlk/asdij").is_some()); assert!(router.route(Get, "/jdlk/asdij").is_some());
let mut router = Router::new(); let mut router = Router::new();
router.add_route(Put, "/", "/hello", dummy_handler); router.add(Route::new(Put, "/hello".to_string(), dummy_handler));
router.add_route(Post, "/", "/hello", dummy_handler); router.add(Route::new(Post, "/hello".to_string(), dummy_handler));
router.add_route(Delete, "/", "/hello", dummy_handler); router.add(Route::new(Delete, "/hello".to_string(), dummy_handler));
assert!(router.route(Put, "/hello").is_some()); assert!(router.route(Put, "/hello").is_some());
assert!(router.route(Post, "/hello").is_some()); assert!(router.route(Post, "/hello").is_some());
assert!(router.route(Delete, "/hello").is_some()); assert!(router.route(Delete, "/hello").is_some());

View File

@ -1,86 +1,47 @@
use term_painter::ToStyle; use term_painter::ToStyle;
use term_painter::Color::*; use term_painter::Color::*;
use std::path::{Path, PathBuf};
use std::fmt; use std::fmt;
use method::Method; use method::Method;
use super::Collider; // :D use super::{Collider, URI, URIBuf}; // :D
use std::path::Component; use handler::Handler;
use route::Handler;
// TODO: Add ranking to routes. Give static routes higher rank by default. // TODO: Add ranking to routes. Give static routes higher rank by default.
// FIXME: Take in the handler! Or maybe keep that in `Router`? // FIXME: Take in the handler! Or maybe keep that in `Router`?
pub struct Route { pub struct Route {
method: Method, pub method: Method,
n_components: usize,
pub handler: Handler<'static>, pub handler: Handler<'static>,
path: PathBuf pub path: URIBuf,
}
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, pub fn new(m: Method, path: String, handler: Handler<'static>) -> Route {
handler: Handler<'static>) -> Route {
let deduped_path = Route::dedup(mount, route);
let path = PathBuf::from(deduped_path);
Route { Route {
method: m, method: m,
n_components: path.components().count(), path: URIBuf::new(path),
handler: handler, handler: handler,
path: path,
} }
} }
#[inline] pub fn set_path(&mut self, path: String) {
pub fn component_count(&self) -> usize { self.path = URIBuf::new(path);
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 // FIXME: Decide whether a component has to be fully variable or not. That
// is, whether you can have: /a<a>b/ or even /<a>:<b>/ // is, whether you can have: /a<a>b/ or even /<a>:<b>/
// TODO: Don't return a Vec...take in an &mut [&'a str] (no alloc!) // 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> { 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.segments();
let route_components = self.path.components(); let uri_components = URI::new(uri).segments();
let uri_components = Path::new(uri).components();
for (route_comp, uri_comp) in route_components.zip(uri_components) { let mut result = Vec::with_capacity(self.path.segment_count());
if comp_to_str!(&route_comp).starts_with("<") { for (route_seg, uri_seg) in route_components.zip(uri_components) {
result.push(comp_to_str!(&uri_comp)); if route_seg.starts_with("<") { // FIXME: Here.
result.push(uri_seg);
} }
} }
result 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 { impl fmt::Display for Route {
@ -91,7 +52,8 @@ impl fmt::Display for Route {
impl Collider for Route { impl Collider for Route {
fn collides_with(&self, b: &Route) -> bool { 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; return false;
} }
@ -101,7 +63,7 @@ impl Collider for Route {
impl<'a> Collider<Route> for &'a str { impl<'a> Collider<Route> for &'a str {
fn collides_with(&self, other: &Route) -> bool { fn collides_with(&self, other: &Route) -> bool {
let path = Path::new(self); let path = URI::new(self);
path.collides_with(&other.path) path.collides_with(&other.path)
} }
} }

266
lib/src/router/uri.rs Normal file
View File

@ -0,0 +1,266 @@
use std::cell::Cell;
use super::Collider;
#[derive(Debug, Clone)]
pub struct URI<'a> {
path: &'a str,
segment_count: Cell<Option<usize>>
}
impl<'a> URI<'a> {
pub fn new<T: AsRef<str> + ?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<Option<usize>>
}
// 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<URI<'a>> for URIBuf {
fn collides_with(&self, other: &URI<'a>) -> bool {
self.as_uri().collides_with(other)
}
}
impl<'a> Collider<URIBuf> 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<Self::Item> {
// 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<cached count> 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/", &[]));
}
}