From b755e53f63dd7ecc9b622605c9b1ee3d20e025bb Mon Sep 17 00:00:00 2001 From: Sergio Benitez Date: Thu, 8 Sep 2016 00:02:17 -0700 Subject: [PATCH] Add trailing params. --- examples/static_files/src/main.rs | 9 ++-- lib/src/lib.rs | 2 +- lib/src/param.rs | 19 +++++++ lib/src/request/request.rs | 33 +++++++++++- lib/src/rocket.rs | 2 +- lib/src/router/collider.rs | 83 +++++++++++++++++-------------- lib/src/router/mod.rs | 50 ++++++++++++++++--- lib/src/router/route.rs | 38 ++++++-------- lib/src/router/uri.rs | 33 ++++-------- macros/src/decorators/route.rs | 57 ++++++++++----------- macros/src/parser/mod.rs | 2 +- macros/src/parser/param.rs | 65 ++++++++++++++++++------ 12 files changed, 255 insertions(+), 138 deletions(-) diff --git a/examples/static_files/src/main.rs b/examples/static_files/src/main.rs index adb13a8e..5c7b7b09 100644 --- a/examples/static_files/src/main.rs +++ b/examples/static_files/src/main.rs @@ -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("/")] -fn files(file: &str) -> Result { - File::open(format!("static/{}", file)) +#[get("/")] +fn files(file: PathBuf) -> Result { + File::open(Path::new("static/").join(file)) } fn main() { diff --git a/lib/src/lib.rs b/lib/src/lib.rs index 238cd1f7..f1fbec95 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -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; diff --git a/lib/src/param.rs b/lib/src/param.rs index f0b01a33..dfda6cfc 100644 --- a/lib/src/param.rs +++ b/lib/src/param.rs @@ -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; +} + +impl<'a> FromSegments<'a> for Segments<'a> { + fn from_segments(segments: Segments<'a>) -> Result, Error> { + Ok(segments) + } +} + +impl<'a> FromSegments<'a> for PathBuf { + fn from_segments(segments: Segments<'a>) -> Result { + Ok(segments.collect()) + } +} diff --git a/lib/src/request/request.rs b/lib/src/request/request.rs index bbf119d0..950280a6 100644 --- a/lib/src/request/request.rs +++ b/lib/src/request/request.rs @@ -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>(&self, n: usize) -> Result { 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 { + 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::(); + 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())) } diff --git a/lib/src/rocket.rs b/lib/src/rocket.rs index b5494a53..33cc1f3a 100644 --- a/lib/src/rocket.rs +++ b/lib/src/rocket.rs @@ -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); } diff --git a/lib/src/router/collider.rs b/lib/src/router/collider.rs index faa66edb..222b539e 100644 --- a/lib/src/router/collider.rs +++ b/lib/src/router/collider.rs @@ -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 { 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("//hi/there", "/dude//there")); assert!(unranked_collide("///", "///")); assert!(unranked_collide("////", "////")); + assert!(unranked_collide("/", "/hi")); + assert!(unranked_collide("/", "/hi/hey")); + assert!(unranked_collide("/", "/hi/hey/hayo")); + assert!(unranked_collide("/a/", "/a/hi/hey/hayo")); + assert!(unranked_collide("/a//", "/a/hi/hey/hayo")); + assert!(unranked_collide("/a///", "/a/hi/hey/hayo")); + assert!(unranked_collide("///", "/a/hi/hey/hayo")); + assert!(unranked_collide("///hey/hayo", "/a/hi/hey/hayo")); } #[test] @@ -114,6 +117,7 @@ mod tests { assert!(unranked_collide("//", "/a/b")); assert!(unranked_collide("//bc", "/a/b")); assert!(unranked_collide("//bcd", "/a/b")); + assert!(unranked_collide("/", "///a///")); } #[test] @@ -130,6 +134,10 @@ mod tests { assert!(!unranked_collide("/a/", "/b/")); assert!(!unranked_collide("/a/", "/b/")); assert!(!unranked_collide("/a/", "/b/")); + assert!(!unranked_collide("/", "/")); + assert!(!unranked_collide("/hi/", "/hi")); + assert!(!unranked_collide("/hi/", "/hi/")); + assert!(!unranked_collide("/", "//////")); } #[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/there", "/hi/there")); - assert!(!s_r_collide("//c", "/hi/person")); - assert!(!s_r_collide("//cd", "/hi/e")); - assert!(!s_r_collide("/a/", "/b/")); - assert!(!s_r_collide("/a/", "/b/")); - assert!(!s_r_collide("/a/", "/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/there", "/hi/there")); - assert!(!r_s_collide("//c", "/hi/person")); - assert!(!r_s_collide("//cd", "/hi/e")); - assert!(!r_s_collide("/a/", "/b/")); - assert!(!r_s_collide("/a/", "/b/")); - assert!(!r_s_collide("/a/", "/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/", "/b/")); assert!(!s_s_collide("/a/", "/b/")); 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/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/there", "/hi/there")); + assert!(!s_s_collide("//c", "/hi/person")); + assert!(!s_s_collide("//cd", "/hi/e")); + assert!(!s_s_collide("/a/", "/b/")); + assert!(!s_s_collide("/a/", "/b/")); + 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/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/there", "/hi/there")); + assert!(!s_s_collide("//c", "/hi/person")); + assert!(!s_s_collide("//cd", "/hi/e")); + assert!(!s_s_collide("/a/", "/b/")); + assert!(!s_s_collide("/a/", "/b/")); + assert!(!s_s_collide("/a/", "/b/")); + assert!(!s_s_collide("/", "/")); + assert!(!s_s_collide("/hi/", "/hi/")); + assert!(!s_s_collide("/a/hi/", "/a/hi/")); } fn ct_route(m: Method, s: &str, ct: &str) -> Route { diff --git a/lib/src/router/mod.rs b/lib/src/router/mod.rs index 3a597678..7d8cbaae 100644 --- a/lib/src/router/mod.rs +++ b/lib/src/router/mod.rs @@ -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(&["/", "/"])); assert!(unranked_route_collisions(&["/hello/bob", "/hello/"])); assert!(unranked_route_collisions(&["/a/b//d", "///c/d"])); + assert!(unranked_route_collisions(&["/a/b", "/"])); + assert!(unranked_route_collisions(&["/a/b/c", "/a/"])); + assert!(unranked_route_collisions(&["//b", "/a/"])); + assert!(unranked_route_collisions(&["/a/", "/a/"])); + assert!(unranked_route_collisions(&["/a/b/", "/a/"])); + } + + #[test] + fn test_no_collisions() { + assert!(!unranked_route_collisions(&["/", "/a/"])); + assert!(!unranked_route_collisions(&["/a/b", "/a/b/c"])); + assert!(!unranked_route_collisions(&["/a/b/c/d", "/a/b/c//e"])); } #[test] @@ -128,6 +145,8 @@ mod test { assert!(!default_rank_route_collisions(&["/hello/bob", "/hello/"])); assert!(!default_rank_route_collisions(&["/a/b/c/d", "///c/d"])); assert!(!default_rank_route_collisions(&["/hi", "/"])); + assert!(!default_rank_route_collisions(&["/hi", "/"])); + assert!(!default_rank_route_collisions(&["/a/b", "/a/b/"])); } 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(&["/"]); + 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, "///edit"), (2, "/profile/"), (5, "///")], expect: (0, "///edit"), (5, "///") ); + + assert_ranked_routing!( + to: "/a/b", + with: [(0, "/a/b"), (1, "/a/")], + expect: (0, "/a/b"), (1, "/a/") + ); + + assert_ranked_routing!( + to: "/a/b/c/d/e/f", + with: [(1, "/a/"), (2, "/a/b/")], + expect: (1, "/a/"), (2, "/a/b/") + ); } fn match_params(router: &Router, path: &str, expected: &[&str]) -> bool { diff --git a/lib/src/router/route.rs b/lib/src/router/route.rs index c672d3f9..eb77311f 100644 --- a/lib/src/router/route.rs +++ b/lib/src/router/route.rs @@ -53,12 +53,14 @@ impl Route { // is, whether you can have: /ab/ or even /:/ // 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 { + ::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 for &'a str { - fn collides_with(&self, other: &Route) -> bool { - let path = URI::new(self); - path.collides_with(&other.path) - } -} - -impl Collider 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> 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) } } diff --git a/lib/src/router/uri.rs b/lib/src/router/uri.rs index ba8b8581..46ccd80f 100644 --- a/lib/src/router/uri.rs +++ b/lib/src/router/uri.rs @@ -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> for URIBuf { - fn collides_with(&self, other: &URI<'a>) -> bool { - self.as_uri().collides_with(other) - } -} - -impl<'a> Collider 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> { diff --git a/macros/src/decorators/route.rs b/macros/src/decorators/route.rs index 3596a636..b7328da8 100644 --- a/macros/src/decorators/route.rs +++ b/macros/src/decorators/route.rs @@ -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 { - 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) { diff --git a/macros/src/parser/mod.rs b/macros/src/parser/mod.rs index e923ed2c..c9cfb19d 100644 --- a/macros/src/parser/mod.rs +++ b/macros/src/parser/mod.rs @@ -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; diff --git a/macros/src/parser/param.rs b/macros/src/parser/param.rs index fc8d61f1..59092bc1 100644 --- a/macros/src/parser/param.rs +++ b/macros/src/parser/param.rs @@ -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), + Many(Spanned) +} + +impl Param { + pub fn inner(&self) -> &Spanned { + 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; + type Item = Param; - fn next(&mut self) -> Option> { + fn next(&mut self) -> Option { // 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)) + } } + } } -