Add trailing params.

This commit is contained in:
Sergio Benitez 2016-09-08 00:02:17 -07:00
parent a6967cb48f
commit b755e53f63
12 changed files with 255 additions and 138 deletions

View File

@ -2,19 +2,20 @@
#![plugin(rocket_macros)] #![plugin(rocket_macros)]
extern crate rocket; extern crate rocket;
use rocket::Rocket; use rocket::Rocket;
use std::fs::File; use std::fs::File;
use std::io::Error as IOError; use std::io::Error as IOError;
use std::path::{Path, PathBuf};
#[get("/")] #[get("/")]
fn index() -> File { fn index() -> File {
File::open("static/index.html").unwrap() File::open("static/index.html").unwrap()
} }
#[get("/<file>")] #[get("/<file..>")]
fn files(file: &str) -> Result<File, IOError> { fn files(file: PathBuf) -> Result<File, IOError> {
File::open(format!("static/{}", file)) File::open(Path::new("static/").join(file))
} }
fn main() { fn main() {

View File

@ -36,7 +36,7 @@ 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, FromSegments};
pub use router::{Router, Route}; pub use router::{Router, Route};
pub use catcher::Catcher; pub use catcher::Catcher;
pub use rocket::Rocket; pub use rocket::Rocket;

View File

@ -1,5 +1,8 @@
use std::str::FromStr; use std::str::FromStr;
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddrV4, SocketAddrV6, SocketAddr}; use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddrV4, SocketAddrV6, SocketAddr};
use std::path::PathBuf;
use router::Segments;
use url; use url;
use error::Error; use error::Error;
@ -34,3 +37,19 @@ macro_rules! impl_with_fromstr {
impl_with_fromstr!(f32, f64, isize, i8, i16, i32, i64, usize, u8, u16, u32, u64, impl_with_fromstr!(f32, f64, isize, i8, i16, i32, i64, usize, u8, u16, u32, u64,
bool, IpAddr, Ipv4Addr, Ipv6Addr, SocketAddrV4, SocketAddrV6, bool, IpAddr, Ipv4Addr, Ipv6Addr, SocketAddrV4, SocketAddrV6,
SocketAddr); SocketAddr);
pub trait FromSegments<'a>: Sized {
fn from_segments(segments: Segments<'a>) -> Result<Self, Error>;
}
impl<'a> FromSegments<'a> for Segments<'a> {
fn from_segments(segments: Segments<'a>) -> Result<Segments<'a>, Error> {
Ok(segments)
}
}
impl<'a> FromSegments<'a> for PathBuf {
fn from_segments(segments: Segments<'a>) -> Result<PathBuf, Error> {
Ok(segments.collect())
}
}

View File

@ -6,7 +6,7 @@ use term_painter::Color::*;
use term_painter::ToStyle; use term_painter::ToStyle;
use error::Error; use error::Error;
use param::FromParam; use param::{FromParam, FromSegments};
use method::Method; use method::Method;
use content_type::ContentType; use content_type::ContentType;
@ -28,15 +28,32 @@ pub struct Request<'a> {
} }
impl<'a> Request<'a> { impl<'a> Request<'a> {
// FIXME: Don't do the parsing here. I think. Not sure. Decide.
pub fn get_param<T: FromParam<'a>>(&self, n: usize) -> Result<T, Error> { pub fn get_param<T: FromParam<'a>>(&self, n: usize) -> Result<T, Error> {
let params = self.params.borrow(); let params = self.params.borrow();
if params.is_none() || n >= params.as_ref().unwrap().len() { if params.is_none() || n >= params.as_ref().unwrap().len() {
debug!("{} is >= param count {}", n, params.as_ref().unwrap().len());
Err(Error::NoKey) Err(Error::NoKey)
} else { } else {
T::from_param(params.as_ref().unwrap()[n]) T::from_param(params.as_ref().unwrap()[n])
} }
} }
/// i is the index of the first segment to consider
pub fn get_segments<'r: 'a, T: FromSegments<'a>>(&'r self, i: usize)
-> Result<T, Error> {
if i >= self.uri().segment_count() {
debug!("{} is >= segment count {}", i, self.uri().segment_count());
Err(Error::NoKey)
} else {
// TODO: Really want to do self.uri.segments().skip(i).into_inner(),
// but the std lib doesn't implement it for Skip.
let mut segments = self.uri.segments();
for _ in segments.by_ref().take(i) { /* do nothing */ }
T::from_segments(segments)
}
}
pub fn mock(method: Method, uri: &str) -> Request { pub fn mock(method: Method, uri: &str) -> Request {
Request { Request {
params: RefCell::new(None), params: RefCell::new(None),
@ -59,10 +76,24 @@ impl<'a> Request<'a> {
hyp_ct.map_or(ContentType::any(), |ct| ContentType::from(&ct.0)) hyp_ct.map_or(ContentType::any(), |ct| ContentType::from(&ct.0))
} }
/// Returns the first content-type accepted by this request.
pub fn accepts(&self) -> ContentType {
let accept = self.headers().get::<header::Accept>();
accept.map_or(ContentType::any(), |accept| {
let items = &accept.0;
if items.len() < 1 {
return ContentType::any();
} else {
return ContentType::from(items[0].item.clone())
}
})
}
pub fn uri(&'a self) -> URI<'a> { pub fn uri(&'a self) -> URI<'a> {
self.uri.as_uri() self.uri.as_uri()
} }
// FIXME: Don't need a refcell for this.
pub fn set_params(&'a self, route: &Route) { pub fn set_params(&'a self, route: &Route) {
*self.params.borrow_mut() = Some(route.get_params(self.uri.as_uri())) *self.params.borrow_mut() = Some(route.get_params(self.uri.as_uri()))
} }

View File

@ -44,7 +44,6 @@ impl Rocket {
info!("{}:", request); info!("{}:", request);
let matches = self.router.route(&request); let matches = self.router.route(&request);
trace_!("Found {} matches.", matches.len());
for route in matches { for route in matches {
// Retrieve and set the requests parameters. // Retrieve and set the requests parameters.
info_!("Matched: {}", route); info_!("Matched: {}", route);
@ -61,6 +60,7 @@ impl Rocket {
}; };
} }
error_!("No matching routes.");
self.handle_not_found(&request, res); self.handle_not_found(&request, res);
} }

View File

@ -1,3 +1,6 @@
/// The Collider trait is used to determine if two items that can be routed on
/// can match against a given request. That is, if two items `collide`, they
/// will both match against _some_ request.
pub trait Collider<T: ?Sized = Self> { pub trait Collider<T: ?Sized = Self> {
fn collides_with(&self, other: &T) -> bool; fn collides_with(&self, other: &T) -> bool;
} }
@ -47,6 +50,7 @@ mod tests {
use {Request, Response}; use {Request, Response};
use content_type::ContentType; use content_type::ContentType;
use std::str::FromStr; use std::str::FromStr;
use router::URI;
type SimpleRoute = (Method, &'static str); type SimpleRoute = (Method, &'static str);
@ -64,17 +68,8 @@ mod tests {
route_a.collides_with(&Route::ranked(0, Get, b.to_string(), dummy_handler)) route_a.collides_with(&Route::ranked(0, Get, b.to_string(), dummy_handler))
} }
fn s_r_collide(a: &'static str, b: &'static str) -> bool {
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.to_string(), dummy_handler);
route_a.collides_with(b)
}
fn s_s_collide(a: &'static str, b: &'static str) -> bool { fn s_s_collide(a: &'static str, b: &'static str) -> bool {
a.collides_with(b) URI::new(a).collides_with(&URI::new(b))
} }
#[test] #[test]
@ -96,6 +91,14 @@ mod tests {
assert!(unranked_collide("/<name>/hi/there", "/dude/<name>/there")); assert!(unranked_collide("/<name>/hi/there", "/dude/<name>/there"));
assert!(unranked_collide("/<name>/<a>/<b>", "/<a>/<b>/<c>")); assert!(unranked_collide("/<name>/<a>/<b>", "/<a>/<b>/<c>"));
assert!(unranked_collide("/<name>/<a>/<b>/", "/<a>/<b>/<c>/")); assert!(unranked_collide("/<name>/<a>/<b>/", "/<a>/<b>/<c>/"));
assert!(unranked_collide("/<a..>", "/hi"));
assert!(unranked_collide("/<a..>", "/hi/hey"));
assert!(unranked_collide("/<a..>", "/hi/hey/hayo"));
assert!(unranked_collide("/a/<a..>", "/a/hi/hey/hayo"));
assert!(unranked_collide("/a/<b>/<a..>", "/a/hi/hey/hayo"));
assert!(unranked_collide("/a/<b>/<c>/<a..>", "/a/hi/hey/hayo"));
assert!(unranked_collide("/<b>/<c>/<a..>", "/a/hi/hey/hayo"));
assert!(unranked_collide("/<b>/<c>/hey/hayo", "/a/hi/hey/hayo"));
} }
#[test] #[test]
@ -114,6 +117,7 @@ mod tests {
assert!(unranked_collide("/<a>/<b>", "/a/b<c>")); assert!(unranked_collide("/<a>/<b>", "/a/b<c>"));
assert!(unranked_collide("/<a>/bc<b>", "/a/b<c>")); assert!(unranked_collide("/<a>/bc<b>", "/a/b<c>"));
assert!(unranked_collide("/<a>/bc<b>d", "/a/b<c>")); assert!(unranked_collide("/<a>/bc<b>d", "/a/b<c>"));
assert!(unranked_collide("/<a..>", "///a///"));
} }
#[test] #[test]
@ -130,6 +134,10 @@ mod tests {
assert!(!unranked_collide("/a<a>/<b>", "/b<b>/<a>")); assert!(!unranked_collide("/a<a>/<b>", "/b<b>/<a>"));
assert!(!unranked_collide("/a/<b>", "/b/<b>")); assert!(!unranked_collide("/a/<b>", "/b/<b>"));
assert!(!unranked_collide("/a<a>/<b>", "/b/<b>")); assert!(!unranked_collide("/a<a>/<b>", "/b/<b>"));
assert!(!unranked_collide("/<a..>", "/"));
assert!(!unranked_collide("/hi/<a..>", "/hi"));
assert!(!unranked_collide("/hi/<a..>", "/hi/"));
assert!(!unranked_collide("/<a..>", "//////"));
} }
#[test] #[test]
@ -144,34 +152,6 @@ mod tests {
#[test] #[test]
fn test_str_non_collisions() { fn test_str_non_collisions() {
assert!(!s_r_collide("/a", "/b"));
assert!(!s_r_collide("/a/b", "/a"));
assert!(!s_r_collide("/a/b", "/a/c"));
assert!(!s_r_collide("/a/hello", "/a/c"));
assert!(!s_r_collide("/hello", "/a/c"));
assert!(!s_r_collide("/hello/there", "/hello/there/guy"));
assert!(!s_r_collide("/b<a>/there", "/hi/there"));
assert!(!s_r_collide("/<a>/<b>c", "/hi/person"));
assert!(!s_r_collide("/<a>/<b>cd", "/hi/<a>e"));
assert!(!s_r_collide("/a<a>/<b>", "/b<b>/<a>"));
assert!(!s_r_collide("/a/<b>", "/b/<b>"));
assert!(!s_r_collide("/a<a>/<b>", "/b/<b>"));
assert!(!r_s_collide("/a", "/b"));
assert!(!r_s_collide("/a/b", "/a"));
assert!(!r_s_collide("/a/b", "/a/c"));
assert!(!r_s_collide("/a/hello", "/a/c"));
assert!(!r_s_collide("/hello", "/a/c"));
assert!(!r_s_collide("/hello/there", "/hello/there/guy"));
assert!(!r_s_collide("/b<a>/there", "/hi/there"));
assert!(!r_s_collide("/<a>/<b>c", "/hi/person"));
assert!(!r_s_collide("/<a>/<b>cd", "/hi/<a>e"));
assert!(!r_s_collide("/a<a>/<b>", "/b<b>/<a>"));
assert!(!r_s_collide("/a/<b>", "/b/<b>"));
assert!(!r_s_collide("/a<a>/<b>", "/b/<b>"));
}
#[test]
fn test_str_collisions() {
assert!(!s_s_collide("/a", "/b")); assert!(!s_s_collide("/a", "/b"));
assert!(!s_s_collide("/a/b", "/a")); assert!(!s_s_collide("/a/b", "/a"));
assert!(!s_s_collide("/a/b", "/a/c")); assert!(!s_s_collide("/a/b", "/a/c"));
@ -184,6 +164,33 @@ mod tests {
assert!(!s_s_collide("/a<a>/<b>", "/b<b>/<a>")); assert!(!s_s_collide("/a<a>/<b>", "/b<b>/<a>"));
assert!(!s_s_collide("/a/<b>", "/b/<b>")); assert!(!s_s_collide("/a/<b>", "/b/<b>"));
assert!(!s_s_collide("/a<a>/<b>", "/b/<b>")); assert!(!s_s_collide("/a<a>/<b>", "/b/<b>"));
assert!(!s_s_collide("/a", "/b"));
assert!(!s_s_collide("/a/b", "/a"));
assert!(!s_s_collide("/a/b", "/a/c"));
assert!(!s_s_collide("/a/hello", "/a/c"));
assert!(!s_s_collide("/hello", "/a/c"));
assert!(!s_s_collide("/hello/there", "/hello/there/guy"));
assert!(!s_s_collide("/b<a>/there", "/hi/there"));
assert!(!s_s_collide("/<a>/<b>c", "/hi/person"));
assert!(!s_s_collide("/<a>/<b>cd", "/hi/<a>e"));
assert!(!s_s_collide("/a<a>/<b>", "/b<b>/<a>"));
assert!(!s_s_collide("/a/<b>", "/b/<b>"));
assert!(!s_s_collide("/a<a>/<b>", "/b/<b>"));
assert!(!s_s_collide("/a", "/b"));
assert!(!s_s_collide("/a/b", "/a"));
assert!(!s_s_collide("/a/b", "/a/c"));
assert!(!s_s_collide("/a/hello", "/a/c"));
assert!(!s_s_collide("/hello", "/a/c"));
assert!(!s_s_collide("/hello/there", "/hello/there/guy"));
assert!(!s_s_collide("/b<a>/there", "/hi/there"));
assert!(!s_s_collide("/<a>/<b>c", "/hi/person"));
assert!(!s_s_collide("/<a>/<b>cd", "/hi/<a>e"));
assert!(!s_s_collide("/a<a>/<b>", "/b<b>/<a>"));
assert!(!s_s_collide("/a/<b>", "/b/<b>"));
assert!(!s_s_collide("/a<a>/<b>", "/b/<b>"));
assert!(!s_s_collide("/<a..>", "/"));
assert!(!s_s_collide("/hi/<a..>", "/hi/"));
assert!(!s_s_collide("/a/hi/<a..>", "/a/hi/"));
} }
fn ct_route(m: Method, s: &str, ct: &str) -> Route { fn ct_route(m: Method, s: &str, ct: &str) -> Route {

View File

@ -3,14 +3,15 @@ mod route;
mod uri; mod uri;
pub use self::collider::Collider; pub use self::collider::Collider;
pub use self::uri::{URI, URIBuf}; pub use self::uri::{URI, URIBuf, Segments};
pub use self::route::Route; pub use self::route::Route;
use std::collections::hash_map::HashMap; use std::collections::hash_map::HashMap;
use method::Method; use method::Method;
use request::Request; use request::Request;
type Selector = (Method, usize); // type Selector = (Method, usize);
type Selector = Method;
#[derive(Default)] #[derive(Default)]
pub struct Router { pub struct Router {
@ -23,7 +24,8 @@ impl Router {
} }
pub fn add(&mut self, route: Route) { pub fn add(&mut self, route: Route) {
let selector = (route.method, route.path.segment_count()); // let selector = (route.method, route.path.segment_count());
let selector = route.method;
self.routes.entry(selector).or_insert_with(|| vec![]).push(route); self.routes.entry(selector).or_insert_with(|| vec![]).push(route);
} }
@ -33,9 +35,12 @@ impl Router {
// FIXME: Figure out a way to get more than one route, i.e., to correctly // FIXME: Figure out a way to get more than one route, i.e., to correctly
// handle ranking. // handle ranking.
pub fn route<'b>(&'b self, req: &Request) -> Vec<&'b Route> { pub fn route<'b>(&'b self, req: &Request) -> Vec<&'b Route> {
let num_segments = req.uri.segment_count(); trace_!("Trying to route: {}", req);
self.routes.get(&(req.method, num_segments)).map_or(vec![], |routes| { // let num_segments = req.uri.segment_count();
let mut matches: Vec<&'b Route> = routes.iter().filter(|r| { // self.routes.get(&(req.method, num_segments)).map_or(vec![], |routes| {
self.routes.get(&req.method).map_or(vec![], |routes| {
trace_!("All possible matches: {:?}", routes);
let mut matches: Vec<_> = routes.iter().filter(|r| {
r.collides_with(req) r.collides_with(req)
}).collect(); }).collect();
@ -120,6 +125,18 @@ mod test {
assert!(unranked_route_collisions(&["/<a>", "/<b>"])); assert!(unranked_route_collisions(&["/<a>", "/<b>"]));
assert!(unranked_route_collisions(&["/hello/bob", "/hello/<b>"])); assert!(unranked_route_collisions(&["/hello/bob", "/hello/<b>"]));
assert!(unranked_route_collisions(&["/a/b/<c>/d", "/<a>/<b>/c/d"])); assert!(unranked_route_collisions(&["/a/b/<c>/d", "/<a>/<b>/c/d"]));
assert!(unranked_route_collisions(&["/a/b", "/<a..>"]));
assert!(unranked_route_collisions(&["/a/b/c", "/a/<a..>"]));
assert!(unranked_route_collisions(&["/<a>/b", "/a/<a..>"]));
assert!(unranked_route_collisions(&["/a/<b>", "/a/<a..>"]));
assert!(unranked_route_collisions(&["/a/b/<c>", "/a/<a..>"]));
}
#[test]
fn test_no_collisions() {
assert!(!unranked_route_collisions(&["/<a>", "/a/<a..>"]));
assert!(!unranked_route_collisions(&["/a/b", "/a/b/c"]));
assert!(!unranked_route_collisions(&["/a/b/c/d", "/a/b/c/<d>/e"]));
} }
#[test] #[test]
@ -128,6 +145,8 @@ mod test {
assert!(!default_rank_route_collisions(&["/hello/bob", "/hello/<b>"])); assert!(!default_rank_route_collisions(&["/hello/bob", "/hello/<b>"]));
assert!(!default_rank_route_collisions(&["/a/b/c/d", "/<a>/<b>/c/d"])); assert!(!default_rank_route_collisions(&["/a/b/c/d", "/<a>/<b>/c/d"]));
assert!(!default_rank_route_collisions(&["/hi", "/<hi>"])); assert!(!default_rank_route_collisions(&["/hi", "/<hi>"]));
assert!(!default_rank_route_collisions(&["/hi", "/<hi>"]));
assert!(!default_rank_route_collisions(&["/a/b", "/a/b/<c..>"]));
} }
fn route<'a>(router: &'a Router, method: Method, uri: &str) -> Option<&'a Route> { fn route<'a>(router: &'a Router, method: Method, uri: &str) -> Option<&'a Route> {
@ -169,6 +188,13 @@ mod test {
assert!(route(&router, Put, "/hello").is_some()); assert!(route(&router, Put, "/hello").is_some());
assert!(route(&router, Post, "/hello").is_some()); assert!(route(&router, Post, "/hello").is_some());
assert!(route(&router, Delete, "/hello").is_some()); assert!(route(&router, Delete, "/hello").is_some());
let router = router_with_routes(&["/<a..>"]);
assert!(route(&router, Get, "/hello/hi").is_some());
assert!(route(&router, Get, "/a/b/").is_some());
assert!(route(&router, Get, "/i/a").is_some());
assert!(route(&router, Get, "/a/b/c/d/e/f").is_some());
} }
#[test] #[test]
@ -275,6 +301,18 @@ mod test {
with: [(0, "/<a>/<b>/edit"), (2, "/profile/<d>"), (5, "/<a>/<b>/<c>")], with: [(0, "/<a>/<b>/edit"), (2, "/profile/<d>"), (5, "/<a>/<b>/<c>")],
expect: (0, "/<a>/<b>/edit"), (5, "/<a>/<b>/<c>") expect: (0, "/<a>/<b>/edit"), (5, "/<a>/<b>/<c>")
); );
assert_ranked_routing!(
to: "/a/b",
with: [(0, "/a/b"), (1, "/a/<b..>")],
expect: (0, "/a/b"), (1, "/a/<b..>")
);
assert_ranked_routing!(
to: "/a/b/c/d/e/f",
with: [(1, "/a/<b..>"), (2, "/a/b/<c..>")],
expect: (1, "/a/<b..>"), (2, "/a/b/<c..>")
);
} }
fn match_params(router: &Router, path: &str, expected: &[&str]) -> bool { fn match_params(router: &Router, path: &str, expected: &[&str]) -> bool {

View File

@ -53,12 +53,14 @@ impl Route {
// 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: URI<'a>) -> Vec<&'a str> { pub fn get_params<'a>(&self, uri: URI<'a>) -> Vec<&'a str> {
let route_components = self.path.segments(); let route_segs = self.path.segments();
let uri_components = uri.segments(); let uri_segs = uri.segments();
let mut result = Vec::with_capacity(self.path.segment_count()); let mut result = Vec::with_capacity(self.path.segment_count());
for (route_seg, uri_seg) in route_components.zip(uri_components) { for (route_seg, uri_seg) in route_segs.zip(uri_segs) {
if route_seg.starts_with('<') { // FIXME: Here. if route_seg.ends_with("..>") { // FIXME: Here.
break;
} else if route_seg.ends_with('>') { // FIXME: Here.
result.push(uri_seg); result.push(uri_seg);
} }
} }
@ -83,6 +85,12 @@ impl fmt::Display for Route {
} }
} }
impl fmt::Debug for Route {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
<Route as fmt::Display>::fmt(self, f)
}
}
impl<'a> From<&'a StaticRouteInfo> for Route { impl<'a> From<&'a StaticRouteInfo> for Route {
fn from(info: &'a StaticRouteInfo) -> Route { fn from(info: &'a StaticRouteInfo) -> Route {
let mut route = Route::new(info.method, info.path, info.handler); let mut route = Route::new(info.method, info.path, info.handler);
@ -97,31 +105,17 @@ impl<'a> From<&'a StaticRouteInfo> for Route {
impl Collider for Route { impl Collider for Route {
fn collides_with(&self, b: &Route) -> bool { fn collides_with(&self, b: &Route) -> bool {
self.path.segment_count() == b.path.segment_count() self.method == b.method
&& self.method == b.method
&& self.rank == b.rank && self.rank == b.rank
&& self.content_type.collides_with(&b.content_type) && self.content_type.collides_with(&b.content_type)
&& self.path.collides_with(&b.path) && self.path.as_uri().collides_with(&b.path.as_uri())
}
}
impl<'a> Collider<Route> for &'a str {
fn collides_with(&self, other: &Route) -> bool {
let path = URI::new(self);
path.collides_with(&other.path)
}
}
impl Collider<str> for Route {
fn collides_with(&self, other: &str) -> bool {
other.collides_with(self)
} }
} }
impl<'r> Collider<Request<'r>> for Route { impl<'r> Collider<Request<'r>> for Route {
fn collides_with(&self, req: &Request) -> bool { fn collides_with(&self, req: &Request) -> bool {
self.method == req.method self.method == req.method
&& req.uri.collides_with(&self.path) && req.uri.as_uri().collides_with(&self.path.as_uri())
&& req.content_type().collides_with(&self.content_type) && req.accepts().collides_with(&self.content_type)
} }
} }

View File

@ -130,38 +130,27 @@ impl<'a> From<&'a str> for URIBuf {
impl<'a> Collider for URI<'a> { impl<'a> Collider for URI<'a> {
fn collides_with(&self, other: &URI) -> bool { fn collides_with(&self, other: &URI) -> bool {
if self.segment_count() != other.segment_count() { let mut trailing = false;
return false; for (seg_a, seg_b) in self.segments().zip(other.segments()) {
if seg_a.ends_with("..>") || seg_b.ends_with("..>") {
trailing = true;
break;
} }
for (seg_a, seg_b) in self.segments().zip(other.segments()) {
if !seg_a.collides_with(seg_b) { if !seg_a.collides_with(seg_b) {
return false; return false;
} }
} }
if !trailing && (self.segment_count() != other.segment_count()) {
return false;
}
true true
} }
} }
impl Collider for URIBuf { #[derive(Clone, Debug)]
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); pub struct Segments<'a>(&'a str);
impl<'a> Iterator for Segments<'a> { impl<'a> Iterator for Segments<'a> {

View File

@ -4,11 +4,11 @@ use std::fmt::Display;
use ::{ROUTE_STRUCT_PREFIX, ROUTE_FN_PREFIX, PARAM_PREFIX}; use ::{ROUTE_STRUCT_PREFIX, ROUTE_FN_PREFIX, PARAM_PREFIX};
use utils::{emit_item, span, sep_by_tok, option_as_expr, strip_ty_lifetimes}; use utils::{emit_item, span, sep_by_tok, option_as_expr, strip_ty_lifetimes};
use utils::{SpanExt, IdentExt, ArgExt}; use utils::{SpanExt, IdentExt, ArgExt};
use parser::RouteParams; use parser::{Param, RouteParams};
use syntax::codemap::{Span, Spanned}; use syntax::codemap::{Span, Spanned};
use syntax::tokenstream::TokenTree; use syntax::tokenstream::TokenTree;
use syntax::ast::{Name, Arg, Ident, Stmt, Expr, MetaItem, Path}; use syntax::ast::{Arg, Ident, Stmt, Expr, MetaItem, Path};
use syntax::ext::base::{Annotatable, ExtCtxt}; use syntax::ext::base::{Annotatable, ExtCtxt};
use syntax::ext::build::AstBuilder; use syntax::ext::build::AstBuilder;
use syntax::parse::token::{self, str_to_ident}; use syntax::parse::token::{self, str_to_ident};
@ -117,39 +117,38 @@ impl RouteGenerateExt for RouteParams {
// TODO: Add some kind of logging facility in Rocket to get be able to log // TODO: Add some kind of logging facility in Rocket to get be able to log
// an error/debug message if parsing a parameter fails. // an error/debug message if parsing a parameter fails.
fn generate_param_statements(&self, ecx: &ExtCtxt) -> Vec<Stmt> { fn generate_param_statements(&self, ecx: &ExtCtxt) -> Vec<Stmt> {
let params: Vec<_> = self.path_params(ecx).collect();
let mut fn_param_statements = vec![]; let mut fn_param_statements = vec![];
// Retrieve an iterator over the user's path parameters and ensure that // Generate a statement for every declared paramter in the path.
// each parameter appears in the function signature. let mut declared_set = HashSet::new();
for param in &params { for (i, param) in self.path_params(ecx).enumerate() {
if self.annotated_fn.find_input(&param.node.name).is_none() { declared_set.insert(param.ident().name.clone());
self.missing_declared_err(ecx, &param); let ty = match self.annotated_fn.find_input(&param.ident().name) {
} Some(arg) => strip_ty_lifetimes(arg.ty.clone()),
None => {
self.missing_declared_err(ecx, param.inner());
continue;
} }
};
// Create a function thats checks if an argument was declared in `path`. let ident = param.ident().prepend(PARAM_PREFIX);
let set: HashSet<&Name> = params.iter().map(|p| &p.node.name).collect(); let expr = match param {
let declared = &|arg: &&Arg| set.contains(&*arg.name().unwrap()); Param::Single(_) => quote_expr!(ecx, _req.get_param($i)),
Param::Many(_) => quote_expr!(ecx, _req.get_segments($i)),
};
// These are all of the arguments in the function signature.
let all = &self.annotated_fn.decl().inputs;
// Generate code for each user declared parameter.
for (i, arg) in all.iter().filter(declared).enumerate() {
let ident = arg.ident().unwrap().prepend(PARAM_PREFIX);
let ty = strip_ty_lifetimes(arg.ty.clone());
fn_param_statements.push(quote_stmt!(ecx, fn_param_statements.push(quote_stmt!(ecx,
let $ident: $ty = match _req.get_param($i) { let $ident: $ty = match $expr {
Ok(v) => v, Ok(v) => v,
Err(_) => return ::rocket::Response::forward() Err(_) => return ::rocket::Response::forward()
}; };
).expect("declared param parsing statement")); ).expect("declared param parsing statement"));
} }
// A from_request parameter is one that isn't declared, `form`, or query. // A from_request parameter is one that isn't declared, form, or query.
let from_request = |a: &&Arg| { let from_request = |a: &&Arg| {
!declared(a) && self.form_param.as_ref().map_or(true, |p| { !declared_set.contains(&*a.name().unwrap())
&& self.form_param.as_ref().map_or(true, |p| {
!a.named(&p.value().name) !a.named(&p.value().name)
}) && self.query_param.as_ref().map_or(true, |p| { }) && self.query_param.as_ref().map_or(true, |p| {
!a.named(&p.node.name) !a.named(&p.node.name)
@ -157,8 +156,10 @@ impl RouteGenerateExt for RouteParams {
}; };
// Generate the code for `form_request` parameters. // Generate the code for `form_request` parameters.
let all = &self.annotated_fn.decl().inputs;
for arg in all.iter().filter(from_request) { for arg in all.iter().filter(from_request) {
let (ident, ty) = (arg.ident().unwrap().prepend(PARAM_PREFIX), &arg.ty); let ident = arg.ident().unwrap().prepend(PARAM_PREFIX);
let ty = strip_ty_lifetimes(arg.ty.clone());
fn_param_statements.push(quote_stmt!(ecx, fn_param_statements.push(quote_stmt!(ecx,
let $ident: $ty = match let $ident: $ty = match
<$ty as ::rocket::request::FromRequest>::from_request(&_req) { <$ty as ::rocket::request::FromRequest>::from_request(&_req) {

View File

@ -7,5 +7,5 @@ mod function;
pub use self::keyvalue::KVSpanned; pub use self::keyvalue::KVSpanned;
pub use self::route::RouteParams; pub use self::route::RouteParams;
pub use self::error::ErrorParams; pub use self::error::ErrorParams;
pub use self::param::ParamIter; pub use self::param::{Param, ParamIter};
pub use self::function::Function; pub use self::function::Function;

View File

@ -3,12 +3,32 @@ use syntax::ext::base::ExtCtxt;
use syntax::codemap::{Span, Spanned, BytePos}; use syntax::codemap::{Span, Spanned, BytePos};
use syntax::parse::token::str_to_ident; use syntax::parse::token::str_to_ident;
use utils::span; use utils::{span, SpanExt};
#[derive(Debug)]
pub enum Param {
Single(Spanned<Ident>),
Many(Spanned<Ident>)
}
impl Param {
pub fn inner(&self) -> &Spanned<Ident> {
match *self {
Param::Single(ref ident) | Param::Many(ref ident) => ident
}
}
pub fn ident(&self) -> &Ident {
match *self {
Param::Single(ref ident) | Param::Many(ref ident) => &ident.node
}
}
}
pub struct ParamIter<'s, 'a, 'c: 'a> { pub struct ParamIter<'s, 'a, 'c: 'a> {
ctxt: &'a ExtCtxt<'c>, ctxt: &'a ExtCtxt<'c>,
span: Span, span: Span,
string: &'s str string: &'s str,
} }
impl<'s, 'a, 'c: 'a> ParamIter<'s, 'a, 'c> { impl<'s, 'a, 'c: 'a> ParamIter<'s, 'a, 'c> {
@ -16,19 +36,19 @@ impl<'s, 'a, 'c: 'a> ParamIter<'s, 'a, 'c> {
ParamIter { ParamIter {
ctxt: c, ctxt: c,
span: p, span: p,
string: s string: s,
} }
} }
} }
impl<'s, 'a, 'c> Iterator for ParamIter<'s, 'a, 'c> { impl<'s, 'a, 'c> Iterator for ParamIter<'s, 'a, 'c> {
type Item = Spanned<Ident>; type Item = Param;
fn next(&mut self) -> Option<Spanned<Ident>> { fn next(&mut self) -> Option<Param> {
// Find the start and end indexes for the next parameter, if any. // Find the start and end indexes for the next parameter, if any.
let (start, end) = match (self.string.find('<'), self.string.find('>')) { let (start, end) = match (self.string.find('<'), self.string.find('>')) {
(Some(i), Some(j)) => (i, j), (Some(i), Some(j)) => (i, j),
_ => return None _ => return None,
}; };
// Ensure we found a valid parameter. // Ensure we found a valid parameter.
@ -37,24 +57,41 @@ impl<'s, 'a, 'c> Iterator for ParamIter<'s, 'a, 'c> {
return None; return None;
} }
// Calculate the parameter and the span for the parameter. // Calculate the parameter's ident.
let param = &self.string[(start + 1)..end]; let full_param = &self.string[(start + 1)..end];
let (is_many, param) = match full_param.ends_with("..") {
true => (true, &full_param[..(full_param.len() - 2)]),
false => (false, full_param)
};
let mut param_span = self.span; let mut param_span = self.span;
param_span.lo = self.span.lo + BytePos(start as u32); param_span.lo = self.span.lo + BytePos(start as u32);
param_span.hi = self.span.lo + BytePos((end + 1) as u32); param_span.hi = self.span.lo + BytePos((end + 1) as u32);
// Check for nonemptiness and that the characters are correct. // Advance the string and span.
self.string = &self.string[(end + 1)..];
self.span.lo = self.span.lo + BytePos((end + 1) as u32);
// Check for nonemptiness, that the characters are correct, and return.
if param.is_empty() { if param.is_empty() {
self.ctxt.span_err(param_span, "parameter names cannot be empty"); self.ctxt.span_err(param_span, "parameter names cannot be empty");
None None
} else if param.contains(|c: char| !c.is_alphanumeric()) { } else if param.contains(|c: char| !c.is_alphanumeric()) {
self.ctxt.span_err(param_span, "parameters must be alphanumeric"); self.ctxt.span_err(param_span, "parameter names must be alphanumeric");
None
} else if is_many && !self.string.is_empty() {
let sp = self.span.shorten_to(self.string.len() as u32);
self.ctxt.struct_span_err(sp, "text after a trailing '..' param")
.span_note(param_span, "trailing param is here")
.emit();
None None
} else { } else {
self.string = &self.string[(end + 1)..]; let spanned_ident = span(str_to_ident(param), param_span);
self.span.lo = self.span.lo + BytePos((end + 1) as u32); match is_many {
Some(span(str_to_ident(param), param_span)) true => Some(Param::Many(spanned_ident)),
} false => Some(Param::Single(spanned_ident))
} }
} }
}
}