Trait rocket::form::FromForm

source ·
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.

TypeStrategyDefaultDataValueNotes
Strict<T>strictif strict Tif Tif TT: FromForm
Lenient<T>lenientif lenient Tif Tif TT: FromForm
Option<T>strictNoneif Tif TInfallible, T: FromForm
Result<T>inheritT::finalize()if Tif TInfallible, T: FromForm
Vec<T>inheritvec![]if Tif TT: FromForm
HashMap<K, V>inheritHashMap::new()if Vif VK: FromForm + Eq + Hash, V: FromForm
BTreeMap<K, V>inheritBTreeMap::new()if Vif VK: FromForm + Ord, V: FromForm
boolinheritfalseNoYes"yes"/"on"/"true", "no"/"off"/"false"
(un)signed intinheritno defaultNoYes{u,i}{size,8,16,32,64,128}
nonzero intinheritno defaultNoYesNonZero{I,U}{size,8,16,32,64,128}
floatinheritno defaultNoYesf{32,64}
&strinheritno defaultYesYesPercent-decoded. Data limit string applies.
Stringinheritno defaultYesYesExactly &str, but owned. Prefer &str.
IP Addressinheritno defaultNoYesIpAddr, Ipv4Addr, Ipv6Addr
Socket Addressinheritno defaultNoYesSocketAddr, SocketAddrV4, SocketAddrV6
TempFileinheritno defaultYesYesData limits apply. See TempFile.
Capped<C>inheritno defaultYesYesC is &str, String, or TempFile.
time::Dateinheritno defaultNoYes%F (YYYY-MM-DD). HTML “date” input.
time::DateTimeinheritno defaultNoYes%FT%R or %FT%T (YYYY-MM-DDTHH:MM[:SS])
time::Timeinheritno defaultNoYes%R or %T (HH:MM[:SS])

Additional Notes

  • Vec<T> where T: FromForm

    Parses a sequence of T’s. A new T is created whenever the field name’s key changes or is empty; the previous T is finalized and errors are stored. While the key remains the same and non-empty, form values are pushed to the current T after being shifted. All collected errors are returned at finalization, if any, or the successfully created vector is returned.

  • HashMap<K, V> where K: FromForm + Eq + Hash, V: FromForm

    BTreeMap<K, V> where K: 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 to K’s parser and the remaining shifted field is pushed to V’s parser.

    If the key has two indices (map[k:index]=value or map[v:index]=value), the first index must start with k or v. If the first index starts with k, the shifted field is pushed to K’s parser. If the first index starts with v, the shifted field is pushed to V’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 of off, false, and no. Parses as true for values of on, true, yes, and the empty value. Failed to parse otherwise.

  • time::DateTime

    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.

  • 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.

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:

  1. 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")
    }
  2. Push. The structure is repeatedly pushed form fields; the latest context is provided with each push. If the structure contains children, it uses the first key() to identify a child to which it then pushes the remaining field to, likely with a shift()ed name. Otherwise, the structure parses the value 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`")
    }
  3. 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 generate Errors.

    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:

  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.

    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 }
    }
  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.

    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.

    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 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 A
  • pair[1] - type B

Examples include:

  • pair[0]=id&pair[1]=100 as Pair(&str, usize)
  • pair[0]=id&pair[1]=100 as Pair(&str, &str)
  • pair[0]=2012-10-12&pair[1]=100 as Pair(time::Date, &str)
  • pair.0=2012-10-12&pair.1=100 as Pair(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§

source

type Context: Send

The form guard’s parsing context.

Required Methods§

source

fn init(opts: Options) -> Self::Context

Initializes and returns the parsing context for Self.

source

fn push_value(ctxt: &mut Self::Context, field: ValueField<'r>)

Processes the value field field.

source

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,

Processes the data field field.

source

fn finalize(ctxt: Self::Context) -> Result<'r, Self>

Finalizes parsing. Returns the parsed value when successful or collection of Errors otherwise.

Provided Methods§

source

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.

source

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.

Implementations on Foreign Types§

source§

impl<'v, T: FromForm<'v> + 'v> FromForm<'v> for Vec<T>

§

type Context = VecContext<'v, T>

source§

fn init(opts: Options) -> Self::Context

source§

fn push_value(this: &mut Self::Context, field: ValueField<'v>)

source§

fn push_data<'life0, 'life1, 'async_trait>( this: &'life0 mut Self::Context, field: DataField<'v, 'life1> ) -> Pin<Box<dyn Future<Output = ()> + Send + 'async_trait>>where Self: 'async_trait, 'v: 'async_trait, 'life0: 'async_trait, 'life1: 'async_trait,

source§

fn finalize(this: Self::Context) -> Result<'v, Self>

source§

impl<'v, A: FromForm<'v>, B: FromForm<'v>> FromForm<'v> for (A, B)

§

type Context = PairContext<'v, A, B>

source§

fn init(opts: Options) -> Self::Context

source§

fn push_value(ctxt: &mut Self::Context, field: ValueField<'v>)

source§

fn push_data<'life0, 'life1, 'async_trait>( ctxt: &'life0 mut Self::Context, field: DataField<'v, 'life1> ) -> Pin<Box<dyn Future<Output = ()> + Send + 'async_trait>>where Self: 'async_trait, 'v: 'async_trait, 'life0: 'async_trait, 'life1: 'async_trait,

source§

fn finalize(ctxt: Self::Context) -> Result<'v, Self>

source§

impl<'v, T: FromForm<'v> + Sync> FromForm<'v> for Arc<T>

§

type Context = <T as FromForm<'v>>::Context

source§

fn init(opts: Options) -> Self::Context

source§

fn push_value(ctxt: &mut Self::Context, field: ValueField<'v>)

source§

fn push_data<'life0, 'life1, 'async_trait>( ctxt: &'life0 mut Self::Context, field: DataField<'v, 'life1> ) -> Pin<Box<dyn Future<Output = ()> + Send + 'async_trait>>where Self: 'async_trait, 'v: 'async_trait, 'life0: 'async_trait, 'life1: 'async_trait,

source§

fn finalize(this: Self::Context) -> Result<'v, Self>

source§

impl<'v, K, V> FromForm<'v> for HashMap<K, V>where K: FromForm<'v> + Eq + Hash, V: FromForm<'v>,

§

type Context = MapContext<'v, K, V>

source§

fn init(opts: Options) -> Self::Context

source§

fn push_value(ctxt: &mut Self::Context, field: ValueField<'v>)

source§

fn push_data<'life0, 'life1, 'async_trait>( ctxt: &'life0 mut Self::Context, field: DataField<'v, 'life1> ) -> Pin<Box<dyn Future<Output = ()> + Send + 'async_trait>>where Self: 'async_trait, 'v: 'async_trait, 'life0: 'async_trait, 'life1: 'async_trait,

source§

fn finalize(this: Self::Context) -> Result<'v, Self>

source§

impl<'v, K, V> FromForm<'v> for BTreeMap<K, V>where K: FromForm<'v> + Ord, V: FromForm<'v>,

§

type Context = MapContext<'v, K, V>

source§

fn init(opts: Options) -> Self::Context

source§

fn push_value(ctxt: &mut Self::Context, field: ValueField<'v>)

source§

fn push_data<'life0, 'life1, 'async_trait>( ctxt: &'life0 mut Self::Context, field: DataField<'v, 'life1> ) -> Pin<Box<dyn Future<Output = ()> + Send + 'async_trait>>where Self: 'async_trait, 'v: 'async_trait, 'life0: 'async_trait, 'life1: 'async_trait,

source§

fn finalize(this: Self::Context) -> Result<'v, Self>

source§

impl<'v, T: FromForm<'v>> FromForm<'v> for Option<T>

§

type Context = <T as FromForm<'v>>::Context

source§

fn init(opts: Options) -> Self::Context

source§

fn push_value(ctxt: &mut Self::Context, field: ValueField<'v>)

source§

fn push_data<'life0, 'life1, 'async_trait>( ctxt: &'life0 mut Self::Context, field: DataField<'v, 'life1> ) -> Pin<Box<dyn Future<Output = ()> + Send + 'async_trait>>where Self: 'async_trait, 'v: 'async_trait, 'life0: 'async_trait, 'life1: 'async_trait,

source§

fn finalize(this: Self::Context) -> Result<'v, Self>

Implementors§

source§

impl<'v, T: FromForm<'v>> FromForm<'v> for Contextual<'v, T>

§

type Context = (<T as FromForm<'v>>::Context, Context<'v>)

source§

impl<'v, T: FromForm<'v>> FromForm<'v> for Lenient<T>

§

type Context = <T as FromForm<'v>>::Context

source§

impl<'v, T: FromForm<'v>> FromForm<'v> for Strict<T>

§

type Context = <T as FromForm<'v>>::Context

source§

impl<'v, T: FromForm<'v>> FromForm<'v> for Result<'v, T>

§

type Context = <T as FromForm<'v>>::Context

source§

impl<'v, T: FromFormField<'v>> FromForm<'v> for T

§

type Context = FromFieldContext<'v, T>