HRTB for the win! Manual routes example fully working.

This commit is contained in:
Sergio Benitez 2016-04-03 03:36:30 -07:00
parent 72329a7145
commit 3dfa049a1a
11 changed files with 92 additions and 49 deletions

View File

@ -5,4 +5,3 @@ authors = ["Sergio Benitez <sb@sergio.bz>"]
[dependencies]
rocket = { path = "../../lib" }
rocket_macros = { path = "../../macros" }

View File

@ -3,23 +3,25 @@ extern crate rocket;
use rocket::{Rocket, Request, Response, Route};
use rocket::Method::*;
fn root(req: Request) -> Response<'static> {
fn root(req: Request) -> Response {
let name = req.get_param(0).unwrap_or("unnamed");
Response::new(format!("Hello, {}!", name))
}
// TODO: Work with these lifetimes.
#[allow(dead_code)]
fn lifetime_root<'a>(req: Request<'a>) -> Response<'a> {
Response::new(req.get_uri())
fn echo_url<'a>(req: Request<'a>) -> Response<'a> {
Response::new(req.get_uri().split_at(6).1)
}
fn main() {
let mut rocket = Rocket::new("localhost", 8000);
let first = Route::new(Get, "/hello", root);
let second = Route::new(Get, "/hello/<any>", root);
Rocket::new("localhost", 8000).mount_and_launch("/", &[&first, &second]);
rocket.mount("/", vec![first, second]);
// This below _should_ work.
// let lifetime = Route::new(Get, "/other", lifetime_root);
// Rocket::new("localhost", 8000).mount_and_launch("/", &[&lifetime]);
let echo = Route::new(Get, "/", echo_url);
rocket.mount("/echo:<str>", vec![echo]);
rocket.launch();
}

9
lib/src/codegen.rs Normal file
View File

@ -0,0 +1,9 @@
use method::Method;
use handler::Handler;
pub struct StaticRouteInfo {
pub method: Method,
pub path: &'static str,
pub handler: Handler
}

View File

@ -1,4 +1,4 @@
#![feature(str_char)]
#![feature(str_char, question_mark)]
#![feature(specialization)]
extern crate term_painter;
@ -9,6 +9,7 @@ mod error;
mod param;
mod router;
mod rocket;
mod codegen;
pub mod request;
pub mod response;
@ -16,9 +17,10 @@ pub mod response;
pub mod handler {
use super::{Request, Response};
pub type Handler<'a> = fn(Request) -> Response<'a>;
pub type Handler = for<'r> fn(Request<'r>) -> Response<'r>;
}
pub use codegen::StaticRouteInfo;
pub use request::Request;
pub use method::Method;
pub use response::{Response, Responder};

View File

@ -9,10 +9,6 @@ pub struct Request<'a> {
}
impl<'a> Request<'a> {
pub fn empty() -> Request<'static> {
Request::new(vec![], "")
}
pub fn new(params: Vec<&'a str>, uri: &'a str) -> Request<'a> {
Request {
params: params,

View File

@ -69,11 +69,6 @@ impl Rocket {
self
}
pub fn mount_and_launch(mut self, base: &'static str, routes: Vec<Route>) {
self.mount(base, routes);
self.launch();
}
pub fn launch(self) {
if self.router.has_collisions() {
println!("{}", Yellow.paint("Warning: route collisions detected!"));
@ -81,7 +76,12 @@ impl Rocket {
let full_addr = format!("{}:{}", self.address, self.port);
println!("🚀 {} {}...", White.paint("Rocket has launched from"),
White.bold().paint(&full_addr));
White.bold().paint(&full_addr));
let _ = HyperServer::http(full_addr.as_str()).unwrap().handle(self);
}
pub fn mount_and_launch(mut self, base: &'static str, routes: Vec<Route>) {
self.mount(base, routes);
self.launch();
}
}

View File

@ -14,7 +14,7 @@ use method::Method;
type Selector = (Method, usize);
pub struct Router {
routes: HashMap<Selector, Vec<Route>> // for now
routes: HashMap<Selector, Vec<Route>> // using 'selector' for now
}
impl Router {

View File

@ -5,36 +5,36 @@ use method::Method;
use super::{Collider, URI, URIBuf}; // :D
use handler::Handler;
// FIXME: Take in the handler! Or maybe keep that in `Router`?
pub struct Route {
pub method: Method,
pub handler: Handler<'static>,
pub handler: Handler,
pub path: URIBuf,
pub rank: isize
}
impl Route {
pub fn ranked(rank: isize, m: Method, path: String,
handler: Handler<'static>) -> Route {
pub fn ranked<S>(rank: isize, m: Method, path: S, handler: Handler)
-> Route where S: AsRef<str> {
Route {
method: m,
path: URIBuf::new(path),
path: URIBuf::from(path.as_ref()),
handler: handler,
rank: rank
}
}
pub fn new(m: Method, path: String, handler: Handler<'static>) -> Route {
pub fn new<S>(m: Method, path: S, handler: Handler)
-> Route where S: AsRef<str> {
Route {
method: m,
handler: handler,
rank: (!path.contains("<") as isize),
path: URIBuf::new(path),
rank: (!path.as_ref().contains("<") as isize),
path: URIBuf::from(path.as_ref()),
}
}
pub fn set_path(&mut self, path: String) {
self.path = URIBuf::new(path);
pub fn set_path<S>(&mut self, path: S) where S: AsRef<str> {
self.path = URIBuf::from(path.as_ref());
}
// FIXME: Decide whether a component has to be fully variable or not. That
@ -57,7 +57,7 @@ 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))
write!(f, "{} {}", Green.paint(&self.method), Blue.paint(&self.path))
}
}

View File

@ -1,7 +1,9 @@
use std::cell::Cell;
use super::Collider;
use std::convert::From;
use std::fmt::{self, Write};
#[derive(Debug, Clone)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct URI<'a> {
path: &'a str,
segment_count: Cell<Option<usize>>
@ -32,9 +34,21 @@ impl<'a> URI<'a> {
}
}
impl<'a> fmt::Display for URI<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut last = '\0';
for c in self.path.chars() {
if !(c == '/' && last == '/') { f.write_char(c)?; }
last = c;
}
Ok(())
}
}
unsafe impl<'a> Sync for URI<'a> { /* It's safe! */ }
#[derive(Debug, Clone)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct URIBuf {
path: String,
segment_count: Cell<Option<usize>>
@ -42,16 +56,8 @@ pub struct URIBuf {
// 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
@ -62,6 +68,10 @@ impl URIBuf {
Segments(self.path.as_str())
}
fn as_uri_uncached<'a>(&'a self) -> URI<'a> {
URI::new(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();
@ -79,6 +89,30 @@ impl URIBuf {
unsafe impl Sync for URIBuf { /* It's safe! */ }
impl fmt::Display for URIBuf {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.as_uri_uncached().fmt(f)
}
}
impl From<String> for URIBuf {
fn from(path: String) -> URIBuf {
URIBuf {
segment_count: Cell::new(None),
path: path,
}
}
}
impl<'a> From<&'a str> for URIBuf {
fn from(path: &'a str) -> URIBuf {
URIBuf {
segment_count: Cell::new(None),
path: path.to_string(),
}
}
}
impl<'a> Collider for URI<'a> {
fn collides_with(&self, other: &URI) -> bool {
if self.segment_count() != other.segment_count() {
@ -156,7 +190,7 @@ mod tests {
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();
let actual_buf = URIBuf::from(path).segment_count();
if actual != expected || actual_buf != expected {
println!("Count mismatch: expected {}, got {}.", expected, actual);
println!("{}", if actual != expected { "lifetime" } else { "buf" });
@ -173,7 +207,7 @@ mod tests {
let uri = URI::new(path);
let actual: Vec<&str> = uri.segments().collect();
let uri_buf = URIBuf::new(path.to_string());
let uri_buf = URIBuf::from(path);
let actual_buf: Vec<&str> = uri_buf.segments().collect();
actual == expected && actual_buf == expected

View File

@ -17,7 +17,7 @@ use syntax::parse::token::intern;
use routes_macro::routes_macro;
use route_decorator::route_decorator;
const STRUCT_PREFIX: &'static str = "ROCKET_ROUTE_STRUCT_";
const STRUCT_PREFIX: &'static str = "static_rocket_route_info_for_";
const FN_PREFIX: &'static str = "rocket_route_fn_";
#[plugin_registrar]

View File

@ -326,7 +326,7 @@ pub fn route_decorator(ecx: &mut ExtCtxt, sp: Span, meta_item: &MetaItem,
}
let mut fn_params = get_fn_params(ecx, sp, &route_params.path, &fn_decl,
external_params.clone());
external_params.clone());
// Create a comma seperated list (token tree) of the function parameters
// We pass this in to the user's function that we're wrapping.
@ -365,7 +365,8 @@ pub fn route_decorator(ecx: &mut ExtCtxt, sp: Span, meta_item: &MetaItem,
let route_fn_name = prepend_ident(FN_PREFIX, &item.ident);
let fn_name = item.ident;
let route_fn_item = quote_item!(ecx,
fn $route_fn_name<'rocket>(_req: rocket::Request) -> rocket::Response<'rocket> {
fn $route_fn_name<'rocket>(_req: rocket::Request<'rocket>)
-> rocket::Response<'rocket> {
$form_stmt
$fn_param_exprs
let result = $fn_name($fn_param_idents);
@ -381,7 +382,7 @@ pub fn route_decorator(ecx: &mut ExtCtxt, sp: Span, meta_item: &MetaItem,
let method = method_variant_to_expr(ecx, route_params.method.node);
push(Annotatable::Item(quote_item!(ecx,
#[allow(non_upper_case_globals)]
pub static $struct_name: rocket::Route = rocket::Route {
pub static $struct_name: rocket::StaticRouteInfo = rocket::StaticRouteInfo {
method: $method,
path: $path,
handler: $route_fn_name