Properly resolve dynamic segments, take 2.

Fixes #86.
This commit is contained in:
Sergio Benitez 2016-12-30 23:51:23 -06:00
parent 1f373cc83a
commit a1878ad080
6 changed files with 73 additions and 45 deletions

View File

@ -15,7 +15,6 @@ use syntax::parse::token;
use syntax::ptr::P;
use rocket::http::{Method, ContentType};
use rocket::http::uri::URI;
fn method_to_path(ecx: &ExtCtxt, method: Method) -> Path {
quote_enum!(ecx, method => ::rocket::http::Method {
@ -137,18 +136,10 @@ impl RouteGenerateExt for RouteParams {
Some(s) => <$ty as ::rocket::request::FromParam>::from_param(s),
None => return ::rocket::Outcome::Forward(_data)
}),
Param::Many(_) => {
// Determine the index the dynamic segments parameter begins.
let d = URI::new(self.path.node.as_str()).segments().enumerate()
.filter(|&(_, s)| s.starts_with("<"))
.map((&|(d, _)| d))
.next().expect("segment when segment is iterated");
quote_expr!(ecx, match _req.get_raw_segments($d) {
Some(s) => <$ty as ::rocket::request::FromSegments>::from_segments(s),
None => return ::rocket::Outcome::forward(_data)
})
},
Param::Many(_) => quote_expr!(ecx, match _req.get_raw_segments($i) {
Some(s) => <$ty as ::rocket::request::FromSegments>::from_segments(s),
None => return ::rocket::Outcome::Forward(_data)
}),
};
let original_ident = param.ident();

View File

@ -32,9 +32,9 @@ impl<'a> URI<'a> {
let qmark = uri.find('?');
let hmark = uri.find('#');
let (start, end) = (0, uri.len());
let end = uri.len();
let (path, query, fragment) = match (qmark, hmark) {
(Some(i), Some(j)) => ((start, i), Some((i+1, j)), Some((j+1, end))),
(Some(i), Some(j)) => ((0, i), Some((i+1, j)), Some((j+1, end))),
(Some(i), None) => ((0, i), Some((i+1, end)), None),
(None, Some(j)) => ((0, j), None, Some((j+1, end))),
(None, None) => ((0, end), None, None),
@ -340,7 +340,7 @@ impl<'a, 'b> Collider<URI<'b>> for URI<'a> {
/// }
/// ```
#[derive(Clone, Debug)]
pub struct Segments<'a>(&'a str);
pub struct Segments<'a>(pub &'a str);
impl<'a> Iterator for Segments<'a> {
type Item = &'a str;

View File

@ -245,8 +245,8 @@ impl<'r> Request<'r> {
.unwrap_or(ContentType::Any)
}
/// Retrieves and parses into `T` the `n`th dynamic parameter from the
/// request. Returns `Error::NoKey` if `n` is greater than the number of
/// Retrieves and parses into `T` the 0-indexed `n`th dynamic parameter from
/// the request. Returns `Error::NoKey` if `n` is greater than the number of
/// params. Returns `Error::BadParse` if the parameter type `T` can't be
/// parsed from the parameter.
///
@ -290,49 +290,58 @@ impl<'r> Request<'r> {
}
let (i, j) = params[n];
let uri_str = self.uri.as_str();
if j > uri_str.len() {
let path = self.uri.path();
if j > path.len() {
error!("Couldn't retrieve parameter: internal count incorrect.");
return None;
}
Some(&uri_str[i..j])
Some(&path[i..j])
}
/// Retrieves and parses into `T` all of the path segments in the request
/// URI beginning and including the 0-indexed `i`. `T` must implement
/// [FromSegments](/rocket/request/trait.FromSegments.html), which is used
/// to parse the segments.
/// URI beginning at the 0-indexed `n`th dynamic parameter. `T` must
/// implement [FromSegments](/rocket/request/trait.FromSegments.html), which
/// is used to parse the segments.
///
/// This method exists only to be used by manual routing. To retrieve
/// segments from a request, use Rocket's code generation facilities.
///
/// # Error
///
/// If there are less than `i` segments, returns an `Err` of `NoKey`. If
/// If there are less than `n` segments, returns an `Err` of `NoKey`. If
/// parsing the segments failed, returns an `Err` of `BadParse`.
///
/// # Example
///
/// If the request URI is `"/hello/there/i/am/here"`, then
/// If the request URI is `"/hello/there/i/am/here"`, and the matched route
/// path for this request is `"/hello/<name>/i/<segs..>"`, then
/// `request.get_segments::<T>(1)` will attempt to parse the segments
/// `"there/i/am/here"` as type `T`.
pub fn get_segments<'a, T: FromSegments<'a>>(&'a self, i: usize)
/// `"am/here"` as type `T`.
pub fn get_segments<'a, T: FromSegments<'a>>(&'a self, n: usize)
-> Result<T, Error> {
let segments = self.get_raw_segments(i).ok_or(Error::NoKey)?;
let segments = self.get_raw_segments(n).ok_or(Error::NoKey)?;
T::from_segments(segments).map_err(|_| Error::BadParse)
}
/// Get the segments beginning at the `i`th, if they exists.
/// Get the segments beginning at the `n`th dynamic parameter, if they
/// exist.
#[doc(hidden)]
pub fn get_raw_segments(&self, i: usize) -> Option<Segments> {
if i >= self.uri.segment_count() {
debug!("{} is >= segment count {}", i, self.uri().segment_count());
None
} else {
// TODO: Really want to do self.uri.segments().skip(i).into_inner(),
// but the std lib doesn't implement `into_inner` for Skip.
let mut segments = self.uri.segments();
for _ in segments.by_ref().take(i) { /* do nothing */ }
Some(segments)
pub fn get_raw_segments(&self, n: usize) -> Option<Segments> {
let params = self.params.borrow();
if n >= params.len() {
debug!("{} is >= param (segments) count {}", n, params.len());
return None;
}
let (i, j) = params[n];
let path = self.uri.path();
if j > path.len() {
error!("Couldn't retrieve segments: internal count incorrect.");
return None;
}
Some(Segments(&path[i..j]))
}
/// Convert from Hyper types into a Rocket Request.

View File

@ -133,6 +133,10 @@ mod test {
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..>"]));
assert!(unranked_route_collisions(&["<a..>", "/a/<a..>"]));
assert!(unranked_route_collisions(&["/a/<a..>", "/a/<a..>"]));
assert!(unranked_route_collisions(&["/a/b/<a..>", "/a/<a..>"]));
assert!(unranked_route_collisions(&["/a/b/c/d", "/a/<a..>"]));
}
#[test]
@ -140,10 +144,12 @@ mod test {
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"]));
assert!(!unranked_route_collisions(&["/a/d/<b..>", "/a/b/c"]));
assert!(!unranked_route_collisions(&["/a/d/<b..>", "/a/d"]));
}
#[test]
fn test_none_collisions_when_ranked() {
fn test_no_collision_when_ranked() {
assert!(!default_rank_route_collisions(&["/<a>", "/hello"]));
assert!(!default_rank_route_collisions(&["/hello/bob", "/hello/<b>"]));
assert!(!default_rank_route_collisions(&["/a/b/c/d", "/<a>/<b>/c/d"]));
@ -261,6 +267,17 @@ mod test {
assert!(!ranked_collisions(&[(0, "a/<b>"), (2, "a/<b>")]));
assert!(!ranked_collisions(&[(5, "a/<b>"), (2, "a/<b>")]));
assert!(!ranked_collisions(&[(1, "a/<b>"), (1, "b/<b>")]));
assert!(!ranked_collisions(&[(1, "a/<b..>"), (2, "a/<b..>")]));
assert!(!ranked_collisions(&[(0, "a/<b..>"), (2, "a/<b..>")]));
assert!(!ranked_collisions(&[(5, "a/<b..>"), (2, "a/<b..>")]));
assert!(!ranked_collisions(&[(1, "<a..>"), (2, "<a..>")]));
}
#[test]
fn test_ranked_collisions() {
assert!(ranked_collisions(&[(2, "a/<b..>"), (2, "a/<b..>")]));
assert!(ranked_collisions(&[(2, "a/c/<b..>"), (2, "a/<b..>")]));
assert!(ranked_collisions(&[(2, "<b..>"), (2, "a/<b..>")]));
}
macro_rules! assert_ranked_routing {

View File

@ -77,14 +77,15 @@ impl Route {
pub fn get_param_indexes(&self, uri: &URI) -> Vec<(usize, usize)> {
let route_segs = self.path.segments();
let uri_segs = uri.segments();
let start_addr = uri.as_str().as_ptr() as usize;
let start_addr = uri.path().as_ptr() as usize;
let mut result = Vec::with_capacity(self.path.segment_count());
for (route_seg, uri_seg) in route_segs.zip(uri_segs) {
let i = (uri_seg.as_ptr() as usize) - start_addr;
if route_seg.ends_with("..>") {
result.push((i, uri.path().len()));
break;
} else if route_seg.ends_with('>') {
let i = (uri_seg.as_ptr() as usize) - start_addr;
let j = i + uri_seg.len();
result.push((i, j));
}

View File

@ -25,16 +25,26 @@ fn none(path: Segments) -> String {
path.collect::<Vec<_>>().join("/")
}
#[get("/static/<user>/is/<path..>")]
fn dual(user: String, path: Segments) -> String {
user + "/is/" + &path.collect::<Vec<_>>().join("/")
}
use rocket::testing::MockRequest;
use rocket::http::Method::*;
#[test]
fn segments_works() {
let rocket = rocket::ignite().mount("/", routes![test, two, one_two, none]);
let rocket = rocket::ignite()
.mount("/", routes![test, two, one_two, none, dual])
.mount("/point", routes![test, two, one_two, dual]);
// We construct a path that matches each of the routes above. We ensure the
// prefix is stripped, confirming that dynamic segments are working.
for prefix in &["", "/test", "/two", "/one/two"] {
for prefix in &["", "/test", "/two", "/one/two",
"/point/test", "/point/two", "/point/one/two",
"/static", "/point/static"]
{
let path = "this/is/the/path/we/want";
let mut req = MockRequest::new(Get, format!("{}/{}", prefix, path));