Use the `RawStr` type for all form raw strings.

This is a breaking change.

This commit introduces `RawStr` to forms. In particular, after this
commit, the `&str` type no longer implements `FromFormValue`, and so it
cannot be used as a field in forms. Instad, the `&RawStr` can be used.

The `FormItems` iterator now returns an `(&RawStr, &RawStr)` pair.
This commit is contained in:
Sergio Benitez 2017-03-30 23:06:53 -07:00
parent f57d984e2e
commit 0c44e44641
15 changed files with 145 additions and 110 deletions

View File

@ -68,7 +68,11 @@ pub fn from_form_derive(ecx: &mut ExtCtxt, span: Span, meta_item: &MetaItem,
is_unsafe: false, is_unsafe: false,
supports_unions: false, supports_unions: false,
span: span, span: span,
attributes: Vec::new(), // We add this attribute because some `FromFormValue` implementations
// can't fail. This is indicated via the `!` type. Rust checks if a
// match is made with something of that type, and since we always emit
// an `Err` match, we'll get this lint warning.
attributes: vec![quote_attr!(ecx, #[allow(unreachable_code)])],
path: ty::Path { path: ty::Path {
path: vec!["rocket", "request", "FromForm"], path: vec!["rocket", "request", "FromForm"],
lifetime: lifetime_var, lifetime: lifetime_var,
@ -178,7 +182,8 @@ fn from_form_substructure(cx: &mut ExtCtxt, trait_span: Span, substr: &Substruct
let id_str = ident_string.as_str(); let id_str = ident_string.as_str();
arms.push(quote_tokens!(cx, arms.push(quote_tokens!(cx,
$id_str => { $id_str => {
$ident = match ::rocket::request::FromFormValue::from_form_value(v) { let r = ::rocket::http::RawStr::from_str(v);
$ident = match ::rocket::request::FromFormValue::from_form_value(r) {
Ok(v) => Some(v), Ok(v) => Some(v),
Err(e) => { Err(e) => {
println!(" => Error parsing form val '{}': {:?}", println!(" => Error parsing form val '{}': {:?}",
@ -194,9 +199,9 @@ fn from_form_substructure(cx: &mut ExtCtxt, trait_span: Span, substr: &Substruct
// and use the $arms generated above. // and use the $arms generated above.
stmts.push(quote_stmt!(cx, stmts.push(quote_stmt!(cx,
for (k, v) in $arg { for (k, v) in $arg {
match k { match k.as_str() {
$arms $arms
field if field == "_method" => { "_method" => {
/* This is a Rocket-specific field. If the user hasn't asked /* This is a Rocket-specific field. If the user hasn't asked
* for it, just let it go by without error. This should stay * for it, just let it go by without error. This should stay
* in sync with Rocket::preprocess. */ * in sync with Rocket::preprocess. */
@ -214,19 +219,13 @@ fn from_form_substructure(cx: &mut ExtCtxt, trait_span: Span, substr: &Substruct
// that each parameter actually is Some() or has a default value. // that each parameter actually is Some() or has a default value.
let mut failure_conditions = vec![]; let mut failure_conditions = vec![];
// Start with `false` in case there are no fields.
failure_conditions.push(quote_tokens!(cx, false));
for &(ref ident, ref ty) in (&fields_and_types).iter() { for &(ref ident, ref ty) in (&fields_and_types).iter() {
// Pushing an "||" (or) between every condition.
failure_conditions.push(quote_tokens!(cx, ||));
failure_conditions.push(quote_tokens!(cx, failure_conditions.push(quote_tokens!(cx,
if $ident.is_none() && if $ident.is_none() &&
<$ty as ::rocket::request::FromFormValue>::default().is_none() { <$ty as ::rocket::request::FromFormValue>::default().is_none() {
println!(" => '{}' did not parse.", stringify!($ident)); println!(" => '{}' did not parse.", stringify!($ident));
true $return_err_stmt;
} else { false } }
)); ));
} }
@ -245,9 +244,7 @@ fn from_form_substructure(cx: &mut ExtCtxt, trait_span: Span, substr: &Substruct
// the structure. // the structure.
let self_ident = substr.type_ident; let self_ident = substr.type_ident;
let final_block = quote_block!(cx, { let final_block = quote_block!(cx, {
if $failure_conditions { $failure_conditions
$return_err_stmt;
}
Ok($self_ident { $result_fields }) Ok($self_ident { $result_fields })
}); });

View File

@ -81,7 +81,7 @@ impl RouteGenerateExt for RouteParams {
Err(_) => return ::rocket::Outcome::Forward(_data) Err(_) => return ::rocket::Outcome::Forward(_data)
}; };
if !items.exhausted() { if !items.exhaust() {
println!(" => The query string {:?} is malformed.", $form_string); println!(" => The query string {:?} is malformed.", $form_string);
return ::rocket::Outcome::Failure(::rocket::http::Status::BadRequest); return ::rocket::Outcome::Failure(::rocket::http::Status::BadRequest);
} }

View File

@ -3,12 +3,12 @@
extern crate rocket; extern crate rocket;
use rocket::http::Cookies; use rocket::http::{Cookies, RawStr};
use rocket::request::Form; use rocket::request::Form;
#[derive(FromForm)] #[derive(FromForm)]
struct User<'a> { struct User<'a> {
name: &'a str, name: &'a RawStr,
nickname: String, nickname: String,
} }

View File

@ -4,6 +4,7 @@
extern crate rocket; extern crate rocket;
use rocket::request::{FromForm, FromFormValue, FormItems}; use rocket::request::{FromForm, FromFormValue, FormItems};
use rocket::http::RawStr;
#[derive(Debug, PartialEq, FromForm)] #[derive(Debug, PartialEq, FromForm)]
struct TodoTask { struct TodoTask {
@ -20,8 +21,8 @@ enum FormOption {
impl<'v> FromFormValue<'v> for FormOption { impl<'v> FromFormValue<'v> for FormOption {
type Error = &'v str; type Error = &'v str;
fn from_form_value(v: &'v str) -> Result<Self, Self::Error> { fn from_form_value(v: &'v RawStr) -> Result<Self, Self::Error> {
let variant = match v { let variant = match v.as_str() {
"a" => FormOption::A, "a" => FormOption::A,
"b" => FormOption::B, "b" => FormOption::B,
"c" => FormOption::C, "c" => FormOption::C,
@ -37,19 +38,19 @@ struct FormInput<'r> {
checkbox: bool, checkbox: bool,
number: usize, number: usize,
radio: FormOption, radio: FormOption,
password: &'r str, password: &'r RawStr,
textarea: String, textarea: String,
select: FormOption, select: FormOption,
} }
#[derive(Debug, PartialEq, FromForm)] #[derive(Debug, PartialEq, FromForm)]
struct DefaultInput<'r> { struct DefaultInput<'r> {
arg: Option<&'r str>, arg: Option<&'r RawStr>,
} }
#[derive(Debug, PartialEq, FromForm)] #[derive(Debug, PartialEq, FromForm)]
struct ManualMethod<'r> { struct ManualMethod<'r> {
_method: Option<&'r str>, _method: Option<&'r RawStr>,
done: bool done: bool
} }
@ -61,13 +62,13 @@ struct UnpresentCheckbox {
#[derive(Debug, PartialEq, FromForm)] #[derive(Debug, PartialEq, FromForm)]
struct UnpresentCheckboxTwo<'r> { struct UnpresentCheckboxTwo<'r> {
checkbox: bool, checkbox: bool,
something: &'r str something: &'r RawStr
} }
fn parse<'f, T: FromForm<'f>>(string: &'f str) -> Option<T> { fn parse<'f, T: FromForm<'f>>(string: &'f str) -> Option<T> {
let mut items = FormItems::from(string); let mut items = FormItems::from(string);
let result = T::from_form_items(items.by_ref()); let result = T::from_form_items(items.by_ref());
if !items.exhausted() { if !items.exhaust() {
panic!("Invalid form input."); panic!("Invalid form input.");
} }
@ -103,7 +104,7 @@ fn main() {
checkbox: false, checkbox: false,
number: 10, number: 10,
radio: FormOption::C, radio: FormOption::C,
password: "testing", password: "testing".into(),
textarea: "".to_string(), textarea: "".to_string(),
select: FormOption::A, select: FormOption::A,
})); }));
@ -117,7 +118,7 @@ fn main() {
// Ensure _method can be captured if desired. // Ensure _method can be captured if desired.
let manual: Option<ManualMethod> = parse("_method=put&done=true"); let manual: Option<ManualMethod> = parse("_method=put&done=true");
assert_eq!(manual, Some(ManualMethod { assert_eq!(manual, Some(ManualMethod {
_method: Some("put"), _method: Some("put".into()),
done: true done: true
})); }));
@ -138,6 +139,6 @@ fn main() {
let manual: Option<UnpresentCheckboxTwo> = parse("something=hello"); let manual: Option<UnpresentCheckboxTwo> = parse("something=hello");
assert_eq!(manual, Some(UnpresentCheckboxTwo { assert_eq!(manual, Some(UnpresentCheckboxTwo {
checkbox: false, checkbox: false,
something: "hello" something: "hello".into()
})); }));
} }

View File

@ -5,6 +5,7 @@ use std::str::FromStr;
use std::ops::Deref; use std::ops::Deref;
use rocket::request::{FromParam, FromFormValue}; use rocket::request::{FromParam, FromFormValue};
use rocket::http::RawStr;
pub use self::uuid_ext::ParseError as UuidParseError; pub use self::uuid_ext::ParseError as UuidParseError;
@ -89,11 +90,12 @@ impl<'a> FromParam<'a> for UUID {
} }
impl<'v> FromFormValue<'v> for UUID { impl<'v> FromFormValue<'v> for UUID {
type Error = &'v str; type Error = &'v RawStr;
/// A value is successfully parsed if `form_value` is a properly formatted /// A value is successfully parsed if `form_value` is a properly formatted
/// UUID. Otherwise, the raw form value is returned. /// UUID. Otherwise, the raw form value is returned.
fn from_form_value(form_value: &'v str) -> Result<UUID, &'v str> { #[inline(always)]
fn from_form_value(form_value: &'v RawStr) -> Result<UUID, &'v RawStr> {
form_value.parse().map_err(|_| form_value) form_value.parse().map_err(|_| form_value)
} }
} }

View File

@ -4,11 +4,11 @@
extern crate rocket; extern crate rocket;
mod files; mod files;
#[cfg(test)] #[cfg(test)] mod tests;
mod tests;
use rocket::response::Redirect; use rocket::response::Redirect;
use rocket::request::{Form, FromFormValue}; use rocket::request::{Form, FromFormValue};
use rocket::http::RawStr;
#[derive(Debug)] #[derive(Debug)]
struct StrongPassword<'r>(&'r str); struct StrongPassword<'r>(&'r str);
@ -18,7 +18,7 @@ struct AdultAge(isize);
#[derive(FromForm)] #[derive(FromForm)]
struct UserLogin<'r> { struct UserLogin<'r> {
username: &'r str, username: &'r RawStr,
password: Result<StrongPassword<'r>, &'static str>, password: Result<StrongPassword<'r>, &'static str>,
age: Result<AdultAge, &'static str>, age: Result<AdultAge, &'static str>,
} }
@ -26,11 +26,11 @@ struct UserLogin<'r> {
impl<'v> FromFormValue<'v> for StrongPassword<'v> { impl<'v> FromFormValue<'v> for StrongPassword<'v> {
type Error = &'static str; type Error = &'static str;
fn from_form_value(v: &'v str) -> Result<Self, Self::Error> { fn from_form_value(v: &'v RawStr) -> Result<Self, Self::Error> {
if v.len() < 8 { if v.len() < 8 {
Err("Too short!") Err("too short!")
} else { } else {
Ok(StrongPassword(v)) Ok(StrongPassword(v.as_str()))
} }
} }
} }
@ -38,15 +38,15 @@ impl<'v> FromFormValue<'v> for StrongPassword<'v> {
impl<'v> FromFormValue<'v> for AdultAge { impl<'v> FromFormValue<'v> for AdultAge {
type Error = &'static str; type Error = &'static str;
fn from_form_value(v: &'v str) -> Result<Self, Self::Error> { fn from_form_value(v: &'v RawStr) -> Result<Self, Self::Error> {
let age = match isize::from_form_value(v) { let age = match isize::from_form_value(v) {
Ok(v) => v, Ok(v) => v,
Err(_) => return Err("Age value is not a number."), Err(_) => return Err("value is not a number."),
}; };
match age > 20 { match age > 20 {
true => Ok(AdultAge(age)), true => Ok(AdultAge(age)),
false => Err("Must be at least 21."), false => Err("must be at least 21."),
} }
} }
} }

View File

@ -37,14 +37,14 @@ fn test_invalid_user() {
#[test] #[test]
fn test_invalid_password() { fn test_invalid_password() {
test_login("Sergio", "password1", "30", Status::Ok, "Wrong password!"); test_login("Sergio", "password1", "30", Status::Ok, "Wrong password!");
test_login("Sergio", "ok", "30", Status::Ok, "Password is invalid: Too short!"); test_login("Sergio", "ok", "30", Status::Ok, "Password is invalid: too short!");
} }
#[test] #[test]
fn test_invalid_age() { fn test_invalid_age() {
test_login("Sergio", "password", "20", Status::Ok, "Must be at least 21."); test_login("Sergio", "password", "20", Status::Ok, "must be at least 21.");
test_login("Sergio", "password", "-100", Status::Ok, "Must be at least 21."); test_login("Sergio", "password", "-100", Status::Ok, "must be at least 21.");
test_login("Sergio", "password", "hi", Status::Ok, "Age value is not a number"); test_login("Sergio", "password", "hi", Status::Ok, "value is not a number");
} }
fn check_bad_form(form_str: &str, status: Status) { fn check_bad_form(form_str: &str, status: Status) {

View File

@ -3,9 +3,10 @@
extern crate rocket; extern crate rocket;
use std::io;
use rocket::request::{Form, FromFormValue}; use rocket::request::{Form, FromFormValue};
use rocket::response::NamedFile; use rocket::response::NamedFile;
use std::io; use rocket::http::RawStr;
// TODO: Make deriving `FromForm` for this enum possible. // TODO: Make deriving `FromForm` for this enum possible.
#[derive(Debug)] #[derive(Debug)]
@ -14,10 +15,10 @@ enum FormOption {
} }
impl<'v> FromFormValue<'v> for FormOption { impl<'v> FromFormValue<'v> for FormOption {
type Error = &'v str; type Error = &'v RawStr;
fn from_form_value(v: &'v str) -> Result<Self, Self::Error> { fn from_form_value(v: &'v RawStr) -> Result<Self, Self::Error> {
let variant = match v { let variant = match v.as_str() {
"a" => FormOption::A, "a" => FormOption::A,
"b" => FormOption::B, "b" => FormOption::B,
"c" => FormOption::C, "c" => FormOption::C,

View File

@ -8,12 +8,13 @@ mod files;
use rocket::request::Form; use rocket::request::Form;
use rocket::response::Redirect; use rocket::response::Redirect;
use rocket::http::RawStr;
#[derive(FromForm)] #[derive(FromForm)]
struct UserLogin<'r> { struct UserLogin<'r> {
username: &'r str, username: &'r RawStr,
password: String, password: String,
age: Result<usize, &'r str>, age: Result<usize, &'r RawStr>,
} }
#[post("/login", data = "<user_form>")] #[post("/login", data = "<user_form>")]
@ -36,9 +37,8 @@ fn login<'a>(user_form: Form<'a, UserLogin<'a>>) -> Result<Redirect, String> {
} }
} }
#[get("/user/<username>")] #[get("/user/<username>")]
fn user_page(username: &str) -> String { fn user_page(username: String) -> String {
format!("This is {}'s page.", username) format!("This is {}'s page.", username)
} }

View File

@ -6,8 +6,8 @@ extern crate rocket;
#[cfg(test)] mod tests; #[cfg(test)] mod tests;
#[derive(FromForm)] #[derive(FromForm)]
struct Person<'r> { struct Person {
name: &'r str, name: String,
age: Option<u8> age: Option<u8>
} }

View File

@ -6,6 +6,7 @@
#![feature(type_ascription)] #![feature(type_ascription)]
#![feature(lookup_host)] #![feature(lookup_host)]
#![feature(plugin)] #![feature(plugin)]
#![feature(never_type)]
#![plugin(pear_codegen)] #![plugin(pear_codegen)]

View File

@ -1,17 +1,20 @@
use memchr::memchr2; use memchr::memchr2;
use http::RawStr;
/// Iterator over the key/value pairs of a given HTTP form string. /// Iterator over the key/value pairs of a given HTTP form string.
/// ///
/// **Note:** The returned key/value pairs are _not_ URL decoded. To URL decode /// **Note:** The returned key/value pairs are _not_ URL decoded. To URL decode
/// the raw strings, use `String::from_form_value`: /// the raw strings, use the
/// [`url_decode`](/rocket/http/struct.RawStr.html#method.url_decode) method:
/// ///
/// ```rust /// ```rust
/// use rocket::request::{FormItems, FromFormValue}; /// use rocket::request::{FormItems, FromFormValue};
/// ///
/// let form_string = "greeting=Hello%2C+Mark%21&username=jake%2Fother"; /// let form_string = "greeting=Hello%2C+Mark%21&username=jake%2Fother";
/// for (key, value) in FormItems::from(form_string) { /// for (key, value) in FormItems::from(form_string) {
/// let decoded_value = String::from_form_value(value); /// let decoded_value = value.url_decode();
/// match key { /// match key.as_str() {
/// "greeting" => assert_eq!(decoded_value, Ok("Hello, Mark!".into())), /// "greeting" => assert_eq!(decoded_value, Ok("Hello, Mark!".into())),
/// "username" => assert_eq!(decoded_value, Ok("jake/other".into())), /// "username" => assert_eq!(decoded_value, Ok("jake/other".into())),
/// _ => unreachable!() /// _ => unreachable!()
@ -26,7 +29,7 @@ use memchr::memchr2;
/// for completion via the [completed](#method.completed) method, which returns /// for completion via the [completed](#method.completed) method, which returns
/// `true` if the iterator parsed the entire string that was passed to it. The /// `true` if the iterator parsed the entire string that was passed to it. The
/// iterator can also attempt to parse any remaining contents via /// iterator can also attempt to parse any remaining contents via
/// [exhausted](#method.exhausted); this method returns `true` if exhaustion /// [exhaust](#method.exhaust); this method returns `true` if exhaustion
/// succeeded. /// succeeded.
/// ///
/// This iterator guarantees that all valid form strings are parsed to /// This iterator guarantees that all valid form strings are parsed to
@ -57,13 +60,20 @@ use memchr::memchr2;
/// ///
/// let form_string = "greeting=hello&username=jake"; /// let form_string = "greeting=hello&username=jake";
/// let mut items = FormItems::from(form_string); /// let mut items = FormItems::from(form_string);
/// assert_eq!(items.next(), Some(("greeting", "hello"))); ///
/// assert_eq!(items.next(), Some(("username", "jake"))); /// let next = items.next().unwrap();
/// assert_eq!(next.0, "greeting");
/// assert_eq!(next.1, "hello");
///
/// let next = items.next().unwrap();
/// assert_eq!(next.0, "username");
/// assert_eq!(next.1, "jake");
///
/// assert_eq!(items.next(), None); /// assert_eq!(items.next(), None);
/// assert!(items.completed()); /// assert!(items.completed());
/// ``` /// ```
pub struct FormItems<'f> { pub struct FormItems<'f> {
string: &'f str, string: &'f RawStr,
next_index: usize next_index: usize
} }
@ -117,7 +127,7 @@ impl<'f> FormItems<'f> {
/// ///
/// assert!(items.next().is_some()); /// assert!(items.next().is_some());
/// assert_eq!(items.completed(), false); /// assert_eq!(items.completed(), false);
/// assert_eq!(items.exhausted(), true); /// assert_eq!(items.exhaust(), true);
/// assert_eq!(items.completed(), true); /// assert_eq!(items.completed(), true);
/// ``` /// ```
/// ///
@ -130,10 +140,11 @@ impl<'f> FormItems<'f> {
/// ///
/// assert!(items.next().is_some()); /// assert!(items.next().is_some());
/// assert_eq!(items.completed(), false); /// assert_eq!(items.completed(), false);
/// assert_eq!(items.exhausted(), false); /// assert_eq!(items.exhaust(), false);
/// assert_eq!(items.completed(), false); /// assert_eq!(items.completed(), false);
/// ``` /// ```
pub fn exhausted(&mut self) -> bool { #[inline]
pub fn exhaust(&mut self) -> bool {
while let Some(_) = self.next() { } while let Some(_) = self.next() { }
self.completed() self.completed()
} }
@ -167,15 +178,16 @@ impl<'f> FormItems<'f> {
/// assert_eq!(items.inner_str(), form_string); /// assert_eq!(items.inner_str(), form_string);
/// ``` /// ```
#[inline] #[inline]
pub fn inner_str(&self) -> &'f str { pub fn inner_str(&self) -> &'f RawStr {
self.string self.string
} }
} }
impl<'f> From<&'f str> for FormItems<'f> { impl<'f> From<&'f RawStr> for FormItems<'f> {
/// Returns an iterator over the key/value pairs in the /// Returns an iterator over the key/value pairs in the
/// `x-www-form-urlencoded` form `string`. /// `x-www-form-urlencoded` form `string`.
fn from(string: &'f str) -> FormItems<'f> { #[inline(always)]
fn from(string: &'f RawStr) -> FormItems<'f> {
FormItems { FormItems {
string: string, string: string,
next_index: 0 next_index: 0
@ -183,8 +195,20 @@ impl<'f> From<&'f str> for FormItems<'f> {
} }
} }
impl<'f> From<&'f str> for FormItems<'f> {
/// Returns an iterator over the key/value pairs in the
/// `x-www-form-urlencoded` form `string`.
#[inline(always)]
fn from(string: &'f str) -> FormItems<'f> {
FormItems {
string: string.into(),
next_index: 0
}
}
}
impl<'f> Iterator for FormItems<'f> { impl<'f> Iterator for FormItems<'f> {
type Item = (&'f str, &'f str); type Item = (&'f RawStr, &'f RawStr);
fn next(&mut self) -> Option<Self::Item> { fn next(&mut self) -> Option<Self::Item> {
let s = &self.string[self.next_index..]; let s = &self.string[self.next_index..];
@ -204,7 +228,7 @@ impl<'f> Iterator for FormItems<'f> {
}; };
self.next_index += key.len() + 1 + consumed; self.next_index += key.len() + 1 + consumed;
Some((key, value)) Some((key.into(), value.into()))
} }
} }
@ -224,18 +248,19 @@ mod test {
let (expected_key, actual_key) = (expected[i].0, results[i].0); let (expected_key, actual_key) = (expected[i].0, results[i].0);
let (expected_val, actual_val) = (expected[i].1, results[i].1); let (expected_val, actual_val) = (expected[i].1, results[i].1);
assert!(expected_key == actual_key, assert!(actual_key == expected_key,
"key [{}] mismatch: expected {}, got {}", "key [{}] mismatch: expected {}, got {}",
i, expected_key, actual_key); i, expected_key, actual_key);
assert!(expected_val == actual_val, assert!(actual_val == expected_val,
"val [{}] mismatch: expected {}, got {}", "val [{}] mismatch: expected {}, got {}",
i, expected_val, actual_val); i, expected_val, actual_val);
} }
} else { } else {
assert!(!items.exhausted()); assert!(!items.exhaust());
} }
}); });
(@bad $string:expr) => (check_form!(@opt $string, None : Option<&[(&str, &str)]>)); (@bad $string:expr) => (check_form!(@opt $string, None : Option<&[(&str, &str)]>));
($string:expr, $expected:expr) => (check_form!(@opt $string, Some($expected))); ($string:expr, $expected:expr) => (check_form!(@opt $string, Some($expected)));
} }

View File

@ -1,8 +1,7 @@
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddrV4, SocketAddrV6, SocketAddr}; use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddrV4, SocketAddrV6, SocketAddr};
use std::str::FromStr; use std::str::FromStr;
use error::Error; use http::RawStr;
use http::uri::URI;
/// Trait to create instance of some type from a form value; expected from field /// Trait to create instance of some type from a form value; expected from field
/// types in structs deriving `FromForm`. /// types in structs deriving `FromForm`.
@ -38,15 +37,16 @@ use http::uri::URI;
/// following structure: /// following structure:
/// ///
/// ```rust /// ```rust
/// # use rocket::http::RawStr;
/// # #[allow(dead_code)] /// # #[allow(dead_code)]
/// struct Person<'r> { /// struct Person<'r> {
/// name: String, /// name: String,
/// age: Result<u16, &'r str> /// age: Result<u16, &'r RawStr>
/// } /// }
/// ``` /// ```
/// ///
/// The `Err` value in this case is `&str` since `u16::from_form_value` returns /// The `Err` value in this case is `&RawStr` since `u16::from_form_value`
/// a `Result<u16, &str>`. /// returns a `Result<u16, &RawStr>`.
/// ///
/// # Provided Implementations /// # Provided Implementations
/// ///
@ -67,7 +67,7 @@ use http::uri::URI;
/// `"false"`, `"off"`, or not present. In any other case, the raw form /// `"false"`, `"off"`, or not present. In any other case, the raw form
/// value is returned in the `Err` value. /// value is returned in the `Err` value.
/// ///
/// * **str** /// * **&RawStr**
/// ///
/// _This implementation always returns successfully._ /// _This implementation always returns successfully._
/// ///
@ -106,14 +106,15 @@ use http::uri::URI;
/// ///
/// ```rust /// ```rust
/// use rocket::request::FromFormValue; /// use rocket::request::FromFormValue;
/// use rocket::http::RawStr;
/// ///
/// struct AdultAge(usize); /// struct AdultAge(usize);
/// ///
/// impl<'v> FromFormValue<'v> for AdultAge { /// impl<'v> FromFormValue<'v> for AdultAge {
/// type Error = &'v str; /// type Error = &'v RawStr;
/// ///
/// fn from_form_value(form_value: &'v str) -> Result<AdultAge, &'v str> { /// fn from_form_value(form_value: &'v RawStr) -> Result<AdultAge, &'v RawStr> {
/// match usize::from_form_value(form_value) { /// match form_value.parse::<usize>() {
/// Ok(age) if age >= 21 => Ok(AdultAge(age)), /// Ok(age) if age >= 21 => Ok(AdultAge(age)),
/// _ => Err(form_value), /// _ => Err(form_value),
/// } /// }
@ -141,50 +142,50 @@ pub trait FromFormValue<'v>: Sized {
/// Parses an instance of `Self` from an HTTP form field value or returns an /// Parses an instance of `Self` from an HTTP form field value or returns an
/// `Error` if one cannot be parsed. /// `Error` if one cannot be parsed.
fn from_form_value(form_value: &'v str) -> Result<Self, Self::Error>; fn from_form_value(form_value: &'v RawStr) -> Result<Self, Self::Error>;
/// Returns a default value to be used when the form field does not exist. /// Returns a default value to be used when the form field does not exist.
/// If this returns `None`, then the field is required. Otherwise, this /// If this returns `None`, then the field is required. Otherwise, this
/// should return `Some(default_value)`. The default implementation simply /// should return `Some(default_value)`. The default implementation simply
/// returns `None`. /// returns `None`.
#[inline(always)]
fn default() -> Option<Self> { fn default() -> Option<Self> {
None None
} }
} }
impl<'v> FromFormValue<'v> for &'v str { impl<'v> FromFormValue<'v> for &'v RawStr {
type Error = Error; type Error = !;
// This just gives the raw string. // This just gives the raw string.
fn from_form_value(v: &'v str) -> Result<Self, Self::Error> { #[inline(always)]
fn from_form_value(v: &'v RawStr) -> Result<Self, Self::Error> {
Ok(v) Ok(v)
} }
} }
impl<'v> FromFormValue<'v> for String { impl<'v> FromFormValue<'v> for String {
type Error = &'v str; type Error = &'v RawStr;
// This actually parses the value according to the standard. // This actually parses the value according to the standard.
fn from_form_value(v: &'v str) -> Result<Self, Self::Error> { #[inline(always)]
let replaced = v.replace("+", " "); fn from_form_value(v: &'v RawStr) -> Result<Self, Self::Error> {
match URI::percent_decode(replaced.as_bytes()) { v.url_decode().map_err(|_| v)
Err(_) => Err(v),
Ok(string) => Ok(string.into_owned())
}
} }
} }
impl<'v> FromFormValue<'v> for bool { impl<'v> FromFormValue<'v> for bool {
type Error = &'v str; type Error = &'v RawStr;
fn from_form_value(v: &'v str) -> Result<Self, Self::Error> { fn from_form_value(v: &'v RawStr) -> Result<Self, Self::Error> {
match v { match v.as_str() {
"on" | "true" => Ok(true), "on" | "true" => Ok(true),
"off" | "false" => Ok(false), "off" | "false" => Ok(false),
_ => Err(v), _ => Err(v),
} }
} }
#[inline(always)]
fn default() -> Option<bool> { fn default() -> Option<bool> {
Some(false) Some(false)
} }
@ -193,9 +194,11 @@ impl<'v> FromFormValue<'v> for bool {
macro_rules! impl_with_fromstr { macro_rules! impl_with_fromstr {
($($T:ident),+) => ($( ($($T:ident),+) => ($(
impl<'v> FromFormValue<'v> for $T { impl<'v> FromFormValue<'v> for $T {
type Error = &'v str; type Error = &'v RawStr;
fn from_form_value(v: &'v str) -> Result<Self, Self::Error> {
$T::from_str(v).map_err(|_| v) #[inline(always)]
fn from_form_value(v: &'v RawStr) -> Result<Self, Self::Error> {
$T::from_str(v.as_str()).map_err(|_| v)
} }
} }
)+) )+)
@ -205,29 +208,31 @@ impl_with_fromstr!(f32, f64, isize, i8, i16, i32, i64, usize, u8, u16, u32, u64,
IpAddr, Ipv4Addr, Ipv6Addr, SocketAddrV4, SocketAddrV6, SocketAddr); IpAddr, Ipv4Addr, Ipv6Addr, SocketAddrV4, SocketAddrV6, SocketAddr);
impl<'v, T: FromFormValue<'v>> FromFormValue<'v> for Option<T> { impl<'v, T: FromFormValue<'v>> FromFormValue<'v> for Option<T> {
type Error = Error; type Error = !;
fn from_form_value(v: &'v str) -> Result<Self, Self::Error> { #[inline(always)]
fn from_form_value(v: &'v RawStr) -> Result<Self, Self::Error> {
match T::from_form_value(v) { match T::from_form_value(v) {
Ok(v) => Ok(Some(v)), Ok(v) => Ok(Some(v)),
Err(_) => Ok(None), Err(_) => Ok(None),
} }
} }
#[inline(always)]
fn default() -> Option<Option<T>> { fn default() -> Option<Option<T>> {
Some(None) Some(None)
} }
} }
// TODO: Add more useful implementations (range, regex, etc.). // // TODO: Add more useful implementations (range, regex, etc.).
impl<'v, T: FromFormValue<'v>> FromFormValue<'v> for Result<T, T::Error> { impl<'v, T: FromFormValue<'v>> FromFormValue<'v> for Result<T, T::Error> {
type Error = Error; type Error = !;
fn from_form_value(v: &'v str) -> Result<Self, Self::Error> { #[inline(always)]
fn from_form_value(v: &'v RawStr) -> Result<Self, Self::Error> {
match T::from_form_value(v) { match T::from_form_value(v) {
ok@Ok(_) => Ok(ok), ok@Ok(_) => Ok(ok),
e@Err(_) => Ok(e), e@Err(_) => Ok(e),
} }
} }
} }

View File

@ -72,9 +72,10 @@ use outcome::Outcome::*;
/// # #![allow(deprecated, dead_code, unused_attributes)] /// # #![allow(deprecated, dead_code, unused_attributes)]
/// # #![plugin(rocket_codegen)] /// # #![plugin(rocket_codegen)]
/// # extern crate rocket; /// # extern crate rocket;
/// # use rocket::http::RawStr;
/// #[derive(FromForm)] /// #[derive(FromForm)]
/// struct UserInput<'f> { /// struct UserInput<'f> {
/// value: &'f str /// value: &'f RawStr
/// } /// }
/// # fn main() { } /// # fn main() { }
/// ``` /// ```
@ -88,9 +89,10 @@ use outcome::Outcome::*;
/// # #![plugin(rocket_codegen)] /// # #![plugin(rocket_codegen)]
/// # extern crate rocket; /// # extern crate rocket;
/// # use rocket::request::Form; /// # use rocket::request::Form;
/// # use rocket::http::RawStr;
/// # #[derive(FromForm)] /// # #[derive(FromForm)]
/// # struct UserInput<'f> { /// # struct UserInput<'f> {
/// # value: &'f str /// # value: &'f RawStr
/// # } /// # }
/// #[post("/submit", data = "<user_input>")] /// #[post("/submit", data = "<user_input>")]
/// fn submit_task<'r>(user_input: Form<'r, UserInput<'r>>) -> String { /// fn submit_task<'r>(user_input: Form<'r, UserInput<'r>>) -> String {
@ -221,7 +223,7 @@ impl<'f, T: FromForm<'f> + 'f> Form<'f, T> {
let mut items = FormItems::from(long_lived_string); let mut items = FormItems::from(long_lived_string);
let result = T::from_form_items(items.by_ref()); let result = T::from_form_items(items.by_ref());
if !items.exhausted() { if !items.exhaust() {
return FormResult::Invalid(form_string); return FormResult::Invalid(form_string);
} }

View File

@ -168,10 +168,11 @@ impl Rocket {
from_utf8_unchecked(&data.peek()[..min(data_len, max_len)]) from_utf8_unchecked(&data.peek()[..min(data_len, max_len)])
}; };
let mut form_items = FormItems::from(form); if let Some((key, value)) = FormItems::from(form).next() {
if let Some(("_method", value)) = form_items.next() { if key == "_method" {
if let Ok(method) = value.parse() { if let Ok(method) = value.parse() {
req.set_method(method); req.set_method(method);
}
} }
} }
} }