Allow transforms in 'FromData'. Add 'FromDataSimple'.

The new 'FromData' trait allows an implementor to instruct the caller to
maintain state on its stack and later pass a borrow for processing.
Among other things, it greatly simplifies the 'Form' type, removing a
use of unsafe, and allows references in deserialized data guards.
This commit is contained in:
Sergio Benitez 2018-09-26 01:13:57 -07:00
parent 633d37bc97
commit ec4cc3a293
24 changed files with 675 additions and 610 deletions

View File

@ -1,14 +1,14 @@
use std::ops::{Deref, DerefMut}; use std::ops::{Deref, DerefMut};
use std::io::{self, Read}; use std::io::{self, Read};
use rocket::outcome::{Outcome, IntoOutcome};
use rocket::request::Request; use rocket::request::Request;
use rocket::data::{self, Data, FromData}; use rocket::outcome::Outcome::*;
use rocket::data::{Outcome, Transform, Transform::*, Transformed, Data, FromData};
use rocket::response::{self, Responder, content}; use rocket::response::{self, Responder, content};
use rocket::http::Status; use rocket::http::Status;
use serde::{Serialize, Serializer}; use serde::{Serialize, Serializer};
use serde::de::{Deserialize, DeserializeOwned, Deserializer}; use serde::de::{Deserialize, Deserializer};
use serde_json; use serde_json;
/// The JSON type: implements `FromData` and `Responder`, allowing you to easily /// The JSON type: implements `FromData` and `Responder`, allowing you to easily
@ -19,20 +19,20 @@ use serde_json;
/// If you're receiving JSON data, simply add a `data` parameter to your route /// If you're receiving JSON data, simply add a `data` parameter to your route
/// arguments and ensure the type of the parameter is a `Json<T>`, where `T` is /// arguments and ensure the type of the parameter is a `Json<T>`, where `T` is
/// some type you'd like to parse from JSON. `T` must implement `Deserialize` or /// some type you'd like to parse from JSON. `T` must implement `Deserialize` or
/// `DeserializeOwned` from [Serde](https://github.com/serde-rs/json). The data /// `DeserializeOwned` from [`serde`](https://github.com/serde-rs/json). The
/// is parsed from the HTTP request body. /// data is parsed from the HTTP request body.
/// ///
/// ```rust,ignore /// ```rust,ignore
/// #[post("/users/", format = "application/json", data = "<user>")] /// #[post("/users/", format = "json", data = "<user>")]
/// fn new_user(user: Json<User>) { /// fn new_user(user: Json<User>) {
/// ... /// ...
/// } /// }
/// ``` /// ```
/// ///
/// You don't _need_ to use `format = "application/json"`, but it _may_ be what /// You don't _need_ to use `format = "json"`, but it _may_ be what you want.
/// you want. Using `format = application/json` means that any request that /// Using `format = json` means that any request that doesn't specify
/// doesn't specify "application/json" as its `Content-Type` header value will /// "application/json" as its `Content-Type` header value will not be routed to
/// not be routed to the handler. /// the handler.
/// ///
/// ## Sending JSON /// ## Sending JSON
/// ///
@ -52,7 +52,7 @@ use serde_json;
/// ## Incoming Data Limits /// ## Incoming Data Limits
/// ///
/// The default size limit for incoming JSON data is 1MiB. Setting a limit /// The default size limit for incoming JSON data is 1MiB. Setting a limit
/// protects your application from denial of service (DOS) attacks and from /// protects your application from denial of service (DoS) attacks and from
/// resource exhaustion through high memory consumption. The limit can be /// resource exhaustion through high memory consumption. The limit can be
/// increased by setting the `limits.json` configuration parameter. For /// increased by setting the `limits.json` configuration parameter. For
/// instance, to increase the JSON limit to 5MiB for all environments, you may /// instance, to increase the JSON limit to 5MiB for all environments, you may
@ -81,49 +81,50 @@ impl<T> Json<T> {
} }
} }
/// Like [`from_reader`] but eagerly reads the content of the reader to a string
/// and delegates to `from_str`.
///
/// [`from_reader`]: https://docs.serde.rs/serde_json/fn.from_reader.html
fn from_reader_eager<R, T>(mut reader: R) -> Result<T, JsonError>
where R: Read, T: DeserializeOwned
{
let mut s = String::with_capacity(512);
reader.read_to_string(&mut s).map_err(JsonError::Io)?;
serde_json::from_str(&s).map_err(|e| JsonError::Parse(s, e))
}
/// Default limit for JSON is 1MB. /// Default limit for JSON is 1MB.
const LIMIT: u64 = 1 << 20; const LIMIT: u64 = 1 << 20;
/// An error returned by the [`Json`] data guard when incoming data fails to /// An error returned by the [`Json`] data guard when incoming data fails to
/// serialize as JSON. /// serialize as JSON.
#[derive(Debug)] #[derive(Debug)]
pub enum JsonError { pub enum JsonError<'a> {
/// An I/O error occurred while reading the incoming request data. /// An I/O error occurred while reading the incoming request data.
Io(io::Error), Io(io::Error),
/// The client's data was received successfully but failed to parse as valid /// The client's data was received successfully but failed to parse as valid
/// JSON or as the requested type. The `String` value in `.0` is the raw /// JSON or as the requested type. The `&str` value in `.0` is the raw data
/// data received from the user, while the `Error` in `.1` is the /// received from the user, while the `Error` in `.1` is the deserialization
/// deserialization error from `serde`. /// error from `serde`.
Parse(String, serde_json::error::Error), Parse(&'a str, serde_json::error::Error),
} }
impl<T: DeserializeOwned> FromData for Json<T> { impl<'a, T: Deserialize<'a>> FromData<'a> for Json<T> {
type Error = JsonError; type Error = JsonError<'a>;
type Owned = String;
type Borrowed = str;
fn from_data(request: &Request, data: Data) -> data::Outcome<Self, Self::Error> { fn transform(r: &Request, d: Data) -> Transform<Outcome<Self::Owned, Self::Error>> {
if !request.content_type().map_or(false, |ct| ct.is_json()) { let size_limit = r.limits().get("json").unwrap_or(LIMIT);
error_!("Content-Type is not JSON."); let mut s = String::with_capacity(512);
return Outcome::Forward(data); match d.open().take(size_limit).read_to_string(&mut s) {
Ok(_) => Borrowed(Success(s)),
Err(e) => Borrowed(Failure((Status::BadRequest, JsonError::Io(e))))
}
} }
let size_limit = request.limits().get("json").unwrap_or(LIMIT); fn from_data(_: &Request, o: Transformed<'a, Self>) -> Outcome<Self, Self::Error> {
from_reader_eager(data.open().take(size_limit)) let string = o.borrowed()?;
.map(Json) match serde_json::from_str(&string) {
.map_err(|e| { error_!("Couldn't parse JSON body: {:?}", e); e }) Ok(v) => Success(Json(v)),
.into_outcome(Status::BadRequest) Err(e) => {
error_!("Couldn't parse JSON body: {:?}", e);
if e.is_data() {
Failure((Status::UnprocessableEntity, JsonError::Parse(string, e)))
} else {
Failure((Status::BadRequest, JsonError::Parse(string, e)))
}
}
}
} }
} }

View File

@ -3,14 +3,14 @@ extern crate rmp_serde;
use std::ops::{Deref, DerefMut}; use std::ops::{Deref, DerefMut};
use std::io::{Cursor, Read}; use std::io::{Cursor, Read};
use rocket::outcome::{Outcome, IntoOutcome};
use rocket::request::Request; use rocket::request::Request;
use rocket::data::{self, Data, FromData}; use rocket::outcome::Outcome::*;
use rocket::data::{Outcome, Transform, Transform::*, Transformed, Data, FromData};
use rocket::response::{self, Responder, Response}; use rocket::response::{self, Responder, Response};
use rocket::http::{ContentType, Status}; use rocket::http::Status;
use serde::Serialize; use serde::Serialize;
use serde::de::DeserializeOwned; use serde::de::Deserialize;
pub use self::rmp_serde::decode::Error as MsgPackError; pub use self::rmp_serde::decode::Error as MsgPackError;
@ -23,8 +23,8 @@ pub use self::rmp_serde::decode::Error as MsgPackError;
/// route arguments and ensure the type of the parameter is a `MsgPack<T>`, /// route arguments and ensure the type of the parameter is a `MsgPack<T>`,
/// where `T` is some type you'd like to parse from MessagePack. `T` must /// where `T` is some type you'd like to parse from MessagePack. `T` must
/// implement `Deserialize` or `DeserializeOwned` from /// implement `Deserialize` or `DeserializeOwned` from
/// [Serde](https://github.com/serde-rs/serde). The data is parsed from the HTTP /// [`serde`](https://github.com/serde-rs/serde). The data is parsed from the
/// request body. /// HTTP request body.
/// ///
/// ```rust /// ```rust
/// # #![feature(plugin, decl_macro)] /// # #![feature(plugin, decl_macro)]
@ -45,9 +45,7 @@ pub use self::rmp_serde::decode::Error as MsgPackError;
/// You don't _need_ to use `format = "msgpack"`, but it _may_ be what you want. /// You don't _need_ to use `format = "msgpack"`, but it _may_ be what you want.
/// Using `format = msgpack` means that any request that doesn't specify /// Using `format = msgpack` means that any request that doesn't specify
/// "application/msgpack" as its first `Content-Type:` header parameter will not /// "application/msgpack" as its first `Content-Type:` header parameter will not
/// be routed to this handler. By default, Rocket will accept a Content-Type of /// be routed to this handler.
/// any of the following for MessagePack data: `application/msgpack`,
/// `application/x-msgpack`, `bin/msgpack`, or `bin/x-msgpack`.
/// ///
/// ## Sending MessagePack /// ## Sending MessagePack
/// ///
@ -110,34 +108,36 @@ impl<T> MsgPack<T> {
/// Default limit for MessagePack is 1MB. /// Default limit for MessagePack is 1MB.
const LIMIT: u64 = 1 << 20; const LIMIT: u64 = 1 << 20;
/// Accepted content types are: `application/msgpack`, `application/x-msgpack`, impl<'a, T: Deserialize<'a>> FromData<'a> for MsgPack<T> {
/// `bin/msgpack`, and `bin/x-msgpack`.
#[inline(always)]
fn is_msgpack_content_type(ct: &ContentType) -> bool {
(ct.top() == "application" || ct.top() == "bin")
&& (ct.sub() == "msgpack" || ct.sub() == "x-msgpack")
}
impl<T: DeserializeOwned> FromData for MsgPack<T> {
type Error = MsgPackError; type Error = MsgPackError;
type Owned = Vec<u8>;
type Borrowed = [u8];
fn from_data(request: &Request, data: Data) -> data::Outcome<Self, Self::Error> { fn transform(r: &Request, d: Data) -> Transform<Outcome<Self::Owned, Self::Error>> {
if !request.content_type().map_or(false, |ct| is_msgpack_content_type(&ct)) { let mut buf = Vec::new();
error_!("Content-Type is not MessagePack."); let size_limit = r.limits().get("msgpack").unwrap_or(LIMIT);
return Outcome::Forward(data); match d.open().take(size_limit).read_to_end(&mut buf) {
Ok(_) => Borrowed(Success(buf)),
Err(e) => Borrowed(Failure((Status::BadRequest, MsgPackError::InvalidDataRead(e))))
}
} }
let mut buf = Vec::new(); fn from_data(_: &Request, o: Transformed<'a, Self>) -> Outcome<Self, Self::Error> {
let size_limit = request.limits().get("msgpack").unwrap_or(LIMIT); use self::MsgPackError::*;
if let Err(e) = data.open().take(size_limit).read_to_end(&mut buf) {
let e = MsgPackError::InvalidDataRead(e);
error_!("Couldn't read request data: {:?}", e);
return Outcome::Failure((Status::BadRequest, e));
};
rmp_serde::from_slice(&buf).map(MsgPack) let buf = o.borrowed()?;
.map_err(|e| { error_!("Couldn't parse MessagePack body: {:?}", e); e }) match rmp_serde::from_slice(&buf) {
.into_outcome(Status::BadRequest) Ok(val) => Success(MsgPack(val)),
Err(e) => {
error_!("Couldn't parse MessagePack body: {:?}", e);
match e {
TypeMismatch(_) | OutOfRange | LengthMismatch(_) => {
Failure((Status::UnprocessableEntity, e))
}
_ => Failure((Status::BadRequest, e))
}
}
}
} }
} }

View File

@ -86,7 +86,7 @@ impl RouteParams {
).expect("form statement")) ).expect("form statement"))
} }
fn generate_data_statement(&self, ecx: &ExtCtxt) -> Option<Stmt> { fn generate_data_statements(&self, ecx: &ExtCtxt) -> Option<(Stmt, Stmt)> {
let param = self.data_param.as_ref().map(|p| &p.value)?; let param = self.data_param.as_ref().map(|p| &p.value)?;
let arg = self.annotated_fn.find_input(&param.node.name); let arg = self.annotated_fn.find_input(&param.node.name);
if arg.is_none() { if arg.is_none() {
@ -97,18 +97,43 @@ impl RouteParams {
let arg = arg.unwrap(); let arg = arg.unwrap();
let name = arg.ident().expect("form param identifier").prepend(PARAM_PREFIX); let name = arg.ident().expect("form param identifier").prepend(PARAM_PREFIX);
let ty = strip_ty_lifetimes(arg.ty.clone()); let ty = strip_ty_lifetimes(arg.ty.clone());
Some(quote_stmt!(ecx,
let transform_stmt = quote_stmt!(ecx,
let __transform = <$ty as ::rocket::data::FromData>::transform(__req, __data);
).expect("data statement");
let data_stmt = quote_stmt!(ecx,
#[allow(non_snake_case, unreachable_patterns)] #[allow(non_snake_case, unreachable_patterns)]
let $name: $ty = let $name: $ty = {
match ::rocket::data::FromData::from_data(__req, __data) { let __outcome = match __transform {
::rocket::data::Transform::Owned(::rocket::Outcome::Success(__v)) => {
::rocket::data::Transform::Owned(::rocket::Outcome::Success(__v))
}
::rocket::data::Transform::Borrowed(::rocket::Outcome::Success(ref v)) => {
let borrow = ::std::borrow::Borrow::borrow(v);
::rocket::data::Transform::Borrowed(::rocket::Outcome::Success(borrow))
}
::rocket::data::Transform::Owned(inner) => {
::rocket::data::Transform::Owned(inner)
}
::rocket::data::Transform::Borrowed(inner) => {
::rocket::data::Transform::Borrowed(inner.map(|_| unreachable!()))
}
};
match <$ty as ::rocket::data::FromData>::from_data(__req, __outcome) {
::rocket::Outcome::Success(d) => d, ::rocket::Outcome::Success(d) => d,
::rocket::Outcome::Forward(d) => ::rocket::Outcome::Forward(d) => {
return ::rocket::Outcome::Forward(d), return ::rocket::Outcome::Forward(d);
}
::rocket::Outcome::Failure((code, _)) => { ::rocket::Outcome::Failure((code, _)) => {
return ::rocket::Outcome::Failure(code); return ::rocket::Outcome::Failure(code);
} }
}
}; };
).expect("data statement")) ).expect("data statement");
Some((transform_stmt, data_stmt))
} }
fn generate_query_statement(&self, ecx: &ExtCtxt) -> Option<Stmt> { fn generate_query_statement(&self, ecx: &ExtCtxt) -> Option<Stmt> {
@ -285,9 +310,11 @@ fn generic_route_decorator(known_method: Option<Spanned<Method>>,
let param_statements = route.generate_param_statements(ecx); let param_statements = route.generate_param_statements(ecx);
let query_statement = route.generate_query_statement(ecx); let query_statement = route.generate_query_statement(ecx);
let data_statement = route.generate_data_statement(ecx);
let fn_arguments = route.generate_fn_arguments(ecx); let fn_arguments = route.generate_fn_arguments(ecx);
let uri_macro = route.generate_uri_macro(ecx); let uri_macro = route.generate_uri_macro(ecx);
let (transform_statement, data_statement) = route.generate_data_statements(ecx)
.map(|(a, b)| (Some(a), Some(b)))
.unwrap_or((None, None));
// Generate and emit the wrapping function with the Rocket handler signature. // Generate and emit the wrapping function with the Rocket handler signature.
let user_fn_name = route.annotated_fn.ident(); let user_fn_name = route.annotated_fn.ident();
@ -300,6 +327,7 @@ fn generic_route_decorator(known_method: Option<Spanned<Method>>,
-> ::rocket::handler::Outcome<'_b> { -> ::rocket::handler::Outcome<'_b> {
$param_statements $param_statements
$query_statement $query_statement
$transform_statement
$data_statement $data_statement
let responder = $user_fn_name($fn_arguments); let responder = $user_fn_name($fn_arguments);
::rocket::handler::Outcome::from(__req, responder) ::rocket::handler::Outcome::from(__req, responder)

View File

@ -13,13 +13,13 @@ struct User<'a> {
} }
#[post("/<_name>?<_query>", format = "application/json", data = "<user>", rank = 2)] #[post("/<_name>?<_query>", format = "application/json", data = "<user>", rank = 2)]
fn get<'r>( fn get(
_name: &RawStr, _name: &RawStr,
_query: User<'r>, _query: User,
user: Form<'r, User<'r>>, user: Form<User>,
_cookies: Cookies _cookies: Cookies
) -> String { ) -> String {
format!("{}:{}", user.get().name, user.get().nickname) format!("{}:{}", user.name, user.nickname)
} }
#[test] #[test]

View File

@ -71,7 +71,7 @@ fn no_uri_display_okay(id: i32, form: Form<Second>) -> &'static str {
fn complex<'r>( fn complex<'r>(
name: &RawStr, name: &RawStr,
query: User<'r>, query: User<'r>,
user: Form<'r, User<'r>>, user: Form<User<'r>>,
cookies: Cookies cookies: Cookies
) -> &'static str { "" } ) -> &'static str { "" }

View File

@ -58,7 +58,7 @@ fn validate_struct(gen: &DeriveGenerator, data: Struct) -> Result<()> {
} }
pub fn derive_from_form(input: TokenStream) -> TokenStream { pub fn derive_from_form(input: TokenStream) -> TokenStream {
let form_error = quote!(::rocket::request::FormError); let form_error = quote!(::rocket::request::FormParseError);
DeriveGenerator::build_for(input, "::rocket::request::FromForm<'__f>") DeriveGenerator::build_for(input, "::rocket::request::FromForm<'__f>")
.generic_support(GenericSupport::Lifetime | GenericSupport::Type) .generic_support(GenericSupport::Lifetime | GenericSupport::Type)
.replace_generic(0, 0) .replace_generic(0, 0)
@ -72,7 +72,7 @@ pub fn derive_from_form(input: TokenStream) -> TokenStream {
}) })
.validate_struct(validate_struct) .validate_struct(validate_struct)
.function(|_, inner| quote! { .function(|_, inner| quote! {
type Error = ::rocket::request::FormError<'__f>; type Error = ::rocket::request::FormParseError<'__f>;
fn from_form( fn from_form(
__items: &mut ::rocket::request::FormItems<'__f>, __items: &mut ::rocket::request::FormItems<'__f>,

View File

@ -1,10 +1,10 @@
#[macro_use] extern crate rocket; #[macro_use] extern crate rocket;
use rocket::request::{FromForm, FormItems, FormError}; use rocket::request::{FromForm, FormItems, FormParseError};
use rocket::http::RawStr; use rocket::http::RawStr;
fn parse<'f, T>(string: &'f str, strict: bool) -> Result<T, FormError<'f>> fn parse<'f, T>(string: &'f str, strict: bool) -> Result<T, FormParseError<'f>>
where T: FromForm<'f, Error = FormError<'f>> where T: FromForm<'f, Error = FormParseError<'f>>
{ {
let mut items = FormItems::from(string); let mut items = FormItems::from(string);
let result = T::from_form(items.by_ref(), strict); let result = T::from_form(items.by_ref(), strict);
@ -15,14 +15,14 @@ fn parse<'f, T>(string: &'f str, strict: bool) -> Result<T, FormError<'f>>
result result
} }
fn strict<'f, T>(string: &'f str) -> Result<T, FormError<'f>> fn strict<'f, T>(string: &'f str) -> Result<T, FormParseError<'f>>
where T: FromForm<'f, Error = FormError<'f>> where T: FromForm<'f, Error = FormParseError<'f>>
{ {
parse(string, true) parse(string, true)
} }
fn lenient<'f, T>(string: &'f str) -> Result<T, FormError<'f>> fn lenient<'f, T>(string: &'f str) -> Result<T, FormParseError<'f>>
where T: FromForm<'f, Error = FormError<'f>> where T: FromForm<'f, Error = FormParseError<'f>>
{ {
parse(string, false) parse(string, false)
} }
@ -297,23 +297,23 @@ fn form_errors() {
assert_eq!(form, Ok(WhoopsForm { complete: true, other: 781 })); assert_eq!(form, Ok(WhoopsForm { complete: true, other: 781 }));
let form: Result<WhoopsForm, _> = strict("complete=true&other=unknown"); let form: Result<WhoopsForm, _> = strict("complete=true&other=unknown");
assert_eq!(form, Err(FormError::BadValue("other".into(), "unknown".into()))); assert_eq!(form, Err(FormParseError::BadValue("other".into(), "unknown".into())));
let form: Result<WhoopsForm, _> = strict("complete=unknown&other=unknown"); let form: Result<WhoopsForm, _> = strict("complete=unknown&other=unknown");
assert_eq!(form, Err(FormError::BadValue("complete".into(), "unknown".into()))); assert_eq!(form, Err(FormParseError::BadValue("complete".into(), "unknown".into())));
let form: Result<WhoopsForm, _> = strict("complete=true&other=1&extra=foo"); let form: Result<WhoopsForm, _> = strict("complete=true&other=1&extra=foo");
assert_eq!(form, Err(FormError::Unknown("extra".into(), "foo".into()))); assert_eq!(form, Err(FormParseError::Unknown("extra".into(), "foo".into())));
// Bad values take highest precedence. // Bad values take highest precedence.
let form: Result<WhoopsForm, _> = strict("complete=unknown&unknown=foo"); let form: Result<WhoopsForm, _> = strict("complete=unknown&unknown=foo");
assert_eq!(form, Err(FormError::BadValue("complete".into(), "unknown".into()))); assert_eq!(form, Err(FormParseError::BadValue("complete".into(), "unknown".into())));
// Then unknown key/values for strict parses. // Then unknown key/values for strict parses.
let form: Result<WhoopsForm, _> = strict("complete=true&unknown=foo"); let form: Result<WhoopsForm, _> = strict("complete=true&unknown=foo");
assert_eq!(form, Err(FormError::Unknown("unknown".into(), "foo".into()))); assert_eq!(form, Err(FormParseError::Unknown("unknown".into(), "foo".into())));
// Finally, missing. // Finally, missing.
let form: Result<WhoopsForm, _> = strict("complete=true"); let form: Result<WhoopsForm, _> = strict("complete=true");
assert_eq!(form, Err(FormError::Missing("other".into()))); assert_eq!(form, Err(FormParseError::Missing("other".into())));
} }

View File

@ -27,12 +27,17 @@ const PEEK_BYTES: usize = 512;
/// ///
/// This type is the only means by which the body of a request can be retrieved. /// This type is the only means by which the body of a request can be retrieved.
/// This type is not usually used directly. Instead, types that implement /// This type is not usually used directly. Instead, types that implement
/// [FromData](/rocket/data/trait.FromData.html) are used via code generation by /// [`FromData`] are used via code generation by specifying the `data = "<var>"`
/// specifying the `data = "<param>"` route parameter as follows: /// route parameter as follows:
/// ///
/// ```rust,ignore /// ```rust
/// # #![feature(plugin, decl_macro)]
/// # #![plugin(rocket_codegen)]
/// # #[macro_use] extern crate rocket;
/// # type DataGuard = ::rocket::data::Data;
/// #[post("/submit", data = "<var>")] /// #[post("/submit", data = "<var>")]
/// fn submit(var: T) -> ... { ... } /// fn submit(var: DataGuard) { /* ... */ }
/// # fn main() { }
/// ``` /// ```
/// ///
/// Above, `T` can be any type that implements `FromData`. Note that `Data` /// Above, `T` can be any type that implements `FromData`. Note that `Data`

View File

@ -1,4 +1,5 @@
use std::io::{self, Read}; use std::io::{self, Read};
use std::borrow::Borrow;
use outcome::{self, IntoOutcome}; use outcome::{self, IntoOutcome};
use outcome::Outcome::*; use outcome::Outcome::*;
@ -30,6 +31,84 @@ impl<'a, S, E> IntoOutcome<S, (Status, E), Data> for Result<S, E> {
} }
} }
/// Indicates how incoming data should be transformed before being parsed and
/// validated by a data guard.
///
/// See the documentation for [`FromData`] for usage details.
pub enum Transform<T, B = T> {
/// Indicates that data should be or has been transformed into the
/// [`FromData::Owned`] variant.
Owned(T),
/// Indicates that data should be or has been transformed into the
/// [`FromData::Borrowed`] variant.
Borrowed(B)
}
impl<T, B> Transform<T, B> {
/// Returns the `Owned` value if `self` is `Owned`.
///
/// # Panics
///
/// Panics if `self` is `Borrowed`.
///
///
/// # Example
///
/// ```rust
/// use rocket::data::Transform;
///
/// let owned: Transform<usize, &[usize]> = Transform::Owned(10);
/// assert_eq!(owned.owned(), 10);
/// ```
#[inline]
pub fn owned(self) -> T {
match self {
Transform::Owned(val) => val,
Transform::Borrowed(_) => panic!("Transform::owned() called on Borrowed"),
}
}
/// Returns the `Borrowed` value if `self` is `Borrowed`.
///
/// # Panics
///
/// Panics if `self` is `Owned`.
///
/// ```rust
/// use rocket::data::Transform;
///
/// let borrowed: Transform<usize, &[usize]> = Transform::Borrowed(&[10]);
/// assert_eq!(borrowed.borrowed(), &[10]);
/// ```
#[inline]
pub fn borrowed(self) -> B {
match self {
Transform::Borrowed(val) => val,
Transform::Owned(_) => panic!("Transform::borrowed() called on Owned"),
}
}
}
/// Type alias to the `outcome` input type of [`FromData::from_data`].
///
/// This is a hairy type, but the gist is that this is a [`Transform`] where,
/// for a given `T: FromData`:
///
/// * The `Owned` variant is an `Outcome` whose `Success` value is of type
/// [`FromData::Owned`].
///
/// * The `Borrowed` variant is an `Outcome` whose `Success` value is a borrow
/// of type [`FromData::Borrowed`].
///
/// * In either case, the `Outcome`'s `Failure` variant is a value of type
/// [`FromData::Error`].
pub type Transformed<'a, T> =
Transform<
Outcome<<T as FromData<'a>>::Owned, <T as FromData<'a>>::Error>,
Outcome<&'a <T as FromData<'a>>::Borrowed, <T as FromData<'a>>::Error>
>;
/// Trait implemented by data guards to derive a value from request body data. /// Trait implemented by data guards to derive a value from request body data.
/// ///
/// # Data Guards /// # Data Guards
@ -39,21 +118,122 @@ impl<'a, S, E> IntoOutcome<S, (Status, E), Data> for Result<S, E> {
/// Validation and parsing/conversion is implemented through `FromData`. In /// Validation and parsing/conversion is implemented through `FromData`. In
/// other words, every type that implements `FromData` is a data guard. /// other words, every type that implements `FromData` is a data guard.
/// ///
/// [request guard]: /rocket/request/trait.FromRequest.html
///
/// Data guards are used as the target of the `data` route attribute parameter. /// Data guards are used as the target of the `data` route attribute parameter.
/// A handler can have at most one data guard. /// A handler can have at most one data guard.
/// ///
/// [request guard]: /rocket/request/trait.FromRequest.html
///
/// ## Example /// ## Example
/// ///
/// In the example below, `var` is used as the argument name for the data guard /// In the example below, `var` is used as the argument name for the data guard
/// type `T`. When the `submit` route matches, Rocket will call the `FromData` /// type `DataGuard`. When the `submit` route matches, Rocket will call the
/// implementation for the type `T`. The handler will only be called if the guard /// `FromData` implementation for the type `T`. The handler will only be called
/// returns successfully. /// if the guard returns successfully.
/// ///
/// ```rust,ignore /// ```rust
/// # #![feature(plugin, decl_macro)]
/// # #![plugin(rocket_codegen)]
/// # #[macro_use] extern crate rocket;
/// # type DataGuard = ::rocket::data::Data;
/// #[post("/submit", data = "<var>")] /// #[post("/submit", data = "<var>")]
/// fn submit(var: T) -> ... { ... } /// fn submit(var: DataGuard) { /* ... */ }
/// # fn main() { }
/// ```
///
/// # Transforming
///
/// Data guards can optionally _transform_ incoming data before processing it
/// via an implementation of the [`FromData::transform()`] method. This is
/// useful when a data guard requires or could benefit from a reference to body
/// data as opposed to an owned version. If a data guard has no need to operate
/// on a reference to body data, [`FromDataSimple`] should be implemented
/// instead; it is simpler to implement and less error prone. All types that
/// implement `FromDataSimple` automatically implement `FromData`.
///
/// When exercising a data guard, Rocket first calls the guard's
/// [`FromData::transform()`] method and then subsequently calls the guard's
/// [`FromData::from_data()`] method. Rocket stores data returned by
/// [`FromData::transform()`] on the stack. If `transform` returns a
/// [`Transform::Owned`], Rocket moves the data back to the data guard in the
/// subsequent `from_data` call as a `Transform::Owned`. If instead `transform`
/// returns a [`Transform::Borrowed`] variant, Rocket calls `borrow()` on the
/// owned value, producing a borrow of the associated [`FromData::Borrowed`]
/// type and passing it as a `Transform::Borrowed`.
///
/// ## Example
///
/// Consider a data guard type that wishes to hold a slice to two different
/// parts of the incoming data:
///
/// ```rust
/// struct Name<'a> {
/// first: &'a str,
/// last: &'a str
/// }
/// ```
///
/// Without the ability to transform into a borrow, implementing such a data
/// guard would be impossible. With transformation, however, we can instruct
/// Rocket to produce a borrow to a `Data` that has been transformed into a
/// `String` (an `&str`).
///
/// ```rust
/// # #![feature(plugin, decl_macro)]
/// # #![plugin(rocket_codegen)]
/// # extern crate rocket;
/// # #[derive(Debug)]
/// # struct Name<'a> { first: &'a str, last: &'a str, }
/// use std::io::{self, Read};
///
/// use rocket::{Request, Data, Outcome::*};
/// use rocket::data::{FromData, Outcome, Transform, Transformed};
/// use rocket::http::Status;
///
/// const NAME_LIMIT: u64 = 256;
///
/// enum NameError {
/// Io(io::Error),
/// Parse
/// }
///
/// impl<'a> FromData<'a> for Name<'a> {
/// type Error = NameError;
/// type Owned = String;
/// type Borrowed = str;
///
/// fn transform(_: &Request, data: Data) -> Transform<Outcome<Self::Owned, Self::Error>> {
/// let mut stream = data.open().take(NAME_LIMIT);
/// let mut string = String::with_capacity((NAME_LIMIT / 2) as usize);
/// let outcome = match stream.read_to_string(&mut string) {
/// Ok(_) => Success(string),
/// Err(e) => Failure((Status::InternalServerError, NameError::Io(e)))
/// };
///
/// // Returning `Borrowed` here means we get `Borrowed` in `from_data`.
/// Transform::Borrowed(outcome)
/// }
///
/// fn from_data(_: &Request, outcome: Transformed<'a, Self>) -> Outcome<Self, Self::Error> {
/// // Retrieve a borrow to the now transformed `String` (an &str). This
/// // is only correct because we know we _always_ return a `Borrowed` from
/// // `transform` above.
/// let string = outcome.borrowed()?;
///
/// // Perform a crude, inefficient parse.
/// let splits: Vec<&str> = string.split(" ").collect();
/// if splits.len() != 2 || splits.iter().any(|s| s.is_empty()) {
/// return Failure((Status::UnprocessableEntity, NameError::Parse));
/// }
///
/// // Return successfully.
/// Success(Name { first: splits[0], last: splits[1] })
/// }
/// }
/// # #[post("/person", data = "<person>")]
/// # fn person(person: Name) { }
/// # #[post("/person", data = "<person>")]
/// # fn person2(person: Result<Name, NameError>) { }
/// # fn main() { }
/// ``` /// ```
/// ///
/// # Outcomes /// # Outcomes
@ -127,6 +307,100 @@ impl<'a, S, E> IntoOutcome<S, (Status, E), Data> for Result<S, E> {
/// memory; since the user controls the size of the body, this is an obvious /// memory; since the user controls the size of the body, this is an obvious
/// vector for a denial of service attack. /// vector for a denial of service attack.
/// ///
/// # Simple `FromData`
///
/// For an example of a type that wouldn't require transformation, see the
/// [`FromDataSimple`] documentation.
pub trait FromData<'a>: Sized {
/// The associated error to be returned when the guard fails.
type Error;
/// The owned type returned from [`FromData::transform()`].
///
/// The trait bounds ensures that it is is possible to borrow an
/// `&Self::Borrowed` from a value of this type.
type Owned: Borrow<Self::Borrowed>;
/// The _borrowed_ type consumed by [`FromData::from_data()`] when
/// [`FromData::transform()`] returns a [`Transform::Borrowed`].
///
/// If [`FromData::from_data()`] returns a [`Transform::Owned`], this
/// associated type should be set to `Self::Owned`.
type Borrowed: ?Sized;
/// Transforms `data` into a value of type `Self::Owned`.
///
/// If this method returns a `Transform::Owned(Self::Owned)`, then
/// `from_data` should subsequently be called with a `data` value of
/// `Transform::Owned(Self::Owned)`. If this method returns a
/// `Transform::Borrowed(Self::Owned)`, `from_data` should subsequently be
/// called with a `data` value of `Transform::Borrowed(&Self::Borrowed)`. In
/// other words, the variant of `Transform` returned from this method is
/// used to determine which variant of `Transform` should be passed to the
/// `from_data` method. Rocket _always_ makes the subsequent call correctly.
///
/// It is very unlikely that a correct implementation of this method is
/// capable of returning either of an `Owned` or `Borrowed` variant.
/// Instead, this method should return exactly _one_ of these variants.
///
/// If transformation succeeds, an outcome of `Success` is returned.
/// If the data is not appropriate given the type of `Self`, `Forward` is
/// returned. On failure, `Failure` is returned.
fn transform(request: &Request, data: Data) -> Transform<Outcome<Self::Owned, Self::Error>>;
/// Validates, parses, and converts the incoming request body data into an
/// instance of `Self`.
///
/// If validation and parsing succeeds, an outcome of `Success` is returned.
/// If the data is not appropriate given the type of `Self`, `Forward` is
/// returned. If parsing or validation fails, `Failure` is returned.
///
/// # Example
///
/// When implementing this method, you rarely need to destruct the `outcome`
/// parameter. Instead, the first line of the method should be one of the
/// following:
///
/// ```rust
/// # use rocket::data::{Data, FromData, Transformed, Outcome};
/// # fn f<'a>(outcome: Transformed<'a, Data>) -> Outcome<Data, <Data as FromData<'a>>::Error> {
/// // If `Owned` was returned from `transform`:
/// let data = outcome.owned()?;
/// # unimplemented!()
/// # }
///
/// # fn g<'a>(outcome: Transformed<'a, Data>) -> Outcome<Data, <Data as FromData<'a>>::Error> {
/// // If `Borrowed` was returned from `transform`:
/// let data = outcome.borrowed()?;
/// # unimplemented!()
/// # }
/// ```
fn from_data(request: &Request, outcome: Transformed<'a, Self>) -> Outcome<Self, Self::Error>;
}
/// The identity implementation of `FromData`. Always returns `Success`.
impl<'f> FromData<'f> for Data {
type Error = !;
type Owned = Data;
type Borrowed = Data;
#[inline(always)]
fn transform(_: &Request, data: Data) -> Transform<Outcome<Self::Owned, Self::Error>> {
Transform::Owned(Success(data))
}
#[inline(always)]
fn from_data(_: &Request, outcome: Transformed<'f, Self>) -> Outcome<Self, Self::Error> {
Success(outcome.owned()?)
}
}
/// A simple, less complex variant of [`FromData`].
///
/// When transformation of incoming data isn't required, data guards should
/// implement this trait instead of [`FromData`]. For a description of data
/// guards, see the [`FromData`] documentation.
///
/// # Example /// # Example
/// ///
/// Say that you have a custom type, `Person`: /// Say that you have a custom type, `Person`:
@ -144,16 +418,22 @@ impl<'a, S, E> IntoOutcome<S, (Status, E), Data> for Result<S, E> {
/// application/x-person`. You'd like to use `Person` as a `FromData` type so /// application/x-person`. You'd like to use `Person` as a `FromData` type so
/// that you can retrieve it directly from a client's request body: /// that you can retrieve it directly from a client's request body:
/// ///
/// ```rust,ignore /// ```rust
/// # #![feature(plugin, decl_macro)]
/// # #![plugin(rocket_codegen)]
/// # #[macro_use] extern crate rocket;
/// # type Person = ::rocket::data::Data;
/// #[post("/person", data = "<person>")] /// #[post("/person", data = "<person>")]
/// fn person(person: Person) -> &'static str { /// fn person(person: Person) -> &'static str {
/// "Saved the new person to the database!" /// "Saved the new person to the database!"
/// } /// }
/// ``` /// ```
/// ///
/// A `FromData` implementation allowing this looks like: /// A `FromDataSimple` implementation allowing this looks like:
/// ///
/// ```rust /// ```rust
/// # #![allow(unused_attributes)]
/// # #![allow(unused_variables)]
/// # #![feature(plugin, decl_macro)] /// # #![feature(plugin, decl_macro)]
/// # #![plugin(rocket_codegen)] /// # #![plugin(rocket_codegen)]
/// # extern crate rocket; /// # extern crate rocket;
@ -162,12 +442,15 @@ impl<'a, S, E> IntoOutcome<S, (Status, E), Data> for Result<S, E> {
/// # struct Person { name: String, age: u16 } /// # struct Person { name: String, age: u16 }
/// # /// #
/// use std::io::Read; /// use std::io::Read;
/// use rocket::{Request, Data, Outcome};
/// use rocket::data::{self, FromData};
/// use rocket::http::{Status, ContentType};
/// use rocket::Outcome::*;
/// ///
/// impl FromData for Person { /// use rocket::{Request, Data, Outcome, Outcome::*};
/// use rocket::data::{self, FromDataSimple};
/// use rocket::http::{Status, ContentType};
///
/// // Always use a limit to prevent DoS attacks.
/// const LIMIT: u64 = 256;
///
/// impl FromDataSimple for Person {
/// type Error = String; /// type Error = String;
/// ///
/// fn from_data(req: &Request, data: Data) -> data::Outcome<Self, String> { /// fn from_data(req: &Request, data: Data) -> data::Outcome<Self, String> {
@ -179,13 +462,13 @@ impl<'a, S, E> IntoOutcome<S, (Status, E), Data> for Result<S, E> {
/// ///
/// // Read the data into a String. /// // Read the data into a String.
/// let mut string = String::new(); /// let mut string = String::new();
/// if let Err(e) = data.open().read_to_string(&mut string) { /// if let Err(e) = data.open().take(LIMIT).read_to_string(&mut string) {
/// return Failure((Status::InternalServerError, format!("{:?}", e))); /// return Failure((Status::InternalServerError, format!("{:?}", e)));
/// } /// }
/// ///
/// // Split the string into two pieces at ':'. /// // Split the string into two pieces at ':'.
/// let (name, age) = match string.find(':') { /// let (name, age) = match string.find(':') {
/// Some(i) => (&string[..i], &string[(i + 1)..]), /// Some(i) => (string[..i].to_string(), &string[(i + 1)..]),
/// None => return Failure((Status::UnprocessableEntity, "':'".into())) /// None => return Failure((Status::UnprocessableEntity, "':'".into()))
/// }; /// };
/// ///
@ -196,20 +479,16 @@ impl<'a, S, E> IntoOutcome<S, (Status, E), Data> for Result<S, E> {
/// }; /// };
/// ///
/// // Return successfully. /// // Return successfully.
/// Success(Person { /// Success(Person { name, age })
/// name: name.into(),
/// age: age
/// })
/// } /// }
/// } /// }
/// #
/// # #[post("/person", data = "<person>")] /// # #[post("/person", data = "<person>")]
/// # fn person(person: Person) { } /// # fn person(person: Person) { }
/// # #[post("/person", data = "<person>")] /// # #[post("/person", data = "<person>")]
/// # fn person2(person: Result<Person, String>) { } /// # fn person2(person: Result<Person, String>) { }
/// # fn main() { } /// # fn main() { }
/// ``` /// ```
pub trait FromData: Sized { pub trait FromDataSimple: Sized {
/// The associated error to be returned when the guard fails. /// The associated error to be returned when the guard fails.
type Error; type Error;
@ -222,41 +501,66 @@ pub trait FromData: Sized {
fn from_data(request: &Request, data: Data) -> Outcome<Self, Self::Error>; fn from_data(request: &Request, data: Data) -> Outcome<Self, Self::Error>;
} }
/// The identity implementation of `FromData`. Always returns `Success`. impl<'a, T: FromDataSimple> FromData<'a> for T {
impl FromData for Data { type Error = T::Error;
type Error = !; type Owned = Data;
type Borrowed = Data;
fn from_data(_: &Request, data: Data) -> Outcome<Self, Self::Error> { #[inline(always)]
Success(data) fn transform(_: &Request, d: Data) -> Transform<Outcome<Self::Owned, Self::Error>> {
Transform::Owned(Success(d))
}
#[inline(always)]
fn from_data(req: &Request, o: Transformed<'a, Self>) -> Outcome<Self, Self::Error> {
T::from_data(req, o.owned()?)
} }
} }
impl<T: FromData> FromData for Result<T, T::Error> { impl<'a, T: FromData<'a> + 'a> FromData<'a> for Result<T, T::Error> {
type Error = !; type Error = T::Error;
type Owned = T::Owned;
type Borrowed = T::Borrowed;
fn from_data(request: &Request, data: Data) -> Outcome<Self, Self::Error> { #[inline(always)]
match T::from_data(request, data) { fn transform(r: &Request, d: Data) -> Transform<Outcome<Self::Owned, Self::Error>> {
T::transform(r, d)
}
#[inline(always)]
fn from_data(r: &Request, o: Transformed<'a, Self>) -> Outcome<Self, Self::Error> {
match T::from_data(r, o) {
Success(val) => Success(Ok(val)), Success(val) => Success(Ok(val)),
Failure((_, val)) => Success(Err(val)),
Forward(data) => Forward(data), Forward(data) => Forward(data),
Failure((_, e)) => Success(Err(e)),
} }
} }
} }
impl<T: FromData> FromData for Option<T> { impl<'a, T: FromData<'a> + 'a> FromData<'a> for Option<T> {
type Error = !; type Error = T::Error;
type Owned = T::Owned;
type Borrowed = T::Borrowed;
fn from_data(request: &Request, data: Data) -> Outcome<Self, Self::Error> { #[inline(always)]
match T::from_data(request, data) { fn transform(r: &Request, d: Data) -> Transform<Outcome<Self::Owned, Self::Error>> {
T::transform(r, d)
}
#[inline(always)]
fn from_data(r: &Request, o: Transformed<'a, Self>) -> Outcome<Self, Self::Error> {
match T::from_data(r, o) {
Success(val) => Success(Some(val)), Success(val) => Success(Some(val)),
Failure(_) | Forward(_) => Success(None), Failure(_) | Forward(_) => Success(None),
} }
} }
} }
impl FromData for String { #[cfg(debug_assertions)]
impl FromDataSimple for String {
type Error = io::Error; type Error = io::Error;
#[inline(always)]
fn from_data(_: &Request, data: Data) -> Outcome<Self, Self::Error> { fn from_data(_: &Request, data: Data) -> Outcome<Self, Self::Error> {
let mut string = String::new(); let mut string = String::new();
match data.open().read_to_string(&mut string) { match data.open().read_to_string(&mut string) {
@ -266,9 +570,11 @@ impl FromData for String {
} }
} }
impl FromData for Vec<u8> { #[cfg(debug_assertions)]
impl FromDataSimple for Vec<u8> {
type Error = io::Error; type Error = io::Error;
#[inline(always)]
fn from_data(_: &Request, data: Data) -> Outcome<Self, Self::Error> { fn from_data(_: &Request, data: Data) -> Outcome<Self, Self::Error> {
let mut bytes = Vec::new(); let mut bytes = Vec::new();
match data.open().read_to_end(&mut bytes) { match data.open().read_to_end(&mut bytes) {

View File

@ -7,4 +7,4 @@ mod from_data;
pub use self::data::Data; pub use self::data::Data;
pub use self::data_stream::DataStream; pub use self::data_stream::DataStream;
pub use self::from_data::{FromData, Outcome}; pub use self::from_data::{FromData, FromDataSimple, Outcome, Transform, Transformed};

View File

@ -6,6 +6,7 @@
#![feature(proc_macro_non_items)] #![feature(proc_macro_non_items)]
#![feature(crate_visibility_modifier)] #![feature(crate_visibility_modifier)]
#![feature(try_from)] #![feature(try_from)]
#![feature(label_break_value)]
#![recursion_limit="256"] #![recursion_limit="256"]

View File

@ -1,3 +1,4 @@
use std::io;
use http::RawStr; use http::RawStr;
/// Error returned by the [`FromForm`] derive on form parsing errors. /// Error returned by the [`FromForm`] derive on form parsing errors.
@ -8,7 +9,7 @@ use http::RawStr;
/// * `BadValue` or `Unknown` in incoming form string field order /// * `BadValue` or `Unknown` in incoming form string field order
/// * `Missing` in lexical field order /// * `Missing` in lexical field order
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub enum FormError<'f> { pub enum FormParseError<'f> {
/// The field named `.0` with value `.1` failed to parse or validate. /// The field named `.0` with value `.1` failed to parse or validate.
BadValue(&'f RawStr, &'f RawStr), BadValue(&'f RawStr, &'f RawStr),
/// The parse was strict and the field named `.0` with value `.1` appeared /// The parse was strict and the field named `.0` with value `.1` appeared
@ -19,3 +20,14 @@ pub enum FormError<'f> {
/// The field named `.0` was expected but is missing in the incoming form. /// The field named `.0` was expected but is missing in the incoming form.
Missing(&'f RawStr), Missing(&'f RawStr),
} }
/// Error returned by the [`FromData`] implementations of [`Form`] and
/// [`LenientForm`].
#[derive(Debug)]
pub enum FormDataError<'f, E> {
Io(io::Error),
Malformed(&'f str),
Parse(E, &'f str)
}
pub type FormError<'f> = FormDataError<'f, FormParseError<'f>>;

View File

@ -1,13 +1,13 @@
use std::marker::PhantomData; use std::ops::Deref;
use std::fmt::{self, Debug};
use request::Request; use outcome::Outcome::*;
use data::{self, Data, FromData}; use request::{Request, form::{FromForm, FormItems, FormDataError}};
use request::form::{FromForm, FormItems}; use data::{Outcome, Transform, Transformed, Data, FromData};
use http::Status;
/// A `FromData` type for parsing `FromForm` types strictly. /// A data guard for parsing [`FromForm`] types strictly.
/// ///
/// This type implements the `FromData` trait. It provides a generic means to /// This type implements the [`FromData]` trait. It provides a generic means to
/// parse arbitrary structures from incoming form data. /// parse arbitrary structures from incoming form data.
/// ///
/// # Strictness /// # Strictness
@ -17,15 +17,13 @@ use request::form::{FromForm, FormItems};
/// error on missing and/or extra fields. For instance, if an incoming form /// error on missing and/or extra fields. For instance, if an incoming form
/// contains the fields "a", "b", and "c" while `T` only contains "a" and "c", /// contains the fields "a", "b", and "c" while `T` only contains "a" and "c",
/// the form _will not_ parse as `Form<T>`. If you would like to admit extra /// the form _will not_ parse as `Form<T>`. If you would like to admit extra
/// fields without error, see /// fields without error, see [`LenientForm`].
/// [`LenientForm`](/rocket/request/struct.LenientForm.html).
/// ///
/// # Usage /// # Usage
/// ///
/// This type can be used with any type that implements the `FromForm` trait. /// This type can be used with any type that implements the `FromForm` trait.
/// The trait can be automatically derived; see the /// The trait can be automatically derived; see the [`FromForm`] documentation
/// [FromForm](trait.FromForm.html) documentation for more information on /// for more information on deriving or implementing the trait.
/// deriving or implementing the trait.
/// ///
/// Because `Form` implements `FromData`, it can be used directly as a target of /// Because `Form` implements `FromData`, it can be used directly as a target of
/// the `data = "<param>"` route parameter. For instance, if some structure of /// the `data = "<param>"` route parameter. For instance, if some structure of
@ -38,57 +36,32 @@ use request::form::{FromForm, FormItems};
/// fn submit(form: Form<T>) ... { ... } /// fn submit(form: Form<T>) ... { ... }
/// ``` /// ```
/// ///
/// To preserve memory safety, if the underlying structure type contains /// A type of `Form<T>` automatically dereferences into an `&T`, though you can
/// references into form data, the type can only be borrowed via the /// also tranform a `Form<T>` into a `T` by calling
/// [get](#method.get) or [get_mut](#method.get_mut) methods. Otherwise, the /// [`into_inner()`](Form::into_inner()). Thanks automatic dereferencing, you
/// parsed structure can be retrieved with the [into_inner](#method.into_inner) /// can access fields of `T` transparently through a `Form<T>`:
/// method.
///
/// ## With References
///
/// The simplest data structure with a reference into form data looks like this:
///
/// ```rust
/// # #![feature(plugin, decl_macro)]
/// # #![allow(deprecated, dead_code, unused_attributes)]
/// # #![plugin(rocket_codegen)]
/// # #[macro_use] extern crate rocket;
/// # use rocket::http::RawStr;
/// #[derive(FromForm)]
/// struct UserInput<'f> {
/// value: &'f RawStr
/// }
/// # fn main() { }
/// ```
///
/// This corresponds to a form with a single field named `value` that should be
/// a string. A handler for this type can be written as:
/// ///
/// ```rust /// ```rust
/// # #![feature(plugin, decl_macro)] /// # #![feature(plugin, decl_macro)]
/// # #![allow(deprecated, unused_attributes)] /// # #![allow(deprecated, unused_attributes)]
/// # #![plugin(rocket_codegen)] /// # #![plugin(rocket_codegen)]
/// # #[macro_use] extern crate rocket; /// # #[macro_use] extern crate rocket;
/// # use rocket::request::Form; /// use rocket::request::Form;
/// # use rocket::http::RawStr; /// use rocket::http::RawStr;
/// # #[derive(FromForm)] ///
/// # struct UserInput<'f> { /// #[derive(FromForm)]
/// # value: &'f RawStr /// struct UserInput<'f> {
/// # } /// 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(user_input: Form<UserInput>) -> String {
/// format!("Your value: {}", user_input.get().value) /// format!("Your value: {}", user_input.value)
/// } /// }
/// # fn main() { } /// # fn main() { }
/// ``` /// ```
/// ///
/// Note that the `` `r`` lifetime is used _twice_ in the handler's signature: /// For posterity, the owned analog of the `UserInput` type above is:
/// this is necessary to tie the lifetime of the structure to the lifetime of
/// the request data.
///
/// ## Without References
///
/// The owned analog of the `UserInput` type above is:
/// ///
/// ```rust /// ```rust
/// struct OwnedUserInput { /// struct OwnedUserInput {
@ -96,7 +69,7 @@ use request::form::{FromForm, FormItems};
/// } /// }
/// ``` /// ```
/// ///
/// The handler is written similarly: /// A handler that handles a form of this type can similarly by written:
/// ///
/// ```rust /// ```rust
/// # #![feature(plugin, decl_macro)] /// # #![feature(plugin, decl_macro)]
@ -110,8 +83,7 @@ use request::form::{FromForm, FormItems};
/// # } /// # }
/// #[post("/submit", data = "<user_input>")] /// #[post("/submit", data = "<user_input>")]
/// fn submit_task(user_input: Form<OwnedUserInput>) -> String { /// fn submit_task(user_input: Form<OwnedUserInput>) -> String {
/// let input: OwnedUserInput = user_input.into_inner(); /// format!("Your value: {}", user_input.value)
/// format!("Your value: {}", input.value)
/// } /// }
/// # fn main() { } /// # fn main() { }
/// ``` /// ```
@ -126,7 +98,7 @@ use request::form::{FromForm, FormItems};
/// depends on your use case. The primary question to answer is: _Can the input /// depends on your use case. The primary question to answer is: _Can the input
/// contain characters that must be URL encoded?_ Note that this includes /// contain characters that must be URL encoded?_ Note that this includes
/// common characters such as spaces. If so, then you must use `String`, whose /// common characters such as spaces. If so, then you must use `String`, whose
/// `FromFormValue` implementation automatically URL decodes strings. Because /// [`FromFormValue`] implementation automatically URL decodes strings. Because
/// the `&RawStr` references will refer directly to the underlying form data, /// the `&RawStr` references will refer directly to the underlying form data,
/// they will be raw and URL encoded. /// they will be raw and URL encoded.
/// ///
@ -146,119 +118,11 @@ use request::form::{FromForm, FormItems};
/// [global.limits] /// [global.limits]
/// forms = 524288 /// forms = 524288
/// ``` /// ```
pub struct Form<'f, T: FromForm<'f> + 'f> { #[derive(Debug)]
object: T, pub struct Form<T>(T);
form_string: String,
_phantom: PhantomData<&'f T>,
}
pub enum FormResult<T, E> { impl<T> Form<T> {
Ok(T), /// Consumes `self` and returns the parsed value.
Err(String, E),
Invalid(String)
}
impl<'f, T: FromForm<'f> + 'f> Form<'f, T> {
/// Immutably borrow the parsed type.
///
/// # Example
///
/// ```rust
/// # #![feature(plugin, decl_macro)]
/// # #![plugin(rocket_codegen)]
/// # #[macro_use] extern crate rocket;
/// use rocket::request::Form;
///
/// #[derive(FromForm)]
/// struct MyForm {
/// field: String,
/// }
///
/// #[post("/submit", data = "<form>")]
/// fn submit(form: Form<MyForm>) -> String {
/// format!("Form field is: {}", form.get().field)
/// }
/// #
/// # fn main() { }
/// ```
#[inline(always)]
pub fn get(&'f self) -> &T {
&self.object
}
/// Returns the raw form string that was used to parse the encapsulated
/// object.
///
/// # Example
///
/// ```rust
/// # #![feature(plugin, decl_macro)]
/// # #![plugin(rocket_codegen)]
/// # #[macro_use] extern crate rocket;
/// use rocket::request::Form;
///
/// #[derive(FromForm)]
/// struct MyForm {
/// field: String,
/// }
///
/// #[post("/submit", data = "<form>")]
/// fn submit(form: Form<MyForm>) -> String {
/// format!("Raw form string is: {}", form.raw_form_string())
/// }
/// #
/// # fn main() { }
#[inline(always)]
pub fn raw_form_string(&'f self) -> &str {
&self.form_string
}
// Alright, so here's what's going on here. We'd like to have form
// objects have pointers directly to the form string. This means that
// the form string has to live at least as long as the form object. So,
// to enforce this, we store the form_string along with the form object.
//
// So far so good. Now, this means that the form_string can never be
// deallocated while the object is alive. That implies that the
// `form_string` value should never be moved away. We can enforce that
// easily by 1) not making `form_string` public, and 2) not exposing any
// `&mut self` methods that could modify `form_string`.
//
// Okay, we do all of these things. Now, we still need to give a
// lifetime to `FromForm`. Which one do we choose? The danger is that
// references inside `object` may be copied out, and we have to ensure
// that they don't outlive this structure. So we would really like
// something like `self` and then to transmute to that. But this doesn't
// exist. So we do the next best: we use the first lifetime supplied by the
// caller via `get()` and constrain everything to that lifetime. This is, in
// reality a little coarser than necessary, but the user can simply move the
// call to right after the creation of a Form object to get the same effect.
crate fn new(string: String, strict: bool) -> FormResult<Self, T::Error> {
let long_lived_string: &'f str = unsafe {
::std::mem::transmute(string.as_str())
};
let mut items = FormItems::from(long_lived_string);
let result = T::from_form(items.by_ref(), strict);
if !items.exhaust() {
return FormResult::Invalid(string);
}
match result {
Ok(obj) => FormResult::Ok(Form {
form_string: string,
object: obj,
_phantom: PhantomData
}),
Err(e) => FormResult::Err(string, e)
}
}
}
impl<'f, T: FromForm<'f> + 'static> Form<'f, T> {
/// Consumes `self` and returns the parsed value. For safety reasons, this
/// method may only be called when the parsed value contains no
/// non-`'static` references.
/// ///
/// # Example /// # Example
/// ///
@ -277,39 +141,89 @@ impl<'f, T: FromForm<'f> + 'static> Form<'f, T> {
/// fn submit(form: Form<MyForm>) -> String { /// fn submit(form: Form<MyForm>) -> String {
/// form.into_inner().field /// form.into_inner().field
/// } /// }
/// #
/// # fn main() { } /// # fn main() { }
#[inline(always)] #[inline(always)]
pub fn into_inner(self) -> T { pub fn into_inner(self) -> T {
self.object self.0
} }
} }
impl<'f, T: FromForm<'f> + Debug + 'f> Debug for Form<'f, T> { impl<T> Deref for Form<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { type Target = T;
write!(f, "{:?} from form string: {:?}", self.object, self.form_string)
fn deref(&self) -> &T {
&self.0
} }
} }
impl<'f, T: FromForm<'f>> FromData for Form<'f, T> where T::Error: Debug { impl<'f, T: FromForm<'f>> Form<T> {
/// The raw form string, if it was able to be retrieved from the request. crate fn from_data(
type Error = Option<String>; form_str: &'f str,
strict: bool
) -> Outcome<T, FormDataError<'f, T::Error>> {
use self::FormDataError::*;
/// Parses a `Form` from incoming form data. let mut items = FormItems::from(form_str);
/// let result = T::from_form(&mut items, strict);
/// If the content type of the request data is not if !items.exhaust() {
/// `application/x-www-form-urlencoded`, `Forward`s the request. If the form error_!("The request's form string was malformed.");
/// data cannot be parsed into a `T`, a `Failure` with status code return Failure((Status::BadRequest, Malformed(form_str)));
/// `UnprocessableEntity` is returned. If the form string is malformed, a }
/// `Failure` with status code `BadRequest` is returned. Finally, if reading
/// the incoming stream fails, returns a `Failure` with status code match result {
/// `InternalServerError`. In all failure cases, the raw form string is Ok(v) => Success(v),
/// returned if it was able to be retrieved from the incoming stream. Err(e) => {
/// error_!("The incoming form failed to parse.");
/// All relevant warnings and errors are written to the console in Rocket Failure((Status::UnprocessableEntity, Parse(e, form_str)))
/// logging format. }
#[inline] }
fn from_data(request: &Request, data: Data) -> data::Outcome<Self, Self::Error> { }
super::from_data(request, data, true) }
/// Parses a `Form` from incoming form data.
///
/// If the content type of the request data is not
/// `application/x-www-form-urlencoded`, `Forward`s the request. If the form
/// data cannot be parsed into a `T`, a `Failure` with status code
/// `UnprocessableEntity` is returned. If the form string is malformed, a
/// `Failure` with status code `BadRequest` is returned. Finally, if reading the
/// incoming stream fails, returns a `Failure` with status code
/// `InternalServerError`. In all failure cases, the raw form string is returned
/// if it was able to be retrieved from the incoming stream.
///
/// All relevant warnings and errors are written to the console in Rocket
/// logging format.
impl<'f, T: FromForm<'f>> FromData<'f> for Form<T> {
type Error = FormDataError<'f, T::Error>;
type Owned = String;
type Borrowed = str;
fn transform(
request: &Request,
data: Data
) -> Transform<Outcome<Self::Owned, Self::Error>> {
use std::{cmp::min, io::Read};
let outcome = 'o: {
if !request.content_type().map_or(false, |ct| ct.is_form()) {
warn_!("Form data does not have form content type.");
break 'o Forward(data);
}
let limit = request.limits().forms;
let mut stream = data.open().take(limit);
let mut form_string = String::with_capacity(min(4096, limit) as usize);
if let Err(e) = stream.read_to_string(&mut form_string) {
break 'o Failure((Status::InternalServerError, FormDataError::Io(e)));
}
break 'o Success(form_string);
};
Transform::Borrowed(outcome)
}
fn from_data(_: &Request, o: Transformed<'f, Self>) -> Outcome<Self, Self::Error> {
<Form<T>>::from_data(o.borrowed()?, true).map(Form)
} }
} }

View File

@ -43,7 +43,7 @@ use request::FormItems;
/// # struct TodoTask { description: String, completed: bool } /// # struct TodoTask { description: String, completed: bool }
/// #[post("/submit", data = "<task>")] /// #[post("/submit", data = "<task>")]
/// fn submit_task(task: Form<TodoTask>) -> String { /// fn submit_task(task: Form<TodoTask>) -> String {
/// format!("New task: {}", task.get().description) /// format!("New task: {}", task.description)
/// } /// }
/// # fn main() { } /// # fn main() { }
/// ``` /// ```

View File

@ -1,16 +1,14 @@
use std::fmt::{self, Debug}; use std::ops::Deref;
use request::Request; use request::{Request, form::{Form, FormDataError, FromForm}};
use request::form::{Form, FromForm}; use data::{Data, Transform, Transformed, FromData, Outcome};
use data::{self, Data, FromData};
/// A `FromData` type for parsing `FromForm` types leniently. /// A data gaurd for parsing [`FromForm`] types leniently.
/// ///
/// This type implements the `FromData` trait, and like /// This type implements the [`FromData`] trait, and like [`Form`], provides a
/// [`Form`](/rocket/request/struct.Form.html), provides a generic means to /// generic means to parse arbitrary structures from incoming form data. Unlike
/// parse arbitrary structures from incoming form data. Unlike `Form`, this type /// `Form`, this type uses a _lenient_ parsing strategy: forms that contains a
/// uses a _lenient_ parsing strategy: forms that contains a superset of the /// superset of the expected fields (i.e, extra fields) will parse successfully.
/// expected fields (i.e, extra fields) will parse successfully.
/// ///
/// # Leniency /// # Leniency
/// ///
@ -22,9 +20,8 @@ use data::{self, Data, FromData};
/// ///
/// # Usage /// # Usage
/// ///
/// The usage of a `LenientForm` type is equivalent to that of /// The usage of a `LenientForm` type is equivalent to that of [`Form`], so we
/// [`Form`](/rocket/request/struct.Form.html), so we defer details to its /// defer details to its documentation.
/// documentation. We provide shallow information here.
/// ///
/// `LenientForm` implements `FromData`, so it can be used directly as a target /// `LenientForm` implements `FromData`, so it can be used directly as a target
/// of the `data = "<param>"` route parameter. For instance, if some structure /// of the `data = "<param>"` route parameter. For instance, if some structure
@ -32,9 +29,23 @@ use data::{self, Data, FromData};
/// automatically parsed into the `T` structure with the following route and /// automatically parsed into the `T` structure with the following route and
/// handler: /// handler:
/// ///
/// ```rust,ignore /// ```rust
/// #[post("/form_submit", data = "<param>")] /// # #![feature(plugin, decl_macro)]
/// fn submit(form: LenientForm<T>) ... { ... } /// # #![allow(deprecated, unused_attributes)]
/// # #![plugin(rocket_codegen)]
/// # #[macro_use] extern crate rocket;
/// use rocket::request::LenientForm;
///
/// #[derive(FromForm)]
/// struct UserInput {
/// value: String
/// }
///
/// #[post("/submit", data = "<user_input>")]
/// fn submit_task(user_input: LenientForm<UserInput>) -> String {
/// format!("Your value: {}", user_input.value)
/// }
/// # fn main() { }
/// ``` /// ```
/// ///
/// ## Incoming Data Limits /// ## Incoming Data Limits
@ -48,68 +59,11 @@ use data::{self, Data, FromData};
/// [global.limits] /// [global.limits]
/// forms = 524288 /// forms = 524288
/// ``` /// ```
pub struct LenientForm<'f, T: FromForm<'f> + 'f>(Form<'f, T>); #[derive(Debug)]
pub struct LenientForm<T>(T);
impl<'f, T: FromForm<'f> + 'f> LenientForm<'f, T> { impl<T> LenientForm<T> {
/// Immutably borrow the parsed type. /// Consumes `self` and returns the parsed value.
///
/// # Example
///
/// ```rust
/// # #![feature(plugin, decl_macro)]
/// # #![plugin(rocket_codegen)]
/// # #[macro_use] extern crate rocket;
/// use rocket::request::LenientForm;
///
/// #[derive(FromForm)]
/// struct MyForm {
/// field: String,
/// }
///
/// #[post("/submit", data = "<form>")]
/// fn submit(form: LenientForm<MyForm>) -> String {
/// format!("Form field is: {}", form.get().field)
/// }
/// #
/// # fn main() { }
/// ```
#[inline(always)]
pub fn get(&'f self) -> &T {
self.0.get()
}
/// Returns the raw form string that was used to parse the encapsulated
/// object.
///
/// # Example
///
/// ```rust
/// # #![feature(plugin, decl_macro)]
/// # #![plugin(rocket_codegen)]
/// # #[macro_use] extern crate rocket;
/// use rocket::request::LenientForm;
///
/// #[derive(FromForm)]
/// struct MyForm {
/// field: String,
/// }
///
/// #[post("/submit", data = "<form>")]
/// fn submit(form: LenientForm<MyForm>) -> String {
/// format!("Raw form string is: {}", form.raw_form_string())
/// }
/// #
/// # fn main() { }
#[inline(always)]
pub fn raw_form_string(&'f self) -> &str {
self.0.raw_form_string()
}
}
impl<'f, T: FromForm<'f> + 'static> LenientForm<'f, T> {
/// Consumes `self` and returns the parsed value. For safety reasons, this
/// method may only be called when the parsed value contains no
/// non-`'static` references.
/// ///
/// # Example /// # Example
/// ///
@ -128,39 +82,31 @@ impl<'f, T: FromForm<'f> + 'static> LenientForm<'f, T> {
/// fn submit(form: LenientForm<MyForm>) -> String { /// fn submit(form: LenientForm<MyForm>) -> String {
/// form.into_inner().field /// form.into_inner().field
/// } /// }
/// #
/// # fn main() { } /// # fn main() { }
#[inline(always)] #[inline(always)]
pub fn into_inner(self) -> T { pub fn into_inner(self) -> T {
self.0.into_inner() self.0
} }
} }
impl<'f, T: FromForm<'f> + Debug + 'f> Debug for LenientForm<'f, T> { impl<T> Deref for LenientForm<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { type Target = T;
self.0.fmt(f)
fn deref(&self) -> &T {
&self.0
} }
} }
impl<'f, T: FromForm<'f>> FromData for LenientForm<'f, T> where T::Error: Debug { impl<'f, T: FromForm<'f>> FromData<'f> for LenientForm<T> {
/// The raw form string, if it was able to be retrieved from the request. type Error = FormDataError<'f, T::Error>;
type Error = Option<String>; type Owned = String;
type Borrowed = str;
/// Parses a `LenientForm` from incoming form data. fn transform(r: &Request, d: Data) -> Transform<Outcome<Self::Owned, Self::Error>> {
/// <Form<T>>::transform(r, d)
/// If the content type of the request data is not }
/// `application/x-www-form-urlencoded`, `Forward`s the request. If the form
/// data cannot be parsed into a `T`, a `Failure` with status code fn from_data(_: &Request, o: Transformed<'f, Self>) -> Outcome<Self, Self::Error> {
/// `UnprocessableEntity` is returned. If the form string is malformed, a <Form<T>>::from_data(o.borrowed()?, false).map(LenientForm)
/// `Failure` with status code `BadRequest` is returned. Finally, if reading
/// the incoming stream fails, returns a `Failure` with status code
/// `InternalServerError`. In all failure cases, the raw form string is
/// returned if it was able to be retrieved from the incoming stream.
///
/// All relevant warnings and errors are written to the console in Rocket
/// logging format.
#[inline]
fn from_data(request: &Request, data: Data) -> data::Outcome<Self, Self::Error> {
super::from_data(request, data, false).map(LenientForm)
} }
} }

View File

@ -1,20 +1,4 @@
//! Types and traits to handle form processing. //! Types and traits for form processing.
//!
//! In general, you will deal with forms in Rocket via the `form` parameter in
//! routes:
//!
//! ```rust,ignore
//! #[post("/", form = <my_form>)]
//! fn form_submit(my_form: MyType) -> ...
//! ```
//!
//! Form parameter types must implement the [FromForm](trait.FromForm.html)
//! trait, which is auto-derivable. Automatically deriving `FromForm` for a
//! structure requires that all of its fields implement
//! [FromFormValue](trait.FormFormValue.html), which parses and validates form
//! fields. See the [codegen](/rocket_codegen/) documentation or the [forms
//! guide](/guide/forms) for more information on forms and on deriving
//! `FromForm`.
mod form_items; mod form_items;
mod from_form; mod from_form;
@ -28,137 +12,4 @@ pub use self::from_form::FromForm;
pub use self::from_form_value::FromFormValue; pub use self::from_form_value::FromFormValue;
pub use self::form::Form; pub use self::form::Form;
pub use self::lenient::LenientForm; pub use self::lenient::LenientForm;
pub use self::error::FormError; pub use self::error::{FormError, FormParseError, FormDataError};
use std::cmp;
use std::io::Read;
use std::fmt::Debug;
use outcome::Outcome::*;
use request::Request;
use data::{self, Data};
use self::form::FormResult;
use http::Status;
fn from_data<'f, T>(request: &Request,
data: Data,
strict: bool
) -> data::Outcome<Form<'f, T>, Option<String>>
where T: FromForm<'f>, T::Error: Debug
{
if !request.content_type().map_or(false, |ct| ct.is_form()) {
warn_!("Form data does not have form content type.");
return Forward(data);
}
let limit = request.limits().forms;
let mut form_string = String::with_capacity(cmp::min(4096, limit) as usize);
let mut stream = data.open().take(limit);
if let Err(e) = stream.read_to_string(&mut form_string) {
error_!("IO Error: {:?}", e);
Failure((Status::InternalServerError, None))
} else {
match Form::new(form_string, strict) {
FormResult::Ok(form) => Success(form),
FormResult::Invalid(form_string) => {
error_!("The request's form string was malformed.");
Failure((Status::BadRequest, Some(form_string)))
}
FormResult::Err(form_string, e) => {
error_!("Failed to parse value from form: {:?}", e);
Failure((Status::UnprocessableEntity, Some(form_string)))
}
}
}
}
#[cfg(test)]
mod test {
use super::{Form, FormResult};
use ::request::{FromForm, FormItems};
impl<T, E> FormResult<T, E> {
fn unwrap(self) -> T {
match self {
FormResult::Ok(val) => val,
_ => panic!("Unwrapping non-Ok FormResult.")
}
}
}
struct Simple<'s> {
value: &'s str
}
struct Other {
value: String
}
impl<'s> FromForm<'s> for Simple<'s> {
type Error = ();
fn from_form(items: &mut FormItems<'s>, _: bool) -> Result<Simple<'s>, ()> {
Ok(Simple { value: items.inner_str() })
}
}
impl<'s> FromForm<'s> for Other {
type Error = ();
fn from_form(items: &mut FormItems<'s>, _: bool) -> Result<Other, ()> {
Ok(Other { value: items.inner_str().to_string() })
}
}
#[test]
fn test_lifetime() {
let form_string = "hello=world".to_string();
let form: Form<Simple> = Form::new(form_string, true).unwrap();
let string: &str = form.get().value;
assert_eq!(string, "hello=world");
}
#[test]
fn test_lifetime_2() {
let form_string = "hello=world".to_string();
let mut _y = "hi";
let _form: Form<Simple> = Form::new(form_string, true).unwrap();
// _y = form.get().value;
// fn should_not_compile<'f>(form: Form<'f, &'f str>) -> &'f str {
// form.get()
// }
// fn should_not_compile_2<'f>(form: Form<'f, Simple<'f>>) -> &'f str {
// form.into_inner().value
// }
// assert_eq!(should_not_compile(form), "hello=world");
}
#[test]
fn test_lifetime_3() {
let form_string = "hello=world".to_string();
let form: Form<Other> = Form::new(form_string, true).unwrap();
// Not bad.
fn should_compile(form: Form<Other>) -> String {
form.into_inner().value
}
assert_eq!(should_compile(form), "hello=world".to_string());
}
#[test]
fn test_lifetime_4() {
let form_string = "hello=world".to_string();
let form: Form<Simple> = Form::new(form_string, true).unwrap();
fn should_compile<'f>(_form: Form<'f, Simple<'f>>) { }
should_compile(form)
// assert_eq!(should_not_compile(form), "hello=world");
}
}

View File

@ -12,7 +12,8 @@ mod tests;
pub use self::request::Request; pub use self::request::Request;
pub use self::from_request::{FromRequest, Outcome}; pub use self::from_request::{FromRequest, Outcome};
pub use self::param::{FromParam, FromSegments}; pub use self::param::{FromParam, FromSegments};
pub use self::form::{Form, FormError, LenientForm, FromForm, FromFormValue, FormItems}; pub use self::form::{Form, LenientForm, FormItems};
pub use self::form::{FromForm, FormError, FromFormValue, FormParseError, FormDataError};
pub use self::state::State; pub use self::state::State;
#[doc(inline)] #[doc(inline)]

View File

@ -12,7 +12,7 @@ struct FormData {
#[patch("/", data = "<form_data>")] #[patch("/", data = "<form_data>")]
fn bug(form_data: Form<FormData>) -> &'static str { fn bug(form_data: Form<FormData>) -> &'static str {
assert_eq!("Form data", &form_data.get().form_data); assert_eq!("Form data", form_data.form_data);
"OK" "OK"
} }

View File

@ -21,9 +21,9 @@ impl<'a, 'r> FromRequest<'a, 'r> for HasContentType {
} }
} }
use rocket::data::{self, FromData}; use rocket::data::{self, FromDataSimple};
impl FromData for HasContentType { impl FromDataSimple for HasContentType {
type Error = (); type Error = ();
fn from_data(request: &Request, data: Data) -> data::Outcome<Self, ()> { fn from_data(request: &Request, data: Data) -> data::Outcome<Self, ()> {

View File

@ -12,13 +12,13 @@ struct MyForm<'r> {
} }
#[post("/strict", data = "<form>")] #[post("/strict", data = "<form>")]
fn strict<'r>(form: Form<'r, MyForm<'r>>) -> String { fn strict<'r>(form: Form<MyForm<'r>>) -> String {
form.get().field.as_str().into() form.field.as_str().into()
} }
#[post("/lenient", data = "<form>")] #[post("/lenient", data = "<form>")]
fn lenient<'r>(form: LenientForm<'r, MyForm<'r>>) -> String { fn lenient<'r>(form: LenientForm<MyForm<'r>>) -> String {
form.get().field.as_str().into() form.field.as_str().into()
} }
mod strict_and_lenient_forms_tests { mod strict_and_lenient_forms_tests {

View File

@ -5,7 +5,7 @@
use std::io; use std::io;
use rocket::request::Form; use rocket::request::{Form, FormError, FormDataError};
use rocket::response::NamedFile; use rocket::response::NamedFile;
use rocket::http::RawStr; use rocket::http::RawStr;
@ -29,11 +29,13 @@ struct FormInput<'r> {
} }
#[post("/", data = "<sink>")] #[post("/", data = "<sink>")]
fn sink<'r>(sink: Result<Form<'r, FormInput<'r>>, Option<String>>) -> String { fn sink(sink: Result<Form<FormInput>, FormError>) -> String {
match sink { match sink {
Ok(form) => format!("{:?}", form.get()), Ok(form) => format!("{:?}", &*form),
Err(Some(f)) => format!("Invalid form input: {}", f), Err(FormDataError::Io(_)) => format!("Form input was invalid UTF-8."),
Err(None) => format!("Form input was invalid UTF8."), Err(FormDataError::Malformed(f)) | Err(FormDataError::Parse(_, f)) => {
format!("Invalid form input: {}", f)
}
} }
} }

View File

@ -177,6 +177,6 @@ fn check_bad_utf8() {
let client = Client::new(rocket()).unwrap(); let client = Client::new(rocket()).unwrap();
unsafe { unsafe {
let bad_str = ::std::str::from_utf8_unchecked(b"a=\xff"); let bad_str = ::std::str::from_utf8_unchecked(b"a=\xff");
assert_form_eq(&client, bad_str, "Form input was invalid UTF8.".into()); assert_form_eq(&client, bad_str, "Form input was invalid UTF-8.".into());
} }
} }

View File

@ -51,10 +51,8 @@ impl<'v> FromFormValue<'v> for AdultAge {
} }
} }
#[post("/login", data = "<user_form>")] #[post("/login", data = "<user>")]
fn login<'a>(user_form: Form<'a, UserLogin<'a>>) -> Result<Redirect, String> { fn login(user: Form<UserLogin>) -> Result<Redirect, String> {
let user = user_form.get();
if let Err(e) = user.age { if let Err(e) = user.age {
return Err(format!("Age is invalid: {}", e)); return Err(format!("Age is invalid: {}", e));
} }

View File

@ -37,7 +37,7 @@ impl<'a, 'r> FromRequest<'a, 'r> for User {
#[post("/login", data = "<login>")] #[post("/login", data = "<login>")]
fn login(mut cookies: Cookies, login: Form<Login>) -> Result<Redirect, Flash<Redirect>> { fn login(mut cookies: Cookies, login: Form<Login>) -> Result<Redirect, Flash<Redirect>> {
if login.get().username == "Sergio" && login.get().password == "password" { if login.username == "Sergio" && login.password == "password" {
cookies.add_private(Cookie::new("user_id", 1.to_string())); cookies.add_private(Cookie::new("user_id", 1.to_string()));
Ok(Redirect::to(uri!(index))) Ok(Redirect::to(uri!(index)))
} else { } else {