From d610e0adff115b2646ab454199ab918e18c68c8c Mon Sep 17 00:00:00 2001 From: Sergio Benitez Date: Thu, 29 Dec 2016 21:26:22 -0600 Subject: [PATCH 01/14] Update Tera dependency to 0.6. --- contrib/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/Cargo.toml b/contrib/Cargo.toml index c14362fb..e07a6719 100644 --- a/contrib/Cargo.toml +++ b/contrib/Cargo.toml @@ -32,4 +32,4 @@ serde_json = { version = "^0.8", optional = true } handlebars = { version = "^0.23", optional = true, features = ["serde_type"] } glob = { version = "^0.2", optional = true } lazy_static = { version = "^0.2", optional = true } -tera = { version = "^0.5", optional = true } +tera = { version = "^0.6", optional = true } From 337173eaf417d54187a0ca8b24b475e8a3fe8aad Mon Sep 17 00:00:00 2001 From: Sergio Benitez Date: Thu, 29 Dec 2016 22:06:35 -0600 Subject: [PATCH 02/14] Use register_custom_derive to remove custom_derive deprecation warning. --- codegen/src/lib.rs | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/codegen/src/lib.rs b/codegen/src/lib.rs index 81300fb1..cf7d8c94 100644 --- a/codegen/src/lib.rs +++ b/codegen/src/lib.rs @@ -93,6 +93,7 @@ #![feature(quote, concat_idents, plugin_registrar, rustc_private, unicode)] #![feature(custom_attribute)] #![allow(unused_attributes)] +#![allow(deprecated)] #[macro_use] extern crate syntax; #[macro_use] extern crate log; @@ -127,6 +128,14 @@ macro_rules! register_decorators { ) } +macro_rules! register_derives { + ($registry:expr, $($name:expr => $func:ident),+) => ( + $($registry.register_custom_derive(Symbol::intern($name), + SyntaxExtension::MultiDecorator(Box::new(decorators::$func))); + )+ + ) +} + /// Compiler hook for Rust to register plugins. #[plugin_registrar] pub fn plugin_registrar(reg: &mut Registry) { @@ -138,9 +147,11 @@ pub fn plugin_registrar(reg: &mut Registry) { reg.register_macro("routes", macros::routes); reg.register_macro("errors", macros::errors); - register_decorators!(reg, - "derive_FromForm" => from_form_derive, + register_derives!(reg, + "derive_FromForm" => from_form_derive + ); + register_decorators!(reg, "error" => error_decorator, "route" => route_decorator, "get" => get_decorator, From bad0c20cda2e8ad9aa907ee573471fd3304e0923 Mon Sep 17 00:00:00 2001 From: Sergio Benitez Date: Fri, 30 Dec 2016 00:22:05 -0600 Subject: [PATCH 03/14] Check for rustc version incompatibility when building codegen. --- codegen/Cargo.toml | 4 +++ codegen/build.rs | 71 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+) create mode 100644 codegen/build.rs diff --git a/codegen/Cargo.toml b/codegen/Cargo.toml index aa994165..0b3031b9 100644 --- a/codegen/Cargo.toml +++ b/codegen/Cargo.toml @@ -9,6 +9,7 @@ repository = "https://github.com/SergioBenitez/Rocket" readme = "../README.md" keywords = ["rocket", "web", "framework", "code", "generation"] license = "MIT/Apache-2.0" +build = "build.rs" [lib] plugin = true @@ -19,3 +20,6 @@ log = "^0.3" [dev-dependencies] compiletest_rs = "^0.2" + +[build-dependencies] +ansi_term = "^0.9" diff --git a/codegen/build.rs b/codegen/build.rs new file mode 100644 index 00000000..96a0199a --- /dev/null +++ b/codegen/build.rs @@ -0,0 +1,71 @@ +#![feature(slice_patterns)] + +//! This tiny build script ensures that rocket_codegen is not compiled with an +//! incompatible version of rust. It does this by executing `rustc --version` +//! and comparing the version to `MIN_VERSION`, the minimum required version. If +//! the installed version is less than the minimum required version, an error is +//! printed out to the console and compilation is halted. + +extern crate ansi_term; + +use std::env; +use std::process::Command; + +use ansi_term::Colour::{Red, Yellow, Blue, White}; + +// Specifies the minimum nightly version needed to compile Rocket's codegen. +const MIN_VERSION: &'static str = "2016-12-28"; + +// Convenience macro for writing to stderr. +macro_rules! printerr { + ($($arg:tt)*) => ({ + use std::io::prelude::*; + write!(&mut ::std::io::stderr(), "{}\n", format_args!($($arg)*)) + .expect("Failed to write to stderr.") + }) +} + +// Convert a string of %Y-%m-%d to a single u32 maintaining ordering. +fn str_to_ymd(ymd: &str) -> Option { + let ymd: Vec<_> = ymd.split("-").filter_map(|s| s.parse::().ok()).collect(); + match ymd.as_slice() { + &[y, m, d] => Some((y << 9) | (m << 5) | d), + _ => None, + } +} + +fn main() { + // Run rustc to get the version information. + let output = env::var("RUSTC").ok() + .and_then(|rustc| Command::new(rustc).arg("--version").output().ok()) + .and_then(|output| String::from_utf8(output.stdout).ok()) + .and_then(|s| s.split(" ").nth(3).map(|s| s.to_string())) + .map(|s| s.trim_right().trim_right_matches(")").to_string()); + + if let Some(ref version) = output { + let needed = str_to_ymd(MIN_VERSION); + let actual = str_to_ymd(version); + if let (Some(needed), Some(actual)) = (needed, actual) { + if actual < needed { + printerr!("{} {}", + Red.bold().paint("Error:"), + White.paint("Rocket codegen requires a newer version of rustc.")); + printerr!("{}{}{}", + Blue.paint("Use `"), + White.paint("rustup update"), + Blue.paint("` or your preferred method to update Rust.")); + printerr!("{} {}. {} {}.", + White.paint("Installed version is:"), + Yellow.paint(version.as_str()), + White.paint("Minimum required:"), + Yellow.paint(MIN_VERSION)); + panic!("Aborting compilation due to incompatible compiler.") + } else { + return; + } + } + } + + printerr!("{}", Yellow.paint("Warning: Rocket was unable to check rustc compatbility.")); + printerr!("{}", Yellow.paint("Build may fail due to incompatible rustc version.")); +} From 2de006d9f91589a885924acc8712e87d310692e4 Mon Sep 17 00:00:00 2001 From: Sergio Benitez Date: Fri, 30 Dec 2016 00:50:30 -0600 Subject: [PATCH 04/14] Allow head decorator and options via route decorator. --- codegen/src/decorators/route.rs | 5 ++++- codegen/src/lib.rs | 2 ++ codegen/src/parser/route.rs | 4 ++-- codegen/tests/run-pass/empty-fn.rs | 7 ++++--- codegen/tests/run-pass/methods.rs | 28 ++++++++++++++++++++++++++++ 5 files changed, 40 insertions(+), 6 deletions(-) create mode 100644 codegen/tests/run-pass/methods.rs diff --git a/codegen/src/decorators/route.rs b/codegen/src/decorators/route.rs index 7bf2bf01..26d79808 100644 --- a/codegen/src/decorators/route.rs +++ b/codegen/src/decorators/route.rs @@ -290,5 +290,8 @@ method_decorator!(get_decorator, Get); method_decorator!(put_decorator, Put); method_decorator!(post_decorator, Post); method_decorator!(delete_decorator, Delete); -method_decorator!(patch_decorator, Patch); method_decorator!(head_decorator, Head); +method_decorator!(patch_decorator, Patch); + +// TODO: Allow this once Diesel incompatibility is fixed. +// method_decorator!(options_decorator, Options); diff --git a/codegen/src/lib.rs b/codegen/src/lib.rs index cf7d8c94..68ca4cfb 100644 --- a/codegen/src/lib.rs +++ b/codegen/src/lib.rs @@ -160,5 +160,7 @@ pub fn plugin_registrar(reg: &mut Registry) { "delete" => delete_decorator, "head" => head_decorator, "patch" => patch_decorator + // TODO: Allow this once Diesel incompatibility is fixed. Fix docs too. + // "options" => options_decorator ); } diff --git a/codegen/src/parser/route.rs b/codegen/src/parser/route.rs index 98b324a2..07663869 100644 --- a/codegen/src/parser/route.rs +++ b/codegen/src/parser/route.rs @@ -134,8 +134,8 @@ impl RouteParams { fn is_valid_method(method: Method) -> bool { use rocket::http::Method::*; match method { - Get | Put | Post | Delete | Patch => true, - _ => false + Get | Put | Post | Delete | Head | Patch | Options => true, + Trace | Connect => false } } diff --git a/codegen/tests/run-pass/empty-fn.rs b/codegen/tests/run-pass/empty-fn.rs index 86b909f7..66ccad22 100644 --- a/codegen/tests/run-pass/empty-fn.rs +++ b/codegen/tests/run-pass/empty-fn.rs @@ -6,6 +6,7 @@ extern crate rocket; #[get("")] fn get() -> &'static str { "hi" } -fn main() { - let _ = routes![get]; -} +#[get("/")] +fn get_empty() { } + +fn main() { } diff --git a/codegen/tests/run-pass/methods.rs b/codegen/tests/run-pass/methods.rs new file mode 100644 index 00000000..24d41d61 --- /dev/null +++ b/codegen/tests/run-pass/methods.rs @@ -0,0 +1,28 @@ +#![feature(plugin)] +#![plugin(rocket_codegen)] + +extern crate rocket; + +#[get("/")] +fn get() { } + +#[put("/")] +fn put() { } + +#[post("/")] +fn post() { } + +#[delete("/")] +fn delete() { } + +#[head("/")] +fn head() { } + +#[patch("/")] +fn patch() { } + +// TODO: Allow this once Diesel incompatibility is fixed. +// #[options("/")] +// fn options() { } + +fn main() { } From 366eb5d158c7fe6486426403f2127740e9c29295 Mon Sep 17 00:00:00 2001 From: Sergio Benitez Date: Fri, 30 Dec 2016 01:46:47 -0600 Subject: [PATCH 05/14] Test methods via route attribute. --- codegen/tests/run-pass/methods.rs | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/codegen/tests/run-pass/methods.rs b/codegen/tests/run-pass/methods.rs index 24d41d61..d74547a0 100644 --- a/codegen/tests/run-pass/methods.rs +++ b/codegen/tests/run-pass/methods.rs @@ -3,26 +3,26 @@ extern crate rocket; -#[get("/")] -fn get() { } +#[get("/")] fn get() { } +#[route(GET, "/")] fn get_r() { } -#[put("/")] -fn put() { } +#[put("/")] fn put() { } +#[route(PUT, "/")] fn put_r() { } -#[post("/")] -fn post() { } +#[post("/")] fn post() { } +#[route(POST, "/")] fn post_r() { } -#[delete("/")] -fn delete() { } +#[delete("/")] fn delete() { } +#[route(DELETE, "/")] fn delete_r() { } -#[head("/")] -fn head() { } +#[head("/")] fn head() { } +#[route(HEAD, "/")] fn head_r() { } -#[patch("/")] -fn patch() { } +#[patch("/")] fn patch() { } +#[route(PATCH, "/")] fn patch_r() { } // TODO: Allow this once Diesel incompatibility is fixed. -// #[options("/")] -// fn options() { } +// #[options("/")] fn options() { } +#[route(OPTIONS, "/")] fn options_r() { } fn main() { } From 524a2d889d97c2fc26e7a53a15d0b9b0178ffe3a Mon Sep 17 00:00:00 2001 From: Sergio Benitez Date: Fri, 30 Dec 2016 20:06:48 -0600 Subject: [PATCH 06/14] Fix typo in build warning message. --- codegen/build.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codegen/build.rs b/codegen/build.rs index 96a0199a..ced3f47d 100644 --- a/codegen/build.rs +++ b/codegen/build.rs @@ -66,6 +66,6 @@ fn main() { } } - printerr!("{}", Yellow.paint("Warning: Rocket was unable to check rustc compatbility.")); + printerr!("{}", Yellow.paint("Warning: Rocket was unable to check rustc compatibility.")); printerr!("{}", Yellow.paint("Build may fail due to incompatible rustc version.")); } From 1f373cc83a21f490af4ef00cdfa75cd2f29a40e7 Mon Sep 17 00:00:00 2001 From: Sergio Benitez Date: Fri, 30 Dec 2016 20:15:28 -0600 Subject: [PATCH 07/14] Rename 'content_type' Route field to 'format'. --- lib/src/router/collider.rs | 2 +- lib/src/router/route.rs | 20 +++++++++++--------- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/lib/src/router/collider.rs b/lib/src/router/collider.rs index 91fef1ce..a572ac64 100644 --- a/lib/src/router/collider.rs +++ b/lib/src/router/collider.rs @@ -201,7 +201,7 @@ mod tests { fn ct_route(m: Method, s: &str, ct: &str) -> Route { let mut route_a = Route::new(m, s, dummy_handler); - route_a.content_type = ContentType::from_str(ct).expect("Whoops!"); + route_a.format = ContentType::from_str(ct).expect("Whoops!"); route_a } diff --git a/lib/src/router/route.rs b/lib/src/router/route.rs index 1a8d0323..d03f9a1e 100644 --- a/lib/src/router/route.rs +++ b/lib/src/router/route.rs @@ -23,7 +23,7 @@ pub struct Route { /// The rank of this route. Lower ranks have higher priorities. pub rank: isize, /// The Content-Type this route matches against. - pub content_type: ContentType, + pub format: ContentType, } fn default_rank(path: &str) -> isize { @@ -45,7 +45,7 @@ impl Route { handler: handler, rank: default_rank(path.as_ref()), path: URI::from(path.as_ref().to_string()), - content_type: ContentType::Any, + format: ContentType::Any, } } @@ -58,7 +58,7 @@ impl Route { path: URI::from(path.as_ref().to_string()), handler: handler, rank: rank, - content_type: ContentType::Any, + format: ContentType::Any, } } @@ -101,7 +101,7 @@ impl Clone for Route { handler: self.handler, rank: self.rank, path: self.path.clone(), - content_type: self.content_type.clone(), + format: self.format.clone(), } } } @@ -114,8 +114,8 @@ impl fmt::Display for Route { write!(f, " [{}]", White.paint(&self.rank))?; } - if !self.content_type.is_any() { - write!(f, " {}", Yellow.paint(&self.content_type)) + if !self.format.is_any() { + write!(f, " {}", Yellow.paint(&self.format)) } else { Ok(()) } @@ -132,7 +132,7 @@ impl fmt::Debug for Route { impl<'a> From<&'a StaticRouteInfo> for Route { fn from(info: &'a StaticRouteInfo) -> Route { let mut route = Route::new(info.method, info.path, info.handler); - route.content_type = info.format.clone().unwrap_or(ContentType::Any); + route.format = info.format.clone().unwrap_or(ContentType::Any); if let Some(rank) = info.rank { route.rank = rank; } @@ -145,7 +145,7 @@ impl Collider for Route { fn collides_with(&self, b: &Route) -> bool { self.method == b.method && self.rank == b.rank - && self.content_type.collides_with(&b.content_type) + && self.format.collides_with(&b.format) && self.path.collides_with(&b.path) } } @@ -154,6 +154,8 @@ impl<'r> Collider> for Route { fn collides_with(&self, req: &Request<'r>) -> bool { self.method == req.method() && req.uri().collides_with(&self.path) - && req.content_type().collides_with(&self.content_type) + // FIXME: On payload requests, check Content-Type. On non-payload + // requests, check Accept. + && req.content_type().collides_with(&self.format) } } From a1878ad080c2c445edbac1de7b6d4c0684445564 Mon Sep 17 00:00:00 2001 From: Sergio Benitez Date: Fri, 30 Dec 2016 23:51:23 -0600 Subject: [PATCH 08/14] Properly resolve dynamic segments, take 2. Fixes #86. --- codegen/src/decorators/route.rs | 17 ++---- lib/src/http/uri.rs | 6 +- lib/src/request/request.rs | 57 +++++++++++-------- lib/src/router/mod.rs | 19 ++++++- lib/src/router/route.rs | 5 +- ...s-issue-41.rs => segments-issues-41-86.rs} | 14 ++++- 6 files changed, 73 insertions(+), 45 deletions(-) rename lib/tests/{segments-issue-41.rs => segments-issues-41-86.rs} (69%) diff --git a/codegen/src/decorators/route.rs b/codegen/src/decorators/route.rs index 26d79808..c82967ef 100644 --- a/codegen/src/decorators/route.rs +++ b/codegen/src/decorators/route.rs @@ -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(); diff --git a/lib/src/http/uri.rs b/lib/src/http/uri.rs index ffeadf37..fe01b854 100644 --- a/lib/src/http/uri.rs +++ b/lib/src/http/uri.rs @@ -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> 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; diff --git a/lib/src/request/request.rs b/lib/src/request/request.rs index 8cf765d5..f6868d11 100644 --- a/lib/src/request/request.rs +++ b/lib/src/request/request.rs @@ -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//i/"`, then /// `request.get_segments::(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 { - 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 { - 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 { + 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. diff --git a/lib/src/router/mod.rs b/lib/src/router/mod.rs index 44b0622e..e76f244f 100644 --- a/lib/src/router/mod.rs +++ b/lib/src/router/mod.rs @@ -133,6 +133,10 @@ mod test { assert!(unranked_route_collisions(&["//b", "/a/"])); assert!(unranked_route_collisions(&["/a/", "/a/"])); assert!(unranked_route_collisions(&["/a/b/", "/a/"])); + assert!(unranked_route_collisions(&["", "/a/"])); + assert!(unranked_route_collisions(&["/a/", "/a/"])); + assert!(unranked_route_collisions(&["/a/b/", "/a/"])); + assert!(unranked_route_collisions(&["/a/b/c/d", "/a/"])); } #[test] @@ -140,10 +144,12 @@ mod test { 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"])); + assert!(!unranked_route_collisions(&["/a/d/", "/a/b/c"])); + assert!(!unranked_route_collisions(&["/a/d/", "/a/d"])); } #[test] - fn test_none_collisions_when_ranked() { + fn test_no_collision_when_ranked() { assert!(!default_rank_route_collisions(&["/", "/hello"])); assert!(!default_rank_route_collisions(&["/hello/bob", "/hello/"])); assert!(!default_rank_route_collisions(&["/a/b/c/d", "///c/d"])); @@ -261,6 +267,17 @@ mod test { assert!(!ranked_collisions(&[(0, "a/"), (2, "a/")])); assert!(!ranked_collisions(&[(5, "a/"), (2, "a/")])); assert!(!ranked_collisions(&[(1, "a/"), (1, "b/")])); + assert!(!ranked_collisions(&[(1, "a/"), (2, "a/")])); + assert!(!ranked_collisions(&[(0, "a/"), (2, "a/")])); + assert!(!ranked_collisions(&[(5, "a/"), (2, "a/")])); + assert!(!ranked_collisions(&[(1, ""), (2, "")])); + } + + #[test] + fn test_ranked_collisions() { + assert!(ranked_collisions(&[(2, "a/"), (2, "a/")])); + assert!(ranked_collisions(&[(2, "a/c/"), (2, "a/")])); + assert!(ranked_collisions(&[(2, ""), (2, "a/")])); } macro_rules! assert_ranked_routing { diff --git a/lib/src/router/route.rs b/lib/src/router/route.rs index d03f9a1e..a6f731b1 100644 --- a/lib/src/router/route.rs +++ b/lib/src/router/route.rs @@ -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)); } diff --git a/lib/tests/segments-issue-41.rs b/lib/tests/segments-issues-41-86.rs similarity index 69% rename from lib/tests/segments-issue-41.rs rename to lib/tests/segments-issues-41-86.rs index 217b5bb3..8639f554 100644 --- a/lib/tests/segments-issue-41.rs +++ b/lib/tests/segments-issues-41-86.rs @@ -25,16 +25,26 @@ fn none(path: Segments) -> String { path.collect::>().join("/") } +#[get("/static//is/")] +fn dual(user: String, path: Segments) -> String { + user + "/is/" + &path.collect::>().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)); From d19cb0349c33582d94870e3061e82c47f5846668 Mon Sep 17 00:00:00 2001 From: Greg Edwards Date: Fri, 30 Dec 2016 21:33:51 -0700 Subject: [PATCH 09/14] Only override request methods via '_method' on POST. --- lib/src/rocket.rs | 2 +- lib/tests/form_method-issue-45.rs | 15 ++++++++++++++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/lib/src/rocket.rs b/lib/src/rocket.rs index a3d76233..cc989df9 100644 --- a/lib/src/rocket.rs +++ b/lib/src/rocket.rs @@ -141,7 +141,7 @@ impl Rocket { // field which we use to reinterpret the request's method. let data_len = data.peek().len(); let (min_len, max_len) = ("_method=get".len(), "_method=delete".len()); - if req.content_type().is_form() && data_len >= min_len { + if req.method() == Method::Post && req.content_type().is_form() && data_len >= min_len { let form = unsafe { from_utf8_unchecked(&data.peek()[..min(data_len, max_len)]) }; diff --git a/lib/tests/form_method-issue-45.rs b/lib/tests/form_method-issue-45.rs index d451d083..ef071feb 100644 --- a/lib/tests/form_method-issue-45.rs +++ b/lib/tests/form_method-issue-45.rs @@ -4,6 +4,7 @@ extern crate rocket; use rocket::request::Form; +use rocket::http::Status; #[derive(FromForm)] struct FormData { @@ -24,7 +25,7 @@ use rocket::http::ContentType; fn method_eval() { let rocket = rocket::ignite().mount("/", routes![bug]); - let mut req = MockRequest::new(Patch, "/") + let mut req = MockRequest::new(Post, "/") .header(ContentType::Form) .body("_method=patch&form_data=Form+data"); @@ -32,3 +33,15 @@ fn method_eval() { let body_str = response.body().and_then(|b| b.into_string()); assert_eq!(body_str, Some("OK".to_string())); } + +#[test] +fn get_passes_through() { + let rocket = rocket::ignite().mount("/", routes![bug]); + + let mut req = MockRequest::new(Get, "/") + .header(ContentType::Form) + .body("_method=patch&form_data=Form+data"); + + let mut response = req.dispatch_with(&rocket); + assert_eq!(response.status(), Status::NotFound); +} From 83bbea7d4a9a1b8b89fd54c39209b224dd9cb302 Mon Sep 17 00:00:00 2001 From: Sergio Benitez Date: Sat, 31 Dec 2016 00:48:31 -0600 Subject: [PATCH 10/14] Fix decoding of form value Strings. --- examples/forms/src/main.rs | 4 +- lib/src/request/form/from_form_value.rs | 16 ++------ lib/tests/form_method-issue-45.rs | 2 +- lib/tests/form_value_decoding-issue-82.rs | 45 +++++++++++++++++++++++ 4 files changed, 51 insertions(+), 16 deletions(-) create mode 100644 lib/tests/form_value_decoding-issue-82.rs diff --git a/examples/forms/src/main.rs b/examples/forms/src/main.rs index 45b410e1..633e90e3 100644 --- a/examples/forms/src/main.rs +++ b/examples/forms/src/main.rs @@ -12,7 +12,7 @@ use rocket::response::Redirect; #[derive(FromForm)] struct UserLogin<'r> { username: &'r str, - password: &'r str, + password: String, age: Result, } @@ -27,7 +27,7 @@ fn login<'a>(user_form: Form<'a, UserLogin<'a>>) -> Result { }; if user.username == "Sergio" { - match user.password { + match user.password.as_str() { "password" => Ok(Redirect::to("/user/Sergio")), _ => Err("Wrong password!".to_string()) } diff --git a/lib/src/request/form/from_form_value.rs b/lib/src/request/form/from_form_value.rs index 8e8b2afd..218badb4 100644 --- a/lib/src/request/form/from_form_value.rs +++ b/lib/src/request/form/from_form_value.rs @@ -73,21 +73,11 @@ impl<'v> FromFormValue<'v> for String { // This actually parses the value according to the standard. fn from_form_value(v: &'v str) -> Result { - let result = URI::percent_decode(v.as_bytes()); + let replaced = v.replace("+", " "); + let result = URI::percent_decode(replaced.as_bytes()); match result { Err(_) => Err(v), - Ok(mut string) => Ok({ - // Entirely safe because we're changing the single-byte '+'. - unsafe { - for c in string.to_mut().as_mut_vec() { - if *c == b'+' { - *c = b' '; - } - } - } - - string.into_owned() - }) + Ok(string) => Ok(string.into_owned()) } } } diff --git a/lib/tests/form_method-issue-45.rs b/lib/tests/form_method-issue-45.rs index ef071feb..ff1e8bff 100644 --- a/lib/tests/form_method-issue-45.rs +++ b/lib/tests/form_method-issue-45.rs @@ -42,6 +42,6 @@ fn get_passes_through() { .header(ContentType::Form) .body("_method=patch&form_data=Form+data"); - let mut response = req.dispatch_with(&rocket); + let response = req.dispatch_with(&rocket); assert_eq!(response.status(), Status::NotFound); } diff --git a/lib/tests/form_value_decoding-issue-82.rs b/lib/tests/form_value_decoding-issue-82.rs new file mode 100644 index 00000000..d49ddf55 --- /dev/null +++ b/lib/tests/form_value_decoding-issue-82.rs @@ -0,0 +1,45 @@ +#![feature(plugin, custom_derive)] +#![plugin(rocket_codegen)] + +extern crate rocket; + +use rocket::request::Form; + +#[derive(FromForm)] +struct FormData { + form_data: String, +} + +#[post("/", data = "")] +fn bug(form_data: Form) -> String { + form_data.into_inner().form_data +} + +use rocket::testing::MockRequest; +use rocket::http::Method::*; +use rocket::http::ContentType; +use rocket::http::Status; + +fn check_decoding(raw: &str, decoded: &str) { + let rocket = rocket::ignite().mount("/", routes![bug]); + + let mut req = MockRequest::new(Post, "/") + .header(ContentType::Form) + .body(format!("form_data={}", raw)); + + let mut response = req.dispatch_with(&rocket); + let body_string = response.body().and_then(|b| b.into_string()); + assert_eq!(response.status(), Status::Ok); + assert_eq!(Some(decoded.to_string()), body_string); +} + +#[test] +fn test_proper_decoding() { + check_decoding("password", "password"); + check_decoding("", ""); + check_decoding("+", " "); + check_decoding("%2B", "+"); + check_decoding("1+1", "1 1"); + check_decoding("1%2B1", "1+1"); + check_decoding("%3Fa%3D1%26b%3D2", "?a=1&b=2"); +} From 0af01abe5f9e8f69ffa85229f4834e268783e024 Mon Sep 17 00:00:00 2001 From: Liigo Zhuang Date: Fri, 30 Dec 2016 13:57:37 +0800 Subject: [PATCH 11/14] Fix decoding of String form values. @liigo originated a fix and found the problem in #82. --- examples/forms/src/tests.rs | 2 +- lib/src/request/form/from_form_value.rs | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/examples/forms/src/tests.rs b/examples/forms/src/tests.rs index 1e0ccb89..8e39f53c 100644 --- a/examples/forms/src/tests.rs +++ b/examples/forms/src/tests.rs @@ -13,7 +13,7 @@ fn test_login(username: &str, password: &str, age: isize, status: Status, let mut response = req.dispatch_with(&rocket); let body_str = response.body().and_then(|body| body.into_string()); - println!("Checking: {:?}/{:?}", username, password); + println!("Checking: {:?}/{:?}/{:?}/{:?}", username, password, age, body_str); assert_eq!(response.status(), status); if let Some(string) = body { diff --git a/lib/src/request/form/from_form_value.rs b/lib/src/request/form/from_form_value.rs index 218badb4..012d2b94 100644 --- a/lib/src/request/form/from_form_value.rs +++ b/lib/src/request/form/from_form_value.rs @@ -74,8 +74,7 @@ impl<'v> FromFormValue<'v> for String { // This actually parses the value according to the standard. fn from_form_value(v: &'v str) -> Result { let replaced = v.replace("+", " "); - let result = URI::percent_decode(replaced.as_bytes()); - match result { + match URI::percent_decode(replaced.as_bytes()) { Err(_) => Err(v), Ok(string) => Ok(string.into_owned()) } From 6fdc6f025facf4b44065a9658a360c8d1f6e65cb Mon Sep 17 00:00:00 2001 From: Sergio Benitez Date: Sat, 31 Dec 2016 01:31:11 -0600 Subject: [PATCH 12/14] New version: 0.1.3. --- CHANGELOG.md | 43 +++++++++++++++++++++++++++++++++++++++++++ codegen/Cargo.toml | 4 ++-- contrib/Cargo.toml | 4 ++-- lib/Cargo.toml | 4 ++-- 4 files changed, 49 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e07bafe1..ecba3489 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,46 @@ +# Version 0.1.3 (Dec 31, 2016) + +## Core + + * Typo in `Outcome` formatting fixed (Succcess -> Success). + * Added `ContentType::CSV`. + * Dynamic segments parameters are properly resolved, even when mounted. + * Request methods are only overridden via `_method` field on POST. + * Form value `String`s are properly decoded. + +## Codegen + + * The `_method` field is now properly ignored in `FromForm` derivation. + * Unknown Content-Types in `format` no longer result in an error. + * Deriving `FromForm` no longer results in a deprecation warning. + * Codegen will refuse to build with incompatible rustc, presenting error + message and suggestion. + * Added `head` as a valid decorator for `HEAD` requests. + * Added `route(OPTIONS)` is a valid decorator for `OPTIONS` requests. + +## Contrib + + * Files with the `.tera` extension are properly autoescaped. + * Nested template names are properly resolved on Windows. + * Template implements `Display`. + * Tera dependency updated to version 0.6. + +## Docs + + * Todo example requirements clarified in its `README`. + +## Testing + + * Tests added for `config`, `optional_result`, `optional_redirect`, and + `query_params` examples. + * Testing script checks for and disallows tab characters. + +## Infrastructure + + * New script (`bump_version.sh`) automates version bumps. + * Config script emits error when readlink/readpath support is bad. + * Travis badge points to public builds. + # Version 0.1.2 (Dec 24, 2016) ## Codegen diff --git a/codegen/Cargo.toml b/codegen/Cargo.toml index 0b3031b9..a428513f 100644 --- a/codegen/Cargo.toml +++ b/codegen/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rocket_codegen" -version = "0.1.2" +version = "0.1.3" authors = ["Sergio Benitez "] description = "Code generation for the Rocket web framework." documentation = "https://api.rocket.rs/rocket_codegen/" @@ -15,7 +15,7 @@ build = "build.rs" plugin = true [dependencies] -rocket = { version = "0.1.2", path = "../lib/" } +rocket = { version = "0.1.3", path = "../lib/" } log = "^0.3" [dev-dependencies] diff --git a/contrib/Cargo.toml b/contrib/Cargo.toml index e07a6719..0da08d61 100644 --- a/contrib/Cargo.toml +++ b/contrib/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rocket_contrib" -version = "0.1.2" +version = "0.1.3" authors = ["Sergio Benitez "] description = "Community contributed libraries for the Rocket web framework." documentation = "https://api.rocket.rs/rocket_contrib/" @@ -21,7 +21,7 @@ templates = ["serde", "serde_json", "lazy_static_macro", "glob"] lazy_static_macro = ["lazy_static"] [dependencies] -rocket = { version = "0.1.2", path = "../lib/" } +rocket = { version = "0.1.3", path = "../lib/" } log = "^0.3" # JSON and templating dependencies. diff --git a/lib/Cargo.toml b/lib/Cargo.toml index 550d6f9a..798416fe 100644 --- a/lib/Cargo.toml +++ b/lib/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rocket" -version = "0.1.2" +version = "0.1.3" authors = ["Sergio Benitez "] description = """ Web framework for nightly with a focus on ease-of-use, expressability, and @@ -26,7 +26,7 @@ default-features = false [dev-dependencies] lazy_static = "0.2" -rocket_codegen = { version = "0.1.2", path = "../codegen" } +rocket_codegen = { version = "0.1.3", path = "../codegen" } [features] testing = [] From 16c42289a966b8a5e380b20922aa8c465e2ee9a0 Mon Sep 17 00:00:00 2001 From: Sergio Benitez Date: Sat, 31 Dec 2016 02:02:58 -0600 Subject: [PATCH 13/14] Add crates.io badge. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 91501084..cf2e5f8d 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Rocket [![Build Status](https://travis-ci.org/SergioBenitez/Rocket.svg?branch=master)](https://travis-ci.org/SergioBenitez/Rocket) [![Rocket Homepage](https://img.shields.io/badge/web-rocket.rs-red.svg?style=flat&label=https&colorB=d33847)](https://rocket.rs) +# Rocket [![Build Status](https://travis-ci.org/SergioBenitez/Rocket.svg?branch=master)](https://travis-ci.org/SergioBenitez/Rocket) [![Rocket Homepage](https://img.shields.io/badge/web-rocket.rs-red.svg?style=flat&label=https&colorB=d33847)](https://rocket.rs) [![Current Crates.io Version](https://img.shields.io/crates/v/rocket.svg)](https://crates.io/crates/rocket) Rocket is web framework for Rust (nightly) with a focus on ease-of-use, expressability, and speed. Here's an example of a complete Rocket application: From 2852c526c35c0513e61694a8d2f887d8046ca989 Mon Sep 17 00:00:00 2001 From: Sergio Benitez Date: Sat, 31 Dec 2016 02:08:43 -0600 Subject: [PATCH 14/14] Fix minor typos in CHANGELOG. --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ecba3489..cee28968 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,11 +16,11 @@ * Codegen will refuse to build with incompatible rustc, presenting error message and suggestion. * Added `head` as a valid decorator for `HEAD` requests. - * Added `route(OPTIONS)` is a valid decorator for `OPTIONS` requests. + * Added `route(OPTIONS)` as a valid decorator for `OPTIONS` requests. ## Contrib - * Files with the `.tera` extension are properly autoescaped. + * Templates with the `.tera` extension are properly autoescaped. * Nested template names are properly resolved on Windows. * Template implements `Display`. * Tera dependency updated to version 0.6.