pub trait FromForm<'r>: Send + Sized {
type Context: Send;
// Required methods
fn init(opts: Options) -> Self::Context;
fn push_value(ctxt: &mut Self::Context, field: ValueField<'r>);
fn push_data<'life0, 'life1, 'async_trait>(
ctxt: &'life0 mut Self::Context,
field: DataField<'r, 'life1>,
) -> Pin<Box<dyn Future<Output = ()> + Send + 'async_trait>>
where Self: 'async_trait,
'r: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait;
fn finalize(ctxt: Self::Context) -> Result<'r, Self>;
// Provided methods
fn push_error(_ctxt: &mut Self::Context, _error: Error<'r>) { ... }
fn default(opts: Options) -> Option<Self> { ... }
}Expand description
Trait implemented by form guards: types parseable from HTTP forms.
Only form guards that are collections, that is, collect more than one form
field while parsing, should implement FromForm. All other types should
implement FromFormField instead, which offers a simplified interface to
parsing a single form field.
For a gentle introduction to forms in Rocket, see the forms guide.
§Form Guards
A form guard is a guard that operates on form fields, typically those with a
particular name prefix. Form guards validate and parse form field data via
implementations of FromForm. In other words, a type is a form guard iff
it implements FromForm.
Form guards are used as the inner type of the Form data guard:
use rocket::form::Form;
#[post("/submit", data = "<var>")]
fn submit(var: Form<FormGuard>) { /* ... */ }§Deriving
This trait can, and largely should, be automatically derived. When
deriving FromForm, every field in the structure must implement
FromForm. Form fields with the struct field’s name are shifted and
then pushed to the struct field’s FromForm parser.
use rocket::form::FromForm;
#[derive(FromForm)]
struct TodoTask<'r> {
#[field(validate = len(1..))]
description: &'r str,
#[field(name = "done")]
completed: bool
}For full details on deriving FromForm, see the FromForm derive.
§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() 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
overwritten manually, per-field or per-value, by using the Strict or
Lenient form guard:
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 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.
| 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 |
Range<T> | inherit | no default | if T | if T | T: FromForm, expects start, end fields |
RangeFrom<T> | inherit | no default | if T | if T | T: FromForm, expects start field |
RangeTo<T> | inherit | no default | if T | if T | T: FromForm, expects end field |
RangeToInclusive<T> | inherit | no default | if T | if T | T: FromForm, expects end field |
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. |
&[u8] | inherit | no default | Yes | Yes | Raw bytes. Data limit bytes 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, &[u8] 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]) |
§Additional Notes
-
Vec<T>whereT: FromFormParses a sequence of
T’s. A newTis created whenever the field name’s key changes or is empty; the previousTis finalized and errors are stored. While the key remains the same and non-empty, form values are pushed to the currentTafter being shifted. All collected errors are returned at finalization, if any, or the successfully created vector is returned. -
HashMap<K, V>whereK: FromForm + Eq + Hash,V: FromFormBTreeMap<K, V>whereK: FromForm + Ord,V: FromFormParses a sequence of
(K, V)’s. A new pair is created for every unique first index of the key.If the key has only one index (
map[index]=value), the index itself is pushed toK’s parser and the remaining shifted field is pushed toV’s parser.If the key has two indices (
map[k:index]=valueormap[v:index]=value), the first index must start withkorv. If the first index starts withk, the shifted field is pushed toK’s parser. If the first index starts withv, the shifted field is pushed toV’s parser. If the first index is anything else, an error is created for the offending form field.Errors are collected as they occur. Finalization finalizes all pairs and returns errors, if any, or the map.
-
boolParses as
falsefor missing values (when lenient) and case-insensitive values ofoff,false, andno. Parses astruefor values ofon,true,yes, and the empty value. Failed to parse otherwise. -
Parses a date in
%FT%Ror%FT%Tformat, that is,YYYY-MM-DDTHH:MMorYYYY-MM-DDTHH:MM:SS. This is the"datetime-local"HTML input type without support for the millisecond variant. -
Parses a time in
%Ror%Tformat, that is,HH:MMorHH:MM:SS. This is the"time"HTML input type without support for the millisecond variant.
§Push Parsing
FromForm describes a push-based parser for Rocket’s field wire format.
Fields are preprocessed into either ValueFields or DataFields 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 ValueFields. Multipart form fields with Content-Types are processed
as DataFields while those without a set Content-Type are processed as
ValueFields. ValueField field names and values are percent-decoded.
Parsing is split into 3 stages. After preprocessing, the three stages are:
-
Initialization. The type sets up a context for later
pushes.use rocket::form::Options; fn init(opts: Options) -> Self::Context { todo!("return a context for storing parse state") } -
Push. The structure is repeatedly pushed form fields; the latest context is provided with each
push. If the structure contains children, it uses the firstkey()to identify a child to which it thenpushes the remainingfieldto, likely with ashift()ed name. Otherwise, the structure parses thevalueitself. The context is updated as needed.use rocket::form::{ValueField, DataField}; fn push_value(ctxt: &mut Self::Context, field: ValueField<'r>) { todo!("modify context as necessary for `field`") } async fn push_data(ctxt: &mut Self::Context, field: DataField<'r, '_>) { todo!("modify context as necessary for `field`") } -
Finalization. The structure is informed that there are no further fields. It systemizes the effects of previous
pushes via its context to return a parsed structure or generateErrors.use rocket::form::Result; fn finalize(ctxt: Self::Context) -> Result<'r, Self> { todo!("inspect context to generate `Self` or `Errors`") }
These three stages make up the entirety of the FromForm trait.
§Nesting and NameView
Each field name key typically identifies a unique child of a structure. As such, when processed left-to-right, the keys of a field jointly identify a unique leaf of a structure. The value of the field typically represents the desired value of the leaf.
A NameView captures and simplifies this “left-to-right” processing of a
field’s name by exposing a sliding-prefix view into a name. A shift()
shifts the view one key to the right. Thus, a Name of a.b.c when viewed
through a new NameView is a. Shifted once, the view is a.b.
key() returns the last (or “current”) key in the view. A nested
structure can thus handle a field with a NameView, operate on the
key(), shift() the NameView, and pass the field with the shifted
NameView to the next processor which handles b and so on.
§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 fields, indexes, and values.
§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:
-
Initialization. The context stores form options and an
OptionofResult<T, form::Error>for storing theresultof parsingT, which is initially set toNone.use rocket::form::{self, FromFormField}; struct Context<'r, T: FromFormField<'r>> { opts: form::Options, result: Option<form::Result<'r, T>>, } fn init(opts: form::Options) -> Context<'r, T> { Context { opts, result: None } } -
Push. If
ctxt.resultisNone,Tis parsed fromfield, and the result is stored incontext.result. Otherwise a field has already been parsed and nothing is done.fn push_value(ctxt: &mut Context<'r, T>, field: ValueField<'r>) { if ctxt.result.is_none() { ctxt.result = Some(T::from_value(field)); } } -
Finalization. If
ctxt.resultisNone, parsing is lenient, andThas a default, the default is returned. Otherwise aMissingerror is returned. Ifctxt.resultisSome(v), the resultvis returned.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_dataandpush_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
Rocket’s built-in derivation or, for custom types, implementing
FromFormField.
An implementation of FromForm consists of implementing the three stages
outlined above. FromForm is an async trait, so implementations must be
decorated with an attribute of #[rocket::async_trait]:
use rocket::form::{self, FromForm, DataField, ValueField};
#[rocket::async_trait]
impl<'r> FromForm<'r> for MyType {
type Context = MyContext;
fn init(opts: form::Options) -> Self::Context {
todo!()
}
fn push_value(ctxt: &mut Self::Context, field: ValueField<'r>) {
todo!()
}
async fn push_data(ctxt: &mut Self::Context, field: DataField<'r, '_>) {
todo!()
}
fn finalize(this: Self::Context) -> form::Result<'r, Self> {
todo!()
}
}The lifetime 'r corresponds to the lifetime of the request.
§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: FromForm, parseable from forms with at least two fields, one with a key of
0 and the other with a key of 1. The field with key 0 is parsed as an
A while the field with key 1 is parsed as a B. Specifically, to parse
a Pair(A, B) from a field with prefix pair, a form with the following
fields must be submitted:
pair[0]- type Apair[1]- type B
Examples include:
pair[0]=id&pair[1]=100asPair(&str, usize)pair[0]=id&pair[1]=100asPair(&str, &str)pair[0]=2012-10-12&pair[1]=100asPair(time::Date, &str)pair.0=2012-10-12&pair.1=100asPair(time::Date, usize)
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);
// The parsing context. We'll be pushing fields with key `.0` to `left`
// and fields with `.1` to `right`. We'll collect errors along the way.
struct PairContext<'v, A: FromForm<'v>, B: FromForm<'v>> {
left: A::Context,
right: B::Context,
errors: Errors<'v>,
}
#[rocket::async_trait]
impl<'v, A: FromForm<'v>, B: FromForm<'v>> FromForm<'v> for Pair<A, B> {
type Context = PairContext<'v, A, B>;
// We initialize the `PairContext` as expected.
fn init(opts: form::Options) -> Self::Context {
PairContext {
left: A::init(opts),
right: B::init(opts),
errors: Errors::new()
}
}
// For each value, we determine if the key is `.0` (left) or `.1`
// (right) and push to the appropriate parser. If it was neither, we
// store the error for emission on finalization. The parsers for `A` and
// `B` will handle duplicate values and so on.
fn push_value(ctxt: &mut Self::Context, field: ValueField<'v>) {
match ctxt.context(field.name) {
Ok(Either::Left(ctxt)) => A::push_value(ctxt, field.shift()),
Ok(Either::Right(ctxt)) => B::push_value(ctxt, field.shift()),
Err(e) => ctxt.errors.push(e),
}
}
// This is identical to `push_value` but for data fields.
async fn push_data(ctxt: &mut Self::Context, field: DataField<'v, '_>) {
match ctxt.context(field.name) {
Ok(Either::Left(ctxt)) => A::push_data(ctxt, field.shift()).await,
Ok(Either::Right(ctxt)) => B::push_data(ctxt, field.shift()).await,
Err(e) => ctxt.errors.push(e),
}
}
// Finally, we finalize `A` and `B`. If both returned `Ok` and we
// encountered no errors during the push phase, we return our pair. If
// there were errors, we return them. If `A` and/or `B` failed, we
// return the commutative errors.
fn finalize(mut ctxt: Self::Context) -> form::Result<'v, Self> {
match (A::finalize(ctxt.left), B::finalize(ctxt.right)) {
(Ok(l), Ok(r)) if ctxt.errors.is_empty() => Ok(Pair(l, r)),
(Ok(_), Ok(_)) => Err(ctxt.errors),
(left, right) => {
if let Err(e) = left { ctxt.errors.extend(e); }
if let Err(e) = right { ctxt.errors.extend(e); }
Err(ctxt.errors)
}
}
}
}
impl<'v, A: FromForm<'v>, B: FromForm<'v>> PairContext<'v, A, B> {
// Helper method used by `push_{value, data}`. Determines which context
// we should push to based on the field name's key. If the key is
// neither `0` nor `1`, we return an error.
fn context(
&mut self,
name: form::name::NameView<'v>
) -> Result<Either<&mut A::Context, &mut B::Context>, Error<'v>> {
use std::borrow::Cow;
match name.key().map(|k| k.as_str()) {
Some("0") => Ok(Either::Left(&mut self.left)),
Some("1") => Ok(Either::Right(&mut self.right)),
_ => Err(Error::from(&[Cow::Borrowed("0"), Cow::Borrowed("1")])
.with_entity(form::error::Entity::Index(0))
.with_name(name)),
}
}
}Required Associated Types§
Required Methods§
Sourcefn push_value(ctxt: &mut Self::Context, field: ValueField<'r>)
fn push_value(ctxt: &mut Self::Context, field: ValueField<'r>)
Processes the value field field.
Provided Methods§
Sourcefn push_error(_ctxt: &mut Self::Context, _error: Error<'r>)
fn push_error(_ctxt: &mut Self::Context, _error: Error<'r>)
Processes the external form or field error _error.
The default implementation does nothing, which is always correct.
Sourcefn default(opts: Options) -> Option<Self>
fn default(opts: Options) -> Option<Self>
Returns a default value, if any, to use when a value is desired and parsing fails.
The default implementation initializes Self with opts and finalizes
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.
Dyn Compatibility§
This trait is not dyn compatible.
In older versions of Rust, dyn compatibility was called "object safety", so this trait is not object safe.