Parse inner form 'T' in 'Option<T>' strictly.

This behavior more closely matches the expectation that a missing field
results in 'None'.

Also cleans up forms docs for readability, completeness.
This commit is contained in:
Sergio Benitez 2021-03-24 19:53:13 -07:00
parent 33790254f1
commit e7934a2a3f
9 changed files with 592 additions and 298 deletions

View File

@ -564,57 +564,51 @@ fn test_nested_multi() {
});
}
// fn test_multipart() {
// use std::{io, path::Path};
//
// use crate::*;
// use crate::http::ContentType;
// use crate::local::blocking::Client;
// use crate::{data::TempFile, form::Errors};
//
// #[derive(FromForm)]
// struct MyForm {
// names: Vec<String>,
// file: String,
// }
//
// #[post("/", data = "<form>")]
// async fn form(mut form: Form<MyForm>) -> io::Result<&'static str> {
// let path = Path::new("/tmp").join(form.file_name().unwrap_or("upload"));
// form.persist(path).await?;
// println!("result: {:?}", form);
// Ok("hi")
// }
//
// let client = Client::debug_with(routes![form]).unwrap();
// let ct = "multipart/form-data; boundary=X-BOUNDARY"
// .parse::<ContentType>()
// .unwrap();
//
// let body = &[
// // "--X-BOUNDARY",
// // r#"Content-Disposition: form-data; name="names[]""#,
// // "",
// // "abcd",
// // "--X-BOUNDARY",
// // r#"Content-Disposition: form-data; name="names[]""#,
// // "",
// // "123",
// "--X-BOUNDARY",
// r#"Content-Disposition: form-data; name="file"; filename="foo.txt""#,
// "Content-Type: text/plain",
// "",
// "hi there",
// "--X-BOUNDARY--",
// "",
// ].join("\r\n");
//
// let response = client.post("/")
// .header(ct)
// .body(body)
// .dispatch();
//
// let string = response.into_string().unwrap();
// println!("String: {}", string);
// panic!(string);
// }
#[test]
fn test_multipart() {
use rocket::http::ContentType;
use rocket::local::blocking::Client;
use rocket::data::TempFile;
#[derive(FromForm)]
struct MyForm<'r> {
names: Vec<&'r str>,
file: TempFile<'r>,
}
#[rocket::post("/", data = "<form>")]
fn form(form: Form<MyForm>) {
assert_eq!(form.names, &["abcd", "123"]);
assert_eq!(form.file.file_name(), Some("foo"));
}
let client = Client::debug_with(rocket::routes![form]).unwrap();
let ct = "multipart/form-data; boundary=X-BOUNDARY"
.parse::<ContentType>()
.unwrap();
let body = &[
"--X-BOUNDARY",
r#"Content-Disposition: form-data; name="names[]""#,
"",
"abcd",
"--X-BOUNDARY",
r#"Content-Disposition: form-data; name="names[]""#,
"",
"123",
"--X-BOUNDARY",
r#"Content-Disposition: form-data; name="file"; filename="foo.txt""#,
"Content-Type: text/plain",
"",
"hi there",
"--X-BOUNDARY--",
"",
].join("\r\n");
let response = client.post("/")
.header(ct)
.body(body)
.dispatch();
assert!(response.status().class().is_success());
}

View File

@ -57,6 +57,19 @@ use crate::form::prelude::*;
///
/// ## Data Limits
///
/// The total amount of data accepted by the `Form` data guard is limited by the
/// following limits:
///
/// | Limit Name | Default | Description |
/// |-------------|---------|------------------------------------|
/// | `form` | 32KiB | total limit for url-encoded forms |
/// | `data-form` | 2MiB | total limit for multipart forms |
/// | `*` | N/A | each field type has its own limits |
///
/// As noted above, each form field type (a form guard) typically imposes its
/// own limits. For example, the `&str` form guard imposes a data limit of
/// `string` when multipart data is streamed.
///
/// ### URL-Encoded Forms
///
/// The `form` limit specifies the data limit for an entire url-encoded form
@ -95,7 +108,7 @@ use crate::form::prelude::*;
///
/// See the [`Limits`](crate::data::Limits) and [`config`](crate::config) docs
/// for more.
#[derive(Debug)]
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct Form<T>(T);
impl<T> Form<T> {
@ -137,28 +150,98 @@ impl<T> From<T> for Form<T> {
}
}
impl Form<()> {
/// `string` must represent a decoded string.
pub fn values(string: &str) -> impl Iterator<Item = ValueField<'_>> {
// WHATWG URL Living Standard 5.1 steps 1, 2, 3.1 - 3.3.
string.split('&')
.filter(|s| !s.is_empty())
.map(ValueField::parse)
}
}
impl<'r, T: FromForm<'r>> Form<T> {
/// `string` must represent a decoded string.
/// Leniently parses a `T` from a **percent-decoded**
/// `x-www-form-urlencoded` form string. Specifically, this method
/// implements [§5.1 of the WHATWG URL Living Standard] with the exception
/// of steps 3.4 and 3.5, which are assumed to already be relfected in
/// `string`, and then parses the fields as `T`.
///
/// [§5.1 of the WHATWG URL Living Standard]: https://url.spec.whatwg.org/#application/x-www-form-urlencoded
///
/// # Example
///
/// ```rust
/// use rocket::form::{Form, FromForm};
///
/// #[derive(FromForm)]
/// struct Pet<'r> {
/// name: &'r str,
/// wags: bool,
/// }
///
/// let string = "name=Benson Wagger!&wags=true";
/// let pet: Pet<'_> = Form::parse(string).unwrap();
/// assert_eq!(pet.name, "Benson Wagger!");
/// assert_eq!(pet.wags, true);
/// ```
#[inline]
pub fn parse(string: &'r str) -> Result<'r, T> {
// WHATWG URL Living Standard 5.1 steps 1, 2, 3.1 - 3.3.
Self::parse_iter(Form::values(string))
}
/// Leniently parses a `T` from the **percent-decoded** `fields`.
/// Specifically, this method implements [§5.1 of the WHATWG URL Living
/// Standard] with the exception of step 3.
///
/// [§5.1 of the WHATWG URL Living Standard]: https://url.spec.whatwg.org/#application/x-www-form-urlencoded
///
/// # Example
///
/// ```rust
/// use rocket::form::{Form, FromForm, ValueField};
///
/// #[derive(FromForm)]
/// struct Pet<'r> {
/// name: &'r str,
/// wags: bool,
/// }
///
/// let fields = vec![
/// ValueField::parse("name=Bob, the cat. :)"),
/// ValueField::parse("wags=no"),
/// ];
///
/// let pet: Pet<'_> = Form::parse_iter(fields).unwrap();
/// assert_eq!(pet.name, "Bob, the cat. :)");
/// assert_eq!(pet.wags, false);
/// ```
pub fn parse_iter<I>(fields: I) -> Result<'r, T>
where I: IntoIterator<Item = ValueField<'r>>
{
// WHATWG URL Living Standard 5.1 steps 1, 2, 3.1 - 3.3.
let mut ctxt = T::init(Options::Lenient);
Form::values(string).for_each(|f| T::push_value(&mut ctxt, f));
fields.into_iter().for_each(|f| T::push_value(&mut ctxt, f));
T::finalize(ctxt)
}
}
impl<T: for<'a> FromForm<'a> + 'static> Form<T> {
/// `string` must represent an undecoded string.
/// Leniently parses a `T` from a raw, `x-www-form-urlencoded` form string.
/// Specifically, this method implements [§5.1 of the WHATWG URL Living
/// Standard]. Because percent-decoding might modify the input string, the
/// output type `T` must be `'static`.
///
/// [§5.1 of the WHATWG URL Living Standard]:https://url.spec.whatwg.org/#application/x-www-form-urlencoded
///
/// # Example
///
/// ```rust
/// use rocket::http::RawStr;
/// use rocket::form::{Form, FromForm};
///
/// #[derive(FromForm)]
/// struct Pet {
/// name: String,
/// wags: bool,
/// }
///
/// let string = RawStr::new("name=Benson+Wagger%21&wags=true");
/// let pet: Pet = Form::parse_encoded(string).unwrap();
/// assert_eq!(pet.name, "Benson Wagger!");
/// assert_eq!(pet.wags, true);
/// ```
pub fn parse_encoded(string: &RawStr) -> Result<'static, T> {
let buffer = Buffer::new();
let mut ctxt = T::init(Options::Lenient);
@ -170,6 +253,33 @@ impl<T: for<'a> FromForm<'a> + 'static> Form<T> {
}
}
impl Form<()> {
/// Returns an iterator of fields parsed from a `x-www-form-urlencoded` form
/// string. Specifically, this method implements steps 1, 2, and 3.1 - 3.3
/// of [§5.1 of the WHATWG URL Living Standard]. Fields in the returned
/// iterator _are not_ percent-decoded.
///
/// [§5.1 of the WHATWG URL Living Standard]:https://url.spec.whatwg.org/#application/x-www-form-urlencoded
///
/// # Example
///
/// ```rust
/// use rocket::form::{Form, ValueField};
///
/// let string = "name=Bobby Brown&&&email=me@rocket.rs";
/// let mut values = Form::values(string);
/// assert_eq!(values.next().unwrap(), ValueField::parse("name=Bobby Brown"));
/// assert_eq!(values.next().unwrap(), ValueField::parse("email=me@rocket.rs"));
/// assert!(values.next().is_none());
/// ```
pub fn values(string: &str) -> impl Iterator<Item = ValueField<'_>> {
// WHATWG URL Living Standard 5.1 steps 1, 2, 3.1 - 3.3.
string.split('&')
.filter(|s| !s.is_empty())
.map(ValueField::parse)
}
}
impl<T> Deref for Form<T> {
type Target = T;

View File

@ -66,15 +66,80 @@ use crate::http::uncased::AsUncased;
/// [`key()`]: NameView::key()
/// [forms guide]: https://rocket.rs/master/guide/requests/#forms
///
/// # Parsing Strategy
///
/// Form parsing is either _strict_ or _lenient_, controlled by
/// [`Options::strict`]. A _strict_ parse errors when there are missing or extra
/// fields, while a _lenient_ parse allows both, providing there is a
/// [`default()`](FromForm::default()) in the case of a missing field.
///
/// Most type inherit their strategy on [`FromForm::init()`], but some types
/// like `Option` override the requested strategy. The strategy can also be
/// overwritted manually, per-field or per-value, by using the [`Strict`] or
/// [`Lenient`] form guard:
///
/// ```rust
/// use rocket::form::{self, FromForm, Strict, Lenient};
///
/// #[derive(FromForm)]
/// struct TodoTask<'r> {
/// strict_bool: Strict<bool>,
/// lenient_inner_option: Option<Lenient<bool>>,
/// strict_inner_result: form::Result<'r, Strict<bool>>,
/// }
/// ```
///
/// # Defaults
///
/// A form guard may have a _default_ which is used in case of a missing field
/// when parsing is _lenient_. When parsing is strict, all errors, including
/// missing fields, are propagated directly.
///
/// # Provided Implementations
///
/// Rocket implements `FromForm` for several types. Their behavior is documented
/// here.
/// Rocket implements `FromForm` for many common types. As a result, most
/// applications will never need a custom implementation of `FromForm` or
/// `FromFormField`. Their behavior is documented in the table below.
///
/// * **`T` where `T: FromFormField`**
/// | Type | Strategy | Default | Data | Value | Notes |
/// |--------------------|-------------|-------------------|--------|--------|----------------------------------------------------|
/// | [`Strict<T>`] | **strict** | if `strict` `T` | if `T` | if `T` | `T: FromForm` |
/// | [`Lenient<T>`] | **lenient** | if `lenient` `T` | if `T` | if `T` | `T: FromForm` |
/// | `Option<T>` | **strict** | `None` | if `T` | if `T` | Infallible, `T: FromForm` |
/// | [`Result<T>`] | _inherit_ | `T::finalize()` | if `T` | if `T` | Infallible, `T: FromForm` |
/// | `Vec<T>` | _inherit_ | `vec![]` | if `T` | if `T` | `T: FromForm` |
/// | [`HashMap<K, V>`] | _inherit_ | `HashMap::new()` | if `V` | if `V` | `K: FromForm + Eq + Hash`, `V: FromForm` |
/// | [`BTreeMap<K, V>`] | _inherit_ | `BTreeMap::new()` | if `V` | if `V` | `K: FromForm + Ord`, `V: FromForm` |
/// | `bool` | _inherit_ | `false` | No | Yes | `"yes"/"on"/"true"`, `"no"/"off"/"false"` |
/// | (un)signed int | _inherit_ | **no default** | No | Yes | `{u,i}{size,8,16,32,64,128}` |
/// | _nonzero_ int | _inherit_ | **no default** | No | Yes | `NonZero{I,U}{size,8,16,32,64,128}` |
/// | float | _inherit_ | **no default** | No | Yes | `f{32,64}` |
/// | `&str` | _inherit_ | **no default** | Yes | Yes | Percent-decoded. Data limit `string` applies. |
/// | `String` | _inherit_ | **no default** | Yes | Yes | Exactly `&str`, but owned. Prefer `&str`. |
/// | IP Address | _inherit_ | **no default** | No | Yes | [`IpAddr`], [`Ipv4Addr`], [`Ipv6Addr`] |
/// | Socket Address | _inherit_ | **no default** | No | Yes | [`SocketAddr`], [`SocketAddrV4`], [`SocketAddrV6`] |
/// | [`TempFile`] | _inherit_ | **no default** | Yes | Yes | Data limits apply. See [`TempFile`]. |
/// | [`Capped<C>`] | _inherit_ | **no default** | Yes | Yes | `C` is `&str`, `String`, or `TempFile`. |
/// | [`time::Date`] | _inherit_ | **no default** | No | Yes | `%F` (`YYYY-MM-DD`). HTML "date" input. |
/// | [`time::DateTime`] | _inherit_ | **no default** | No | Yes | `%FT%R` or `%FT%T` (`YYYY-MM-DDTHH:MM[:SS]`) |
/// | [`time::Time`] | _inherit_ | **no default** | No | Yes | `%R` or `%T` (`HH:MM[:SS]`) |
///
/// This includes types like `&str`, `usize`, and [`Date`](time::Date). See
/// [`FromFormField`] for details.
/// [`Result<T>`]: crate::form::Result
/// [`Strict<T>`]: crate::form::Strict
/// [`Lenient<T>`]: crate::form::Lenient
/// [`HashMap<K, V>`]: std::collections::HashMap
/// [`BTreeMap<K, V>`]: std::collections::BTreeMap
/// [`TempFile`]: crate::data::TempFile
/// [`Capped<C>`]: crate::data::Capped
/// [`time::DateTime`]: time::PrimitiveDateTime
/// [`IpAddr`]: std::net::IpAddr
/// [`Ipv4Addr`]: std::net::Ipv4Addr
/// [`Ipv6Addr`]: std::net::Ipv6Addr
/// [`SocketAddr`]: std::net::SocketAddr
/// [`SocketAddrV4`]: std::net::SocketAddrV4
/// [`SocketAddrV6`]: std::net::SocketAddrV6
///
/// ## Additional Notes
///
/// * **`Vec<T>` where `T: FromForm`**
///
@ -106,26 +171,32 @@ use crate::http::uncased::AsUncased;
/// Errors are collected as they occur. Finalization finalizes all pairs and
/// returns errors, if any, or the map.
///
/// * **`Option<T>` where `T: FromForm`**
/// * **[`time::DateTime`]**
///
/// _This form guard always succeeds._
/// Parses a date in `%FT%R` or `%FT%T` format, that is, `YYYY-MM-DDTHH:MM`
/// or `YYYY-MM-DDTHH:MM:SS`. This is the `"datetime-local"` HTML input type
/// without support for the millisecond variant.
///
/// Forwards all pushes to `T` without shifting. Finalizes successfully as
/// `Some(T)` if `T` finalizes without error or `None` otherwise.
/// * **[`time::Time`]**
///
/// * **`Result<T, Errors<'r>>` where `T: FromForm`**
///
/// _This form guard always succeeds._
///
/// Forwards all pushes to `T` without shifting. Finalizes successfully as
/// `Some(T)` if `T` finalizes without error or `Err(Errors)` with the
/// errors from `T` otherwise.
/// Parses a time in `%R` or `%T` format, that is, `HH:MM` or `HH:MM:SS`.
/// This is the `"time"` HTML input type without support for the millisecond
/// variant.
///
/// # Push Parsing
///
/// `FromForm` describes a 3-stage push-based interface to form parsing. After
/// preprocessing (see [the top-level docs](crate::form#parsing)), the three
/// stages are:
/// `FromForm` describes a push-based parser for Rocket's [field wire format].
/// Fields are preprocessed into either [`ValueField`]s or [`DataField`]s which
/// are then pushed to the parser in [`FromForm::push_value()`] or
/// [`FromForm::push_data()`], respectively. Both url-encoded forms and
/// multipart forms are supported. All url-encoded form fields are preprocessed
/// as [`ValueField`]s. Multipart form fields with Content-Types are processed
/// as [`DataField`]s while those without a set Content-Type are processed as
/// [`ValueField`]s. `ValueField` field names and values are percent-decoded.
///
/// [field wire format]: crate::form#field-wire-format
///
/// Parsing is split into 3 stages. After preprocessing, the three stages are:
///
/// 1. **Initialization.** The type sets up a context for later `push`es.
///
@ -215,6 +286,86 @@ use crate::http::uncased::AsUncased;
/// [`shift()`]: NameView::shift()
/// [`key()`]: NameView::key()
///
/// ## A Simple Example
///
/// The following example uses `f1=v1&f2=v2` to illustrate field/value pairs
/// `(f1, v2)` and `(f2, v2)`. This is the same encoding used to send HTML forms
/// over HTTP, though Rocket's push-parsers are unaware of any specific
/// encoding, dealing only with logical `field`s, `index`es, and `value`s.
///
/// ### A Single Field (`T: FormFormField`)
///
/// The simplest example parses a single value of type `T` from a string with an
/// optional default value: this is `impl<T: FromFormField> FromForm for T`:
///
/// 1. **Initialization.** The context stores form options and an `Option` of
/// `Result<T, form::Error>` for storing the `result` of parsing `T`, which
/// is initially set to `None`.
///
/// ```rust
/// use rocket::form::{self, FromFormField};
///
/// struct Context<'r, T: FromFormField<'r>> {
/// opts: form::Options,
/// result: Option<form::Result<'r, T>>,
/// }
///
/// # impl<'r, T: FromFormField<'r>> Context<'r, T> {
/// fn init(opts: form::Options) -> Context<'r, T> {
/// Context { opts, result: None }
/// }
/// # }
/// ```
///
/// 2. **Push.** If `ctxt.result` is `None`, `T` is parsed from `field`, and
/// the result is stored in `context.result`. Otherwise a field has already
/// been parsed and nothing is done.
///
/// ```rust
/// # use rocket::form::{self, ValueField, FromFormField};
/// # struct Context<'r, T: FromFormField<'r>> {
/// # opts: form::Options,
/// # result: Option<form::Result<'r, T>>,
/// # }
/// # impl<'r, T: FromFormField<'r>> Context<'r, T> {
/// fn push_value(ctxt: &mut Context<'r, T>, field: ValueField<'r>) {
/// if ctxt.result.is_none() {
/// ctxt.result = Some(T::from_value(field));
/// }
/// }
/// # }
/// ```
///
/// 3. **Finalization.** If `ctxt.result` is `None`, parsing is lenient, and
/// `T` has a default, the default is returned. Otherwise a `Missing` error
/// is returned. If `ctxt.result` is `Some(v)`, the result `v` is returned.
///
/// ```rust
/// # use rocket::form::{self, FromFormField, error::{Errors, ErrorKind}};
/// # struct Context<'r, T: FromFormField<'r>> {
/// # opts: form::Options,
/// # result: Option<form::Result<'r, T>>,
/// # }
/// # impl<'r, T: FromFormField<'r>> Context<'r, T> {
/// fn finalize(ctxt: Context<'r, T>) -> form::Result<'r, T> {
/// match ctxt.result {
/// Some(result) => result,
/// None if ctxt.opts.strict => Err(Errors::from(ErrorKind::Missing)),
/// None => match T::default() {
/// Some(default) => Ok(default),
/// None => Err(Errors::from(ErrorKind::Missing)),
/// }
/// }
/// }
/// # }
/// ```
///
/// This implementation is complete except for the following details:
///
/// * handling both `push_data` and `push_value`
/// * checking for duplicate pushes when parsing is `strict`
/// * tracking the field's name and value to generate a complete [`Error`]
///
/// # Implementing
///
/// Implementing `FromForm` should be a rare occurrence. Prefer instead to use
@ -253,11 +404,9 @@ use crate::http::uncased::AsUncased;
/// }
/// ```
///
/// ## Lifetime
///
/// The lifetime `'r` correponds to the lifetime of the request.
///
/// ## Example
/// ## A More Involved Example
///
/// We illustrate implementation of `FromForm` through an example. The example
/// implements `FromForm` for a `Pair(A, B)` type where `A: FromForm` and `B:
@ -278,8 +427,8 @@ use crate::http::uncased::AsUncased;
/// * `pair.0=2012-10-12&pair.1=100` as `Pair(time::Date, usize)`
///
/// ```rust
/// use rocket::form::{self, FromForm, ValueField, DataField, Error, Errors};
/// use either::Either;
/// use rocket::form::{self, FromForm, ValueField, DataField, Error, Errors};
///
/// /// A form guard parseable from fields `.0` and `.1`.
/// struct Pair<A, B>(A, B);
@ -377,7 +526,7 @@ pub trait FromForm<'r>: Send + Sized {
/// Processes the data field `field`.
async fn push_data(ctxt: &mut Self::Context, field: DataField<'r, '_>);
/// Processes the extern form or field error `_error`.
/// Processes the external form or field error `_error`.
///
/// The default implementation does nothing, which is always correct.
fn push_error(_ctxt: &mut Self::Context, _error: Error<'r>) { }
@ -390,7 +539,9 @@ pub trait FromForm<'r>: Send + Sized {
/// parsing fails.
///
/// The default implementation initializes `Self` with `opts` and finalizes
/// immediately, returning the value if finalization succeeds.
/// immediately, returning the value if finalization succeeds. This is
/// always correct and should likely not be changed. Returning a different
/// value may result in ambiguous parses.
fn default(opts: Options) -> Option<Self> {
Self::finalize(Self::init(opts)).ok()
}
@ -638,7 +789,7 @@ impl<'v, T: FromForm<'v>> FromForm<'v> for Option<T> {
type Context = <T as FromForm<'v>>::Context;
fn init(opts: Options) -> Self::Context {
T::init(opts)
T::init(Options { strict: true, ..opts })
}
fn push_value(ctxt: &mut Self::Context, field: ValueField<'v>) {
@ -650,10 +801,7 @@ impl<'v, T: FromForm<'v>> FromForm<'v> for Option<T> {
}
fn finalize(this: Self::Context) -> Result<'v, Self> {
match T::finalize(this) {
Ok(v) => Ok(Some(v)),
Err(_) => Ok(None)
}
Ok(T::finalize(this).ok())
}
}
@ -674,10 +822,7 @@ impl<'v, T: FromForm<'v>> FromForm<'v> for Result<'v, T> {
}
fn finalize(this: Self::Context) -> Result<'v, Self> {
match T::finalize(this) {
Ok(v) => Ok(Ok(v)),
Err(e) => Ok(Err(e))
}
Ok(T::finalize(this))
}
}

View File

@ -46,79 +46,8 @@ use crate::form::prelude::*;
///
/// # Provided Implementations
///
/// Rocket implements `FromFormField` for many types. Their behavior is
/// documented here.
///
/// *
/// * Numeric types: **`f32`, `f64`, `isize`, `i8`, `i16`, `i32`, `i64`,
/// `i128`, `usize`, `u8`, `u16`, `u32`, `u64`, `u128`**
/// * Address types: **`IpAddr`, `Ipv4Addr`, `Ipv6Addr`, `SocketAddrV4`,
/// `SocketAddrV6`, `SocketAddr`**
/// * Non-zero types: **`NonZeroI8`, `NonZeroI16`, `NonZeroI32`,
/// `NonZeroI64`, `NonZeroI128`, `NonZeroIsize`, `NonZeroU8`,
/// `NonZeroU16`, `NonZeroU32`, `NonZeroU64`, `NonZeroU128`,
/// `NonZeroUsize`**
///
/// A value is validated successfully if the `from_str` method for the given
/// type returns successfully. Only accepts form _values_, not binary data.
///
/// **No type-specific data limit applies.**
///
/// * **`bool`**
///
/// A value is validated successfully as `true` if the the form value is one
/// of `"on"`, `"yes"`, or `"true"` and `false` if the value is one of
/// `"off"`, `"no"`, or `"false"`. Defaults to `false` otherwise. Only
/// accepts form _values_, not binary data.
///
/// **No type-specific data limit applies.**
///
/// * **`&str`, `String`**
///
/// The decoded form value or data is returned directly without
/// modification.
///
/// **The data limit `string` applies.**
///
/// * **[`TempFile`]**
///
/// Streams the form field value or data to a temporary file.
///
/// **See [`TempFile`] for details and data limits.**
///
/// * **[`Capped<TempFile>`], [`Capped<String>`], [`Capped<&str>`]**
///
/// Streams the form value or data to the inner value, succeeding even if
/// the data exceeds the respective type limit by truncating the data.
///
/// **See [`Capped`] for details.**
///
/// * **[`time::Date`]**
///
/// Parses a date in the `%F` format, that is, `%Y-$m-%d` or `YYYY-MM-DD`.
/// This is the `"date"` HTML input type. Only accepts form _values_, not
/// binary data.
///
/// **No type-specific data limit applies.**
///
/// * **[`time::PrimitiveDateTime`]**
///
/// Parses a date in `%FT%R` or `%FT%T` format, that is, `YYYY-MM-DDTHH:MM`
/// or `YYYY-MM-DDTHH:MM:SS`. This is the `"datetime-local"` HTML input type
/// without support for the millisecond variant. Only accepts form _values_,
/// not binary data.
///
/// **No type-specific data limit applies.**
///
/// * **[`time::Time`]**
///
/// Parses a time in `%R` or `%T` format, that is, `HH:MM` or `HH:MM:SS`.
/// This is the `"time"` HTML input type without support for the millisecond
/// variant. Only accepts form _values_, not binary data.
///
/// **No type-specific data limit applies.**
///
/// [`TempFile`]: crate::data::TempFile
/// See [`FromForm`](crate::form::FromForm#provided-implementations) for a list
/// of all form guards, including those implemented via `FromFormField`.
///
/// # Implementing
///
@ -228,19 +157,30 @@ use crate::form::prelude::*;
// this concern. Thus, for now, we keep this as one trait.
#[crate::async_trait]
pub trait FromFormField<'v>: Send + Sized {
/// Parse a value of `T` from a form value field.
///
/// The default implementation returns an error of
/// [`ValueField::unexpected()`].
fn from_value(field: ValueField<'v>) -> Result<'v, Self> {
Err(field.unexpected())?
}
/// Parse a value of `T` from a form data field.
///
/// The default implementation returns an error of
/// [`DataField::unexpected()`].
async fn from_data(field: DataField<'v, '_>) -> Result<'v, Self> {
Err(field.unexpected())?
}
/// Returns a default value to be used when the form field does not exist or
/// parsing otherwise fails.
/// Returns a default value, if any exists, to be used during lenient
/// parsing when the form field is missing.
///
/// If this returns `None`, the field is required. Otherwise, this should
/// return `Some(default_value)`. The default implementation returns `None`.
/// A return value of `None` means that field is required to exist and parse
/// successfully, always. A return value of `Some(default)` means that
/// `default` should be used when a field is missing.
///
/// The default implementation returns `None`.
fn default() -> Option<Self> { None }
}

View File

@ -0,0 +1,129 @@
use std::ops::{Deref, DerefMut};
use crate::form::prelude::*;
use crate::http::uri::{Query, FromUriParam};
/// A form guard for parsing form types leniently.
///
/// This type implements the [`FromForm`] trait and thus can be used as a
/// generic parameter to the [`Form`] data guard: `Form<Lenient<T>>`, where `T`
/// implements `FromForm`. Unlike using `Form` directly, this type uses a
/// _lenient_ parsing strategy: forms that contains a superset of the expected
/// fields (i.e, extra fields) will fail to parse and defaults will not be use
/// for missing fields.
///
/// # Lenient Parsing
///
/// A `Lenient<T>` will parse successfully from an incoming form even if the
/// form contains extra or missing fields. If fields are missing, the form field
/// type's default will be used, if there is one. Extra fields are ignored; only
/// the first is parsed and validated. This is the default strategy for
/// [`Form`].
///
/// # Usage
///
/// `Lenient<T>` implements [`FromForm`] as long as `T` implements `FromForm`.
/// As such, `Form<Lenient<T>>` is a data guard.
///
/// Note that `Form<T>` _already_ parses leniently, so a `Form<Lenient<T>>` is
/// redundant and equal to `Form<T>`. `Lenient`, however, can be used to make
/// otherwise strict parses lenient, for example, in `Option<Lenient<T>>`:
///
/// ```rust
/// # #[macro_use] extern crate rocket;
/// use rocket::form::Lenient;
///
/// #[derive(FromForm)]
/// struct UserInput {
/// lenient_inner_option: Option<Lenient<bool>>,
/// }
/// ```
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Lenient<T>(T);
impl<T> Lenient<T> {
/// Consumes `self` and returns the inner value.
///
/// Note that since `Lenient` implements [`Deref`] and [`DerefMut`] with
/// target `T`, reading and writing an inner value can be accomplished
/// transparently.
///
/// # Example
///
/// ```rust
/// # #[macro_use] extern crate rocket;
/// use rocket::form::{Form, Lenient};
///
/// #[derive(FromForm)]
/// struct MyForm {
/// field: String,
/// }
///
/// #[post("/submit", data = "<form>")]
/// fn submit(form: Form<Lenient<MyForm>>) -> String {
/// // We can read or mutate a value transparently:
/// let field: &str = &form.field;
///
/// // To gain ownership, however, use `into_inner()`:
/// form.into_inner().into_inner().field
/// }
/// ```
pub fn into_inner(self) -> T {
self.0
}
}
#[crate::async_trait]
impl<'v, T: FromForm<'v>> FromForm<'v> for Lenient<T> {
type Context = T::Context;
#[inline(always)]
fn init(opts: Options) -> Self::Context {
T::init(Options { strict: false, ..opts })
}
#[inline(always)]
fn push_value(ctxt: &mut Self::Context, field: ValueField<'v>) {
T::push_value(ctxt, field)
}
#[inline(always)]
async fn push_data(ctxt: &mut Self::Context, field: DataField<'v, '_>) {
T::push_data(ctxt, field).await
}
#[inline(always)]
fn finalize(this: Self::Context) -> Result<'v, Self> {
T::finalize(this).map(Self)
}
}
impl<T> Deref for Lenient<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<T> DerefMut for Lenient<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl<T> From<T> for Lenient<T> {
#[inline]
fn from(val: T) -> Lenient<T> {
Lenient(val)
}
}
impl<'f, A, T: FromUriParam<Query, A> + FromForm<'f>> FromUriParam<Query, A> for Lenient<T> {
type Target = T::Target;
#[inline(always)]
fn from_uri_param(param: A) -> Self::Target {
T::from_uri_param(param)
}
}

View File

@ -45,94 +45,7 @@
//! * `map[k:1]=Bob`
//! * `people[bob]nickname=Stan`
//!
//! # Parsing
//!
//! The [`FromForm`] trait describes a push-based parser for this wire format.
//! Fields are preprocessed into either [`ValueField`]s or [`DataField`]s which
//! are then pushed to the parser in [`FromForm::push_value()`] or
//! [`FromForm::push_data()`], respectively. Both url-encoded forms and
//! multipart forms are supported. All url-encoded form fields are preprocessed
//! as [`ValueField`]s. Multipart form fields with Content-Types are processed
//! as [`DataField`]s while those without a set Content-Type are processed as
//! [`ValueField`]s. `ValueField` field names and values are percent-decoded.
//!
//! # Data Limits
//!
//! The total amount of data accepted by the [`Form`] data guard is limited by
//! the following limits:
//!
//! | Limit Name | Default | Description |
//! |-------------|---------|------------------------------------|
//! | `form` | 32KiB | total limit for url-encoded forms |
//! | `data-form` | 2MiB | total limit for multipart forms |
//! | `*` | N/A | each field type has its own limits |
//!
//! Additionally, as noted above, each form field type (a form guard) typically
//! imposes its own limits. For example, the `&str` form guard imposes a data
//! limit of `string` when multipart data is streamed.
//!
//! See the [`Limits`](crate::data::Limits) and [`Form#data-limits`] docs for
//! more.
//!
//! # A Simple Example
//!
//! The following example uses `f1=v1&f2=v2` to illustrate field/value pairs
//! `(f1, v2)` and `(f2, v2)`. This is the same encoding used to send HTML forms
//! over HTTP but Rocket's push-parsers are unaware of any specific encoding,
//! dealing only with logical `field`s, `index`es, and `value`s.
//!
//! ## A Single Field (`T: FormFormField`)
//!
//! The simplest example parses a single value of type `T` from a string with an
//! optional default value: this is `impl<T: FromFormField> FromForm for T`:
//!
//! 1. **Initialization.** The context stores form options and an `Option` of
//! `Result<T, form::Error>` for storing the `result` of parsing `T`, which
//! is initially set to `None`.
//!
//! ```rust,ignore
//! struct Context<T> {
//! opts: Options,
//! result: Option<Result<T>>,
//! }
//! ```
//!
//! 2. **Push.** If `context.result` is `None`, `T` is parsed from `field`,
//! and the result is stored in `context.result`. Otherwise a field has
//! already been parsed and nothing is done.
//!
//! ```rust,ignore
//! fn push(ctxt: &mut Self::Context, field: Field<'v>) {
//! if ctxt.result.is_none() {
//! ctxt.result = Some(Self::from_field(field));
//! }
//! }
//! ```
//!
//! 3. **Finalization.** If `ctxt.result` is `None` and `T` has a default,
//! the default is returned. Otherwise a `Missing` error is returned. If
//! `ctxt.result` is `Some(v)`, the result `v` is returned.
//!
//! ```rust,ignore
//! fn finalize(ctxt: Self::Context) -> Result<Self> {
//! match ctxt.result {
//! Some(value) => Ok(value),
//! None => match <T as FromFormField>::default() {
//! Some(default) => Ok(default),
//! None => Err(ErrorKind::Missing)?,
//! }
//! }
//! }
//! ```
//!
//! This implementation is complete except for the following details:
//!
//! * not being pseudocode, of course
//! * checking for duplicate pushes when parsing is `strict`
//! * disallowing defaults when parsing is `strict`
//! * tracking the field's name and value to generate a complete `Error`
//!
//! See [`FromForm`] for full details on push-parsing and a complete example.
//! See [`FromForm`] for full details on push-parsing and complete examples.
// ## Maps w/named Fields (`struct`)
//
@ -453,6 +366,7 @@ mod from_form_field;
mod form;
mod context;
mod strict;
mod lenient;
mod parser;
pub mod validate;
pub mod name;
@ -477,6 +391,7 @@ pub use from_form::*;
pub use form::*;
pub use context::*;
pub use strict::*;
pub use lenient::*;
#[doc(hidden)]
pub mod prelude {

View File

@ -53,7 +53,7 @@ use crate::http::uri::{Query, FromUriParam};
/// uses_default: bool
/// }
/// ```
#[derive(Debug)]
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct Strict<T>(T);
impl<T> Strict<T> {

View File

@ -3,9 +3,7 @@ use std::collections::HashMap;
use crate::form::*;
fn parse<'v, T: FromForm<'v>>(values: &[&'v str]) -> Result<'v, T> {
let mut context = T::init(Options::Lenient);
values.iter().for_each(|v| T::push_value(&mut context, ValueField::parse(*v)));
T::finalize(context)
Form::parse_iter(values.iter().cloned().map(ValueField::parse))
}
macro_rules! map {
@ -27,7 +25,7 @@ macro_rules! vec {
macro_rules! assert_values_parse_eq {
($($v:expr => $T:ty = $expected:expr),* $(,)?) => (
$(
assert_value_parse_eq!($v => $T = $expected);
assert_value_parse_eq!($v as &[&str] => $T = $expected);
)*
)
}
@ -41,7 +39,25 @@ macro_rules! assert_value_parse_eq {
panic!("unexpected parse of {:?}\n {:?} instead of {:?}",
$v, actual, expected)
}
Err(e) => panic!("parse of {:?} failed: {:?}", $v, e)
Err(e) => panic!("parse `{:?} {}` failed: {:?}", $v, stringify!(=> $T = $expected), e)
}
)
}
macro_rules! assert_parses_fail {
($($v:expr => $T:ty),* $(,)?) => (
$(
assert_parse_fails!($v as &[&str] => $T);
)*
)
}
macro_rules! assert_parse_fails {
($v:expr => $T:ty) => (
let diag = format!("{:?} {}", $v, stringify!(=> $T = $expected));
match parse::<$T>($v) {
Ok(actual) => panic!("unexpectedly parsed {} as {:?}", diag, actual),
Err(_) => { /* ok */ }
}
)
}
@ -65,6 +81,41 @@ fn bool() {
assert_values_parse_eq! {
&["=true", "=yes", "=on"] => Vec<bool> = vec![true, true, true],
&["=false", "=no", "=off"] => Vec<bool> = vec![false, false, false],
&["=tRuE", "=YES", "=On"] => Vec<bool> = vec![true, true, true],
&["=fAlSE", "=NO", "=OFF"] => Vec<bool> = vec![false, false, false],
}
assert_parses_fail! {
&[] => Strict<bool>,
&["=unknown"] => bool,
&["=unknown", "=please"] => Vec<bool>,
}
}
#[test]
fn defaults() {
assert_values_parse_eq! {
&[] => bool = false,
&[] => Option<&str> = None,
&[] => Option<time::Date> = None,
&[] => Option<bool> = None,
&[] => Option<Strict<bool>> = None,
&[] => Result<'_, bool> = Ok(false),
&[] => Result<'_, Strict<bool>> = Err(error::ErrorKind::Missing.into()),
&["=unknown"] => Option<bool> = None,
&["=unknown"] => Option<Strict<bool>> = None,
&["=unknown"] => Option<Lenient<bool>> = None,
&[] => Option<Lenient<bool>> = Some(false.into()),
&["=123"] => Option<time::Date> = None,
&["=no"] => Option<bool> = Some(false),
&["=yes"] => Option<bool> = Some(true),
&["=yes"] => Option<Lenient<bool>> = Some(true.into()),
&["=yes"] => Option<Strict<bool>> = Some(true.into()),
}
}

View File

@ -726,7 +726,7 @@ fn new(task: Option<Form<Task>>) { /* .. */ }
[`Form`]: @api/rocket/form/struct.Form.html
[`FromForm`]: @api/rocket/form/trait.FromForm.html
### Strict Parsing
### Parsing Strategy
Rocket's `FromForm` parsing is _lenient_ by default: a `Form<T>` will parse
successfully from an incoming form even if it contains extra, duplicate, or
@ -768,7 +768,41 @@ struct Input {
fn new(input: Form<Input>) { /* .. */ }
```
[`Lenient`] is the _lenient_ analog to `Strict`, which forces parsing to be
lenient. `Form` is lenient by default, so a `Form<Lenient<T>>` is redundant, but
`Lenient` can be used to overwrite a strict parse as lenient:
`Option<Lenient<T>>`.
[`Form<Strict<T>>`]: @api/rocket/form/struct.Strict.html
[`Lenient`]: @api/rocket/form/struct.Lenient.html
### Defaults
A form guard may specify a default value to use when a field is missing. The
default value is used only when parsing is _lenient_. When _strict_, all errors,
including, missing fields are propagated directly.
Some types with defaults include `bool`, which defaults to `false`, useful for
checkboxes, `Option<T>`, which defaults to `None`, and [`form::Result`], which
defaults to `Err(Missing)` or otherwise collects errors in an `Err` of
[`Errors<'_>`]. Defaulting guards can be used just like any other form guard:
```rust
# use rocket::form::FromForm;
use rocket::form::{self, Errors};
#[derive(FromForm)]
struct MyForm<'v> {
maybe_string: Option<&'v str>,
ok_or_error: form::Result<'v, Vec<&'v str>>,
here_or_false: bool,
}
# rocket_guide_tests::assert_form_parses_ok!(MyForm, "");
```
[`Errors<'_>`]: @api/rocket/form/struct.Errors.html
[`form::Result`]: @api/rocket/form/type.Result.html
### Field Renaming
@ -913,30 +947,6 @@ If a field's validation doesn't depend on other fields (validation is _local_),
it is validated prior to those fields that do. For `CreditCard`, `cvv` and
`expiration` will be validated prior to `number`.
### Defaults
The [`FromForm`] trait allows types to specify a default value if one isn't
provided in a submitted form. This includes types such as `bool`, useful for
checkboxes, and `Option<T>`. Additionally, `FromForm` is implemented for
`Result<T, Errors<'_>>` where the error value is [`Errors<'_>`]. All of these
types can be used just like any other form field:
```rust
# use rocket::form::FromForm;
use rocket::form::Errors;
#[derive(FromForm)]
struct MyForm<'v> {
maybe_string: Option<String>,
ok_or_error: Result<Vec<String>, Errors<'v>>,
here: bool,
}
# rocket_guide_tests::assert_form_parses_ok!(MyForm, "");
```
[`Errors<'_>`]: @api/rocket/form/struct.Errors.html
### Collections
Rocket's form support allows your application to express _any_ structure with