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 |
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] ) |
Additional Notes
-
Vec<T>
whereT: FromForm
Parses a sequence of
T
’s. A newT
is created whenever the field name’s key changes or is empty; the previousT
is finalized and errors are stored. While the key remains the same and non-empty, form values are pushed to the currentT
after 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: FromForm
BTreeMap<K, V>
whereK: FromForm + Ord
,V: FromForm
Parses 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]=value
ormap[v:index]=value
), the first index must start withk
orv
. 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.
-
bool
Parses as
false
for missing values (when lenient) and case-insensitive values ofoff
,false
, andno
. Parses astrue
for values ofon
,true
,yes
, and the empty value. Failed to parse otherwise. -
Parses a date in
%FT%R
or%FT%T
format, that is,YYYY-MM-DDTHH:MM
orYYYY-MM-DDTHH:MM:SS
. This is the"datetime-local"
HTML input type without support for the millisecond variant. -
Parses a time in
%R
or%T
format, that is,HH:MM
orHH: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 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.
Parsing is split into 3 stages. After preprocessing, the three stages are:
-
Initialization. The type sets up a context for later
push
es.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 thenpush
es the remainingfield
to, likely with ashift()
ed name. Otherwise, the structure parses thevalue
itself. 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
push
es 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 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
:
-
Initialization. The context stores form options and an
Option
ofResult<T, form::Error>
for storing theresult
of 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.result
isNone
,T
is 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.result
isNone
, parsing is lenient, andT
has a default, the default is returned. Otherwise aMissing
error is returned. Ifctxt.result
isSome(v)
, the resultv
is 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_data
andpush_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]=100
asPair(&str, usize)
pair[0]=id&pair[1]=100
asPair(&str, &str)
pair[0]=2012-10-12&pair[1]=100
asPair(time::Date, &str)
pair.0=2012-10-12&pair.1=100
asPair(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.