mirror of https://github.com/rwf2/Rocket.git
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:
parent
633d37bc97
commit
ec4cc3a293
|
@ -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)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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(¶m.node.name);
|
let arg = self.annotated_fn.find_input(¶m.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)
|
||||||
|
|
|
@ -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]
|
||||||
|
|
|
@ -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 { "" }
|
||||||
|
|
||||||
|
|
|
@ -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>,
|
||||||
|
|
|
@ -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())));
|
||||||
}
|
}
|
||||||
|
|
|
@ -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`
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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};
|
||||||
|
|
|
@ -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"]
|
||||||
|
|
||||||
|
|
|
@ -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>>;
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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() { }
|
||||||
/// ```
|
/// ```
|
||||||
|
|
|
@ -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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
|
@ -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)]
|
||||||
|
|
|
@ -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"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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, ()> {
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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));
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
Loading…
Reference in New Issue