mirror of
https://github.com/rwf2/Rocket.git
synced 2025-01-31 05:42:15 +00:00
Add trailing params.
This commit is contained in:
parent
a6967cb48f
commit
b755e53f63
@ -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() {
|
||||||
|
@ -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;
|
||||||
|
@ -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())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -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()))
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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 {
|
||||||
|
@ -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 {
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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> {
|
||||||
|
@ -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 ¶ms {
|
for (i, param) in self.path_params(ecx).enumerate() {
|
||||||
if self.annotated_fn.find_input(¶m.node.name).is_none() {
|
declared_set.insert(param.ident().name.clone());
|
||||||
self.missing_declared_err(ecx, ¶m);
|
let ty = match self.annotated_fn.find_input(¶m.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) {
|
||||||
|
@ -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;
|
||||||
|
@ -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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user