Merge branch 'master' into hello_alt_methods_tests

This commit is contained in:
Robert 2017-01-02 16:22:49 -05:00
commit 8b4d0670c6
23 changed files with 330 additions and 90 deletions

View File

@ -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)` as a valid decorator for `OPTIONS` requests.
## Contrib
* 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.
## 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

View File

@ -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:

View File

@ -1,6 +1,6 @@
[package]
name = "rocket_codegen"
version = "0.1.2"
version = "0.1.3"
authors = ["Sergio Benitez <sb@sergio.bz>"]
description = "Code generation for the Rocket web framework."
documentation = "https://api.rocket.rs/rocket_codegen/"
@ -9,13 +9,17 @@ 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
[dependencies]
rocket = { version = "0.1.2", path = "../lib/" }
rocket = { version = "0.1.3", path = "../lib/" }
log = "^0.3"
[dev-dependencies]
compiletest_rs = "^0.2"
[build-dependencies]
ansi_term = "^0.9"

71
codegen/build.rs Normal file
View File

@ -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<u32> {
let ymd: Vec<_> = ymd.split("-").filter_map(|s| s.parse::<u32>().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 compatibility."));
printerr!("{}", Yellow.paint("Build may fail due to incompatible rustc version."));
}

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();
@ -290,5 +281,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);

View File

@ -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,
@ -149,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
);
}

View File

@ -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
}
}

View File

@ -6,6 +6,7 @@ extern crate rocket;
#[get("")]
fn get() -> &'static str { "hi" }
fn main() {
let _ = routes![get];
}
#[get("/")]
fn get_empty() { }
fn main() { }

View File

@ -0,0 +1,28 @@
#![feature(plugin)]
#![plugin(rocket_codegen)]
extern crate rocket;
#[get("/")] fn get() { }
#[route(GET, "/")] fn get_r() { }
#[put("/")] fn put() { }
#[route(PUT, "/")] fn put_r() { }
#[post("/")] fn post() { }
#[route(POST, "/")] fn post_r() { }
#[delete("/")] fn delete() { }
#[route(DELETE, "/")] fn delete_r() { }
#[head("/")] fn head() { }
#[route(HEAD, "/")] fn head_r() { }
#[patch("/")] fn patch() { }
#[route(PATCH, "/")] fn patch_r() { }
// TODO: Allow this once Diesel incompatibility is fixed.
// #[options("/")] fn options() { }
#[route(OPTIONS, "/")] fn options_r() { }
fn main() { }

View File

@ -1,6 +1,6 @@
[package]
name = "rocket_contrib"
version = "0.1.2"
version = "0.1.3"
authors = ["Sergio Benitez <sb@sergio.bz>"]
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.
@ -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 }

View File

@ -12,7 +12,7 @@ use rocket::response::Redirect;
#[derive(FromForm)]
struct UserLogin<'r> {
username: &'r str,
password: &'r str,
password: String,
age: Result<usize, &'r str>,
}
@ -27,7 +27,7 @@ fn login<'a>(user_form: Form<'a, UserLogin<'a>>) -> Result<Redirect, String> {
};
if user.username == "Sergio" {
match user.password {
match user.password.as_str() {
"password" => Ok(Redirect::to("/user/Sergio")),
_ => Err("Wrong password!".to_string())
}

View File

@ -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 {

View File

@ -1,6 +1,6 @@
[package]
name = "rocket"
version = "0.1.2"
version = "0.1.3"
authors = ["Sergio Benitez <sb@sergio.bz>"]
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 = []

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

@ -73,21 +73,10 @@ impl<'v> FromFormValue<'v> for String {
// This actually parses the value according to the standard.
fn from_form_value(v: &'v str) -> Result<Self, Self::Error> {
let result = URI::percent_decode(v.as_bytes());
match result {
let replaced = v.replace("+", " ");
match URI::percent_decode(replaced.as_bytes()) {
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())
}
}
}

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

@ -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)])
};

View File

@ -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
}

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

@ -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,
}
}
@ -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));
}
@ -101,7 +102,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 +115,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 +133,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 +146,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 +155,8 @@ impl<'r> Collider<Request<'r>> 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)
}
}

View File

@ -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 response = req.dispatch_with(&rocket);
assert_eq!(response.status(), Status::NotFound);
}

View File

@ -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 = "<form_data>")]
fn bug(form_data: Form<FormData>) -> 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");
}

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));