mirror of https://github.com/rwf2/Rocket.git
Add trailing params.
This commit is contained in:
parent
a6967cb48f
commit
b755e53f63
|
@ -2,19 +2,20 @@
|
|||
#![plugin(rocket_macros)]
|
||||
|
||||
extern crate rocket;
|
||||
|
||||
use rocket::Rocket;
|
||||
|
||||
use std::fs::File;
|
||||
use std::io::Error as IOError;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
#[get("/")]
|
||||
fn index() -> File {
|
||||
File::open("static/index.html").unwrap()
|
||||
}
|
||||
|
||||
#[get("/<file>")]
|
||||
fn files(file: &str) -> Result<File, IOError> {
|
||||
File::open(format!("static/{}", file))
|
||||
#[get("/<file..>")]
|
||||
fn files(file: PathBuf) -> Result<File, IOError> {
|
||||
File::open(Path::new("static/").join(file))
|
||||
}
|
||||
|
||||
fn main() {
|
||||
|
|
|
@ -36,7 +36,7 @@ pub use request::Request;
|
|||
pub use method::Method;
|
||||
pub use response::{Response, Responder};
|
||||
pub use error::Error;
|
||||
pub use param::FromParam;
|
||||
pub use param::{FromParam, FromSegments};
|
||||
pub use router::{Router, Route};
|
||||
pub use catcher::Catcher;
|
||||
pub use rocket::Rocket;
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
use std::str::FromStr;
|
||||
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddrV4, SocketAddrV6, SocketAddr};
|
||||
use std::path::PathBuf;
|
||||
use router::Segments;
|
||||
|
||||
use url;
|
||||
|
||||
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,
|
||||
bool, IpAddr, Ipv4Addr, Ipv6Addr, SocketAddrV4, SocketAddrV6,
|
||||
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 error::Error;
|
||||
use param::FromParam;
|
||||
use param::{FromParam, FromSegments};
|
||||
use method::Method;
|
||||
|
||||
use content_type::ContentType;
|
||||
|
@ -28,15 +28,32 @@ pub struct 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> {
|
||||
let params = self.params.borrow();
|
||||
if params.is_none() || n >= params.as_ref().unwrap().len() {
|
||||
debug!("{} is >= param count {}", n, params.as_ref().unwrap().len());
|
||||
Err(Error::NoKey)
|
||||
} else {
|
||||
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 {
|
||||
Request {
|
||||
params: RefCell::new(None),
|
||||
|
@ -59,10 +76,24 @@ impl<'a> Request<'a> {
|
|||
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> {
|
||||
self.uri.as_uri()
|
||||
}
|
||||
|
||||
// FIXME: Don't need a refcell for this.
|
||||
pub fn set_params(&'a self, route: &Route) {
|
||||
*self.params.borrow_mut() = Some(route.get_params(self.uri.as_uri()))
|
||||
}
|
||||
|
|
|
@ -44,7 +44,6 @@ impl Rocket {
|
|||
|
||||
info!("{}:", request);
|
||||
let matches = self.router.route(&request);
|
||||
trace_!("Found {} matches.", matches.len());
|
||||
for route in matches {
|
||||
// Retrieve and set the requests parameters.
|
||||
info_!("Matched: {}", route);
|
||||
|
@ -61,6 +60,7 @@ impl Rocket {
|
|||
};
|
||||
}
|
||||
|
||||
error_!("No matching routes.");
|
||||
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> {
|
||||
fn collides_with(&self, other: &T) -> bool;
|
||||
}
|
||||
|
@ -47,6 +50,7 @@ mod tests {
|
|||
use {Request, Response};
|
||||
use content_type::ContentType;
|
||||
use std::str::FromStr;
|
||||
use router::URI;
|
||||
|
||||
type SimpleRoute = (Method, &'static str);
|
||||
|
||||
|
@ -64,17 +68,8 @@ mod tests {
|
|||
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 {
|
||||
a.collides_with(b)
|
||||
URI::new(a).collides_with(&URI::new(b))
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -96,6 +91,14 @@ mod tests {
|
|||
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("/<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]
|
||||
|
@ -114,6 +117,7 @@ mod tests {
|
|||
assert!(unranked_collide("/<a>/<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..>", "///a///"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -130,6 +134,10 @@ mod tests {
|
|||
assert!(!unranked_collide("/a<a>/<b>", "/b<b>/<a>"));
|
||||
assert!(!unranked_collide("/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]
|
||||
|
@ -144,34 +152,6 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
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", "/a"));
|
||||
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/<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 {
|
||||
|
|
|
@ -3,14 +3,15 @@ mod route;
|
|||
mod uri;
|
||||
|
||||
pub use self::collider::Collider;
|
||||
pub use self::uri::{URI, URIBuf};
|
||||
pub use self::uri::{URI, URIBuf, Segments};
|
||||
pub use self::route::Route;
|
||||
|
||||
use std::collections::hash_map::HashMap;
|
||||
use method::Method;
|
||||
use request::Request;
|
||||
|
||||
type Selector = (Method, usize);
|
||||
// type Selector = (Method, usize);
|
||||
type Selector = Method;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Router {
|
||||
|
@ -23,7 +24,8 @@ impl Router {
|
|||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -33,9 +35,12 @@ impl Router {
|
|||
// FIXME: Figure out a way to get more than one route, i.e., to correctly
|
||||
// handle ranking.
|
||||
pub fn route<'b>(&'b self, req: &Request) -> Vec<&'b Route> {
|
||||
let num_segments = req.uri.segment_count();
|
||||
self.routes.get(&(req.method, num_segments)).map_or(vec![], |routes| {
|
||||
let mut matches: Vec<&'b Route> = routes.iter().filter(|r| {
|
||||
trace_!("Trying to route: {}", req);
|
||||
// let num_segments = req.uri.segment_count();
|
||||
// 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)
|
||||
}).collect();
|
||||
|
||||
|
@ -120,6 +125,18 @@ mod test {
|
|||
assert!(unranked_route_collisions(&["/<a>", "/<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", "/<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]
|
||||
|
@ -128,6 +145,8 @@ mod test {
|
|||
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(&["/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> {
|
||||
|
@ -169,6 +188,13 @@ mod test {
|
|||
assert!(route(&router, Put, "/hello").is_some());
|
||||
assert!(route(&router, Post, "/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]
|
||||
|
@ -275,6 +301,18 @@ mod test {
|
|||
with: [(0, "/<a>/<b>/edit"), (2, "/profile/<d>"), (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 {
|
||||
|
|
|
@ -53,12 +53,14 @@ impl Route {
|
|||
// 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!)
|
||||
pub fn get_params<'a>(&self, uri: URI<'a>) -> Vec<&'a str> {
|
||||
let route_components = self.path.segments();
|
||||
let uri_components = uri.segments();
|
||||
let route_segs = self.path.segments();
|
||||
let uri_segs = uri.segments();
|
||||
|
||||
let mut result = Vec::with_capacity(self.path.segment_count());
|
||||
for (route_seg, uri_seg) in route_components.zip(uri_components) {
|
||||
if route_seg.starts_with('<') { // FIXME: Here.
|
||||
for (route_seg, uri_seg) in route_segs.zip(uri_segs) {
|
||||
if route_seg.ends_with("..>") { // FIXME: Here.
|
||||
break;
|
||||
} else if route_seg.ends_with('>') { // FIXME: Here.
|
||||
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 {
|
||||
fn from(info: &'a StaticRouteInfo) -> Route {
|
||||
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 {
|
||||
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.content_type.collides_with(&b.content_type)
|
||||
&& self.path.collides_with(&b.path)
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
&& self.path.as_uri().collides_with(&b.path.as_uri())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'r> Collider<Request<'r>> for Route {
|
||||
fn collides_with(&self, req: &Request) -> bool {
|
||||
self.method == req.method
|
||||
&& req.uri.collides_with(&self.path)
|
||||
&& req.content_type().collides_with(&self.content_type)
|
||||
&& req.uri.as_uri().collides_with(&self.path.as_uri())
|
||||
&& req.accepts().collides_with(&self.content_type)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -130,38 +130,27 @@ impl<'a> From<&'a str> for URIBuf {
|
|||
|
||||
impl<'a> Collider for URI<'a> {
|
||||
fn collides_with(&self, other: &URI) -> bool {
|
||||
if self.segment_count() != other.segment_count() {
|
||||
return false;
|
||||
}
|
||||
|
||||
let mut trailing = false;
|
||||
for (seg_a, seg_b) in self.segments().zip(other.segments()) {
|
||||
if seg_a.ends_with("..>") || seg_b.ends_with("..>") {
|
||||
trailing = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if !seg_a.collides_with(seg_b) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if !trailing && (self.segment_count() != other.segment_count()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
impl Collider for URIBuf {
|
||||
fn collides_with(&self, other: &URIBuf) -> bool {
|
||||
self.as_uri().collides_with(&other.as_uri())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Collider<URI<'a>> for URIBuf {
|
||||
fn collides_with(&self, other: &URI<'a>) -> bool {
|
||||
self.as_uri().collides_with(other)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Collider<URIBuf> for URI<'a> {
|
||||
fn collides_with(&self, other: &URIBuf) -> bool {
|
||||
other.as_uri().collides_with(self)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Segments<'a>(&'a str);
|
||||
|
||||
impl<'a> Iterator for Segments<'a> {
|
||||
|
|
|
@ -4,11 +4,11 @@ use std::fmt::Display;
|
|||
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::{SpanExt, IdentExt, ArgExt};
|
||||
use parser::RouteParams;
|
||||
use parser::{Param, RouteParams};
|
||||
|
||||
use syntax::codemap::{Span, Spanned};
|
||||
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::build::AstBuilder;
|
||||
use syntax::parse::token::{self, str_to_ident};
|
||||
|
@ -117,48 +117,49 @@ impl RouteGenerateExt for RouteParams {
|
|||
// TODO: Add some kind of logging facility in Rocket to get be able to log
|
||||
// an error/debug message if parsing a parameter fails.
|
||||
fn generate_param_statements(&self, ecx: &ExtCtxt) -> Vec<Stmt> {
|
||||
let params: Vec<_> = self.path_params(ecx).collect();
|
||||
let mut fn_param_statements = vec![];
|
||||
|
||||
// Retrieve an iterator over the user's path parameters and ensure that
|
||||
// each parameter appears in the function signature.
|
||||
for param in ¶ms {
|
||||
if self.annotated_fn.find_input(¶m.node.name).is_none() {
|
||||
self.missing_declared_err(ecx, ¶m);
|
||||
}
|
||||
}
|
||||
// Generate a statement for every declared paramter in the path.
|
||||
let mut declared_set = HashSet::new();
|
||||
for (i, param) in self.path_params(ecx).enumerate() {
|
||||
declared_set.insert(param.ident().name.clone());
|
||||
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 set: HashSet<&Name> = params.iter().map(|p| &p.node.name).collect();
|
||||
let declared = &|arg: &&Arg| set.contains(&*arg.name().unwrap());
|
||||
let ident = param.ident().prepend(PARAM_PREFIX);
|
||||
let expr = match param {
|
||||
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,
|
||||
let $ident: $ty = match _req.get_param($i) {
|
||||
let $ident: $ty = match $expr {
|
||||
Ok(v) => v,
|
||||
Err(_) => return ::rocket::Response::forward()
|
||||
};
|
||||
).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| {
|
||||
!declared(a) && self.form_param.as_ref().map_or(true, |p| {
|
||||
!a.named(&p.value().name)
|
||||
}) && self.query_param.as_ref().map_or(true, |p| {
|
||||
!a.named(&p.node.name)
|
||||
})
|
||||
!declared_set.contains(&*a.name().unwrap())
|
||||
&& self.form_param.as_ref().map_or(true, |p| {
|
||||
!a.named(&p.value().name)
|
||||
}) && self.query_param.as_ref().map_or(true, |p| {
|
||||
!a.named(&p.node.name)
|
||||
})
|
||||
};
|
||||
|
||||
// Generate the code for `form_request` parameters.
|
||||
let all = &self.annotated_fn.decl().inputs;
|
||||
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,
|
||||
let $ident: $ty = match
|
||||
<$ty as ::rocket::request::FromRequest>::from_request(&_req) {
|
||||
|
|
|
@ -7,5 +7,5 @@ mod function;
|
|||
pub use self::keyvalue::KVSpanned;
|
||||
pub use self::route::RouteParams;
|
||||
pub use self::error::ErrorParams;
|
||||
pub use self::param::ParamIter;
|
||||
pub use self::param::{Param, ParamIter};
|
||||
pub use self::function::Function;
|
||||
|
|
|
@ -3,12 +3,32 @@ use syntax::ext::base::ExtCtxt;
|
|||
use syntax::codemap::{Span, Spanned, BytePos};
|
||||
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> {
|
||||
ctxt: &'a ExtCtxt<'c>,
|
||||
span: Span,
|
||||
string: &'s str
|
||||
string: &'s str,
|
||||
}
|
||||
|
||||
impl<'s, 'a, 'c: 'a> ParamIter<'s, 'a, 'c> {
|
||||
|
@ -16,19 +36,19 @@ impl<'s, 'a, 'c: 'a> ParamIter<'s, 'a, 'c> {
|
|||
ParamIter {
|
||||
ctxt: c,
|
||||
span: p,
|
||||
string: s
|
||||
string: s,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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.
|
||||
let (start, end) = match (self.string.find('<'), self.string.find('>')) {
|
||||
(Some(i), Some(j)) => (i, j),
|
||||
_ => return None
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
// Ensure we found a valid parameter.
|
||||
|
@ -37,24 +57,41 @@ impl<'s, 'a, 'c> Iterator for ParamIter<'s, 'a, 'c> {
|
|||
return None;
|
||||
}
|
||||
|
||||
// Calculate the parameter and the span for the parameter.
|
||||
let param = &self.string[(start + 1)..end];
|
||||
// Calculate the parameter's ident.
|
||||
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;
|
||||
param_span.lo = self.span.lo + BytePos(start 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() {
|
||||
self.ctxt.span_err(param_span, "parameter names cannot be empty");
|
||||
None
|
||||
} 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
|
||||
} else {
|
||||
self.string = &self.string[(end + 1)..];
|
||||
self.span.lo = self.span.lo + BytePos((end + 1) as u32);
|
||||
Some(span(str_to_ident(param), param_span))
|
||||
let spanned_ident = span(str_to_ident(param), param_span);
|
||||
match is_many {
|
||||
true => Some(Param::Many(spanned_ident)),
|
||||
false => Some(Param::Single(spanned_ident))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue