mirror of https://github.com/rwf2/Rocket.git
Merge branch 'master' into hello_alt_methods_tests
This commit is contained in:
commit
8b4d0670c6
43
CHANGELOG.md
43
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)` 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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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."));
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@ extern crate rocket;
|
|||
#[get("")]
|
||||
fn get() -> &'static str { "hi" }
|
||||
|
||||
fn main() {
|
||||
let _ = routes![get];
|
||||
}
|
||||
#[get("/")]
|
||||
fn get_empty() { }
|
||||
|
||||
fn main() { }
|
||||
|
|
|
@ -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() { }
|
|
@ -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 }
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 = []
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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)])
|
||||
};
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
}
|
|
@ -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));
|
||||
|
Loading…
Reference in New Issue