mirror of https://github.com/rwf2/Rocket.git
It works! Next steps: clean-up, error handling, docs.
This commit is contained in:
parent
5cdb645fc9
commit
433a9119bd
|
@ -3,36 +3,19 @@
|
|||
|
||||
extern crate rocket;
|
||||
use rocket::Rocket;
|
||||
|
||||
use std::fs::File;
|
||||
|
||||
#[route(GET, path = "/")]
|
||||
fn simple() -> File {
|
||||
fn root() -> File {
|
||||
File::open("/tmp/index.html").unwrap()
|
||||
}
|
||||
|
||||
#[route(GET, path = "/hello/")]
|
||||
fn simple2() -> &'static str {
|
||||
"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 {
|
||||
#[route(GET, path = "/hello/<name>/<age>")]
|
||||
fn hello(name: &str, age: i8) -> String {
|
||||
format!("Hello, {} year old named {}!", age, name)
|
||||
}
|
||||
|
||||
#[route(GET, path = "/something")]
|
||||
fn simple5() -> &'static str {
|
||||
"hi"
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let mut rocket = Rocket::new("localhost", 8000);
|
||||
rocket.mount("/", routes![simple, simple2, simple3, simple4, simple5]);
|
||||
rocket.mount_and_launch("/hello/", routes![simple, simple3, simple4, simple5]);
|
||||
let rocket = Rocket::new("localhost", 8000);
|
||||
rocket.mount_and_launch("/", routes![root, hello]);
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
pub enum Error {
|
||||
BadMethod,
|
||||
BadParse,
|
||||
NoRoute,
|
||||
NoKey
|
||||
}
|
||||
|
|
|
@ -27,15 +27,14 @@ use hyper::Server;
|
|||
|
||||
pub type Handler<'a> = fn(Request) -> Response<'a>;
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Clone)]
|
||||
pub struct Route<'a> {
|
||||
// TODO: Figure out if having Handler<'static> there is a good idea.
|
||||
pub struct Route {
|
||||
pub method: Method,
|
||||
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 {
|
||||
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 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;
|
||||
let uri_str = uri_string.as_str();
|
||||
let route = self.router.route(method, uri_str);
|
||||
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>])
|
||||
-> &mut Self {
|
||||
pub fn mount(&mut self, base: &'static str, routes: &[&Route]) -> &mut Self {
|
||||
println!("🛰 {} '{}':", Magenta.paint("Mounting"), Blue.paint(base));
|
||||
for route in routes {
|
||||
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
|
||||
}
|
||||
|
||||
pub fn mount_and_launch(mut self, base: &'static str,
|
||||
routes: &[&Route<'static>]) {
|
||||
pub fn mount_and_launch(mut self, base: &'static str, routes: &[&Route]) {
|
||||
self.mount(base, routes);
|
||||
self.launch();
|
||||
}
|
||||
|
|
|
@ -1,19 +1,32 @@
|
|||
use error::Error;
|
||||
use param::FromParam;
|
||||
|
||||
pub struct Request;
|
||||
pub struct Request<'a> {
|
||||
params: Vec<&'a str>,
|
||||
uri: &'a str,
|
||||
}
|
||||
|
||||
impl Request {
|
||||
pub fn empty() -> Request {
|
||||
Request
|
||||
impl<'a> Request<'a> {
|
||||
pub fn empty() -> Request<'static> {
|
||||
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)
|
||||
} 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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,8 @@ pub trait Collider<T: ?Sized = Self> {
|
|||
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 (mut i, mut j, delta) = if dir {
|
||||
(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 {
|
||||
break;
|
||||
} else if c1 != c2 {
|
||||
return false;
|
||||
return None;
|
||||
} else {
|
||||
i += 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 {
|
||||
|
@ -42,7 +47,7 @@ macro_rules! comp_to_str {
|
|||
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));
|
||||
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 Method;
|
||||
use Method::*;
|
||||
use {Request, Response};
|
||||
|
||||
type SimpleRoute = (Method, &'static str);
|
||||
|
||||
fn dummy_handler(_req: Request) -> Response<'static> {
|
||||
Response::empty()
|
||||
}
|
||||
|
||||
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))
|
||||
let route_a = Route::new(a.0, "/", a.1, dummy_handler);
|
||||
route_a.collides_with(&Route::new(b.0, "/", b.1, dummy_handler))
|
||||
}
|
||||
|
||||
fn collide(a: &'static str, b: &'static str) -> bool {
|
||||
let route_a = Route::new(Get, "/", a);
|
||||
route_a.collides_with(&Route::new(Get, "/", b))
|
||||
let route_a = Route::new(Get, "/", a, dummy_handler);
|
||||
route_a.collides_with(&Route::new(Get, "/", b, dummy_handler))
|
||||
}
|
||||
|
||||
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 {
|
||||
let route_a = Route::new(Get, "/", a);
|
||||
let route_a = Route::new(Get, "/", a, dummy_handler);
|
||||
route_a.collides_with(b)
|
||||
}
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@ use self::route::Route;
|
|||
use std::collections::hash_map::HashMap;
|
||||
use std::path::Path;
|
||||
use method::Method;
|
||||
use Handler;
|
||||
|
||||
type Selector = (Method, usize);
|
||||
|
||||
|
@ -25,25 +26,28 @@ impl Router {
|
|||
|
||||
// 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);
|
||||
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);
|
||||
}
|
||||
|
||||
// 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) {
|
||||
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)) {
|
||||
for route in routes {
|
||||
if route.collides_with(uri) {
|
||||
for route in routes.iter().filter(|r| r.collides_with(uri)) {
|
||||
println!("Matched {} to: {}", uri, route);
|
||||
if let None = matched_route {
|
||||
matched_route = Some(route);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
matched_route
|
||||
}
|
||||
|
||||
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"]));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,35 +4,68 @@ use std::path::{Path, PathBuf};
|
|||
use std::fmt;
|
||||
use method::Method;
|
||||
use super::Collider; // :D
|
||||
use std::path::Component;
|
||||
use Handler;
|
||||
|
||||
// FIXME: Take in the handler! Or maybe keep that in `Router`?
|
||||
#[derive(Debug)]
|
||||
pub struct Route {
|
||||
method: Method,
|
||||
mount: &'static str,
|
||||
route: &'static str,
|
||||
n_components: usize,
|
||||
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)
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
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 path = PathBuf::from(deduped_path);
|
||||
|
||||
Route {
|
||||
method: m,
|
||||
mount: mount,
|
||||
route: route,
|
||||
n_components: path.components().count(),
|
||||
path: path
|
||||
handler: handler,
|
||||
path: path,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn component_count(&self) -> usize {
|
||||
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 {
|
||||
let mut deduped = String::with_capacity(base.len() + route.len() + 1);
|
||||
|
||||
|
@ -56,7 +89,7 @@ impl fmt::Display for Route {
|
|||
}
|
||||
|
||||
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.
|
||||
//
|
||||
// Idea: Create a `CachedPath` type that caches the number of components
|
||||
|
|
|
@ -189,10 +189,10 @@ pub fn route_decorator(ecx: &mut ExtCtxt, sp: Span, meta_item: &MetaItem,
|
|||
debug!("Function Declaration: {:?}", fn_decl);
|
||||
|
||||
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_fn_item = quote_stmt!(ecx,
|
||||
let $param_ident = match _req.get_param($param) {
|
||||
let $param_ident = match _req.get_param($i) {
|
||||
Ok(v) => v,
|
||||
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);
|
||||
push(Annotatable::Item(quote_item!(ecx,
|
||||
#[allow(non_upper_case_globals)]
|
||||
pub static $struct_name: rocket::Route<'static> = rocket::Route {
|
||||
pub static $struct_name: rocket::Route = rocket::Route {
|
||||
method: $method,
|
||||
path: $path,
|
||||
handler: $route_fn_name
|
||||
|
|
Loading…
Reference in New Issue