Complete forms documentation. Improve 'validate'.

* Add a `msg!()` macro to easily change a field validation message.
  * Allow a field to refer to itself via `self.field`.
  * Improve the various field validation traits.
This commit is contained in:
Sergio Benitez 2021-03-03 21:59:11 -08:00
parent 78e2f8a3c9
commit 398a044eb0
14 changed files with 2241 additions and 1000 deletions

View File

@ -445,4 +445,5 @@ macro_rules! json {
};
}
#[doc(inline)]
pub use json;

View File

@ -1,7 +1,10 @@
use devise::{*, ext::{TypeExt, SpanDiagnosticExt}};
use syn::visit_mut::VisitMut;
use syn::visit::Visit;
use crate::exports::*;
use crate::proc_macro2::Span;
use crate::proc_macro2::{Span, TokenStream, TokenTree};
use crate::name::Name;
pub struct FormField {
@ -86,62 +89,139 @@ impl FieldExt for Field<'_> {
fn name_view(&self) -> Result<syn::Expr> {
let field_name = self.field_name()?;
Ok(syn::parse_quote!(#_form::NameBuf::from((__c.__parent, #field_name))))
let name_view = quote_spanned! { self.span() =>
#_form::NameBuf::from((__c.__parent, #field_name))
};
Ok(syn::parse2(name_view).unwrap())
}
}
struct RecordMemberAccesses(Vec<syn::Member>);
impl<'a> Visit<'a> for RecordMemberAccesses {
fn visit_expr_field(&mut self, i: &syn::ExprField) {
if let syn::Expr::Path(e) = &*i.base {
if e.path.is_ident("self") {
self.0.push(i.member.clone());
}
}
syn::visit::visit_expr_field(self, i);
}
}
struct ValidationMutator<'a> {
field: &'a syn::Ident,
parent: &'a syn::Ident,
local: bool,
visited: bool,
}
impl ValidationMutator<'_> {
fn visit_token_stream(&mut self, tt: TokenStream) -> TokenStream {
use quote::{ToTokens, TokenStreamExt};
use TokenTree::*;
let mut iter = tt.into_iter();
let mut stream = TokenStream::new();
while let Some(tt) = iter.next() {
match tt {
Ident(s3lf) if s3lf == "self" => {
match (iter.next(), iter.next()) {
(Some(Punct(p)), Some(Ident(i))) if p.as_char() == '.' => {
let field = syn::parse_quote!(#s3lf #p #i);
let mut expr = syn::Expr::Field(field);
self.visit_expr_mut(&mut expr);
expr.to_tokens(&mut stream);
},
(tt1, tt2) => stream.append_all(&[Some(Ident(s3lf)), tt1, tt2]),
}
},
TokenTree::Group(group) => {
let tt = self.visit_token_stream(group.stream());
let mut new = proc_macro2::Group::new(group.delimiter(), tt);
new.set_span(group.span());
let group = TokenTree::Group(new);
stream.append(group);
}
tt => stream.append(tt),
}
}
stream
}
}
impl VisitMut for ValidationMutator<'_> {
fn visit_expr_call_mut(&mut self, call: &mut syn::ExprCall) {
syn::visit_mut::visit_expr_call_mut(self, call);
// Only modify the first call we see.
if self.visited { return; }
let (parent, field) = (self.parent, self.field);
let form_field = match self.local {
true => syn::parse2(quote_spanned!(field.span() => &#field)).unwrap(),
false => syn::parse2(quote_spanned!(field.span() => &#parent.#field)).unwrap(),
};
call.args.insert(0, form_field);
self.visited = true;
}
fn visit_ident_mut(&mut self, i: &mut syn::Ident) {
if !self.local && i == "self" {
*i = self.parent.clone();
}
}
fn visit_macro_mut(&mut self, mac: &mut syn::Macro) {
mac.tokens = self.visit_token_stream(mac.tokens.clone());
syn::visit_mut::visit_macro_mut(self, mac);
}
fn visit_expr_mut(&mut self, i: &mut syn::Expr) {
// If this is a local, replace accesses of `self.field` with `field`.
if let syn::Expr::Field(e) = i {
if let syn::Expr::Path(e) = &*e.base {
if e.path.is_ident("self") && self.local {
let new_expr = &self.field;
*i = syn::parse_quote!(#new_expr);
}
}
}
return syn::visit_mut::visit_expr_mut(self, i);
}
}
pub fn validators<'v>(
field: Field<'v>,
out: &'v syn::Ident,
parent: &'v syn::Ident, // field ident (if local) or form ident (if !local)
local: bool,
) -> Result<impl Iterator<Item = syn::Expr> + 'v> {
use syn::visit_mut::VisitMut;
struct ValidationMutator<'a> {
field: syn::Expr,
self_expr: syn::Expr,
form: &'a syn::Ident,
visited: bool,
rec: bool,
}
impl<'a> ValidationMutator<'a> {
fn new(field: &'a syn::Ident, form: &'a syn::Ident) -> Self {
let self_expr = syn::parse_quote!(&#form.#field);
let field = syn::parse_quote!(&#field);
ValidationMutator { field, self_expr, form, visited: false, rec: false }
}
}
impl VisitMut for ValidationMutator<'_> {
fn visit_expr_call_mut(&mut self, call: &mut syn::ExprCall) {
syn::visit_mut::visit_expr_call_mut(self, call);
let ident = if self.rec { &self.self_expr } else { &self.field };
if !self.visited {
call.args.insert(0, ident.clone());
self.visited = true;
}
}
fn visit_ident_mut(&mut self, i: &mut syn::Ident) {
if i == "self" {
*i = self.form.clone();
self.rec = true;
}
syn::visit_mut::visit_ident_mut(self, i);
}
}
Ok(FieldAttr::from_attrs(FieldAttr::NAME, &field.attrs)?
.into_iter()
.filter_map(|a| a.validate)
.map(move |mut expr| {
let mut mutator = ValidationMutator::new(field.ident(), out);
mutator.visit_expr_mut(&mut expr);
(expr, mutator.rec)
.map(move |expr| {
let mut members = RecordMemberAccesses(vec![]);
members.visit_expr(&expr);
let field_ident = field.ident();
let is_local_validation = members.0.iter()
.all(|member| match member {
syn::Member::Named(i) => i == field_ident,
_ => false
});
(expr, is_local_validation)
})
.filter(move |(_, global)| local != *global)
.map(|(e, _)| e))
.filter(move |(_, is_local)| *is_local == local)
.map(move |(mut expr, _)| {
let field = field.ident();
let mut v = ValidationMutator { parent, local, field, visited: false };
v.visit_expr_mut(&mut expr);
expr
}))
}

View File

@ -51,6 +51,7 @@ fn context_type(input: Input<'_>) -> (TokenStream, Option<syn::WhereClause>) {
pub fn derive_from_form(input: proc_macro::TokenStream) -> TokenStream {
DeriveGenerator::build_for(input, quote!(impl<'__f> #_form::FromForm<'__f>))
// NOTE: If support is widened, fix `FieldExt::ident()` `expect()`.
.support(Support::NamedStruct | Support::Lifetime | Support::Type)
.replace_generic(0, 0)
.type_bound(quote!(#_form::FromForm<'__f> + '__f))
@ -189,13 +190,6 @@ pub fn derive_from_form(input: proc_macro::TokenStream) -> TokenStream {
.split2();
Ok(quote_spanned! { fields.span() =>
// if __c.__parent.is_none() {
// let _e = #_form::Error::from(#_form::ErrorKind::Missing)
// .with_entity(#_form::Entity::Form);
//
// return #_Err(_e.into());
// }
#(let #ident = match #finalize_field {
#_ok(#ident) => #_some(#ident),
#_err(_e) => { __c.__errors.extend(_e); #_none }
@ -231,7 +225,6 @@ pub fn derive_from_form(input: proc_macro::TokenStream) -> TokenStream {
.unwrap_or_else(|| <#ty as #_form::FromForm<'__f>>::default()
.ok_or_else(|| #_form::ErrorKind::Missing.into())
)
// <#ty as #_form::FromForm<'__f>>::finalize(__c.#ident)
.and_then(|#ident| {
let mut _es = #_form::Errors::new();
#(if let #_err(_e) = #validator { _es.extend(_e); })*

File diff suppressed because it is too large Load Diff

View File

@ -3,52 +3,225 @@ use crate::form::error::{Error, ErrorKind, Entity};
use crate::http::{ContentType, RawStr};
use crate::{Request, Data};
/// A form field with a string value.
///
/// Rocket preprocesses all form fields into either [`ValueField`]s or
/// [`DataField`]s. All fields from url-encoded forms, and fields without
/// Content-Types from multipart forms, are preprocessed as a `ValueField`.
#[derive(Debug, Clone)]
pub struct ValueField<'r> {
/// The (decoded) name of the form field.
pub name: NameView<'r>,
/// The (decoded) value of the form field.
pub value: &'r str,
}
/// A multipart form field with an underlying data stream.
///
/// Rocket preprocesses all form fields into either [`ValueField`]s or
/// [`DataField`]s. Multipart form fields with a `Content-Type` are preprocessed
/// as a `DataField`. The underlying data is _not_ read into memory, but
/// instead, streamable from the contained [`Data`] structure.
pub struct DataField<'r, 'i> {
/// The (decoded) name of the form field.
pub name: NameView<'r>,
/// The form field's sanitized file name.
///
/// ## Santization
///
/// A "sanitized" file name is a non-empty string, stripped of its file
/// exntesion, which does not contain any path delimiters (`/` or `\`), is
/// not a hidden (`.`) or `*`-prefixed name, and does not contain special
/// characters (`:`, `>`, or `<`). If the submitted file name matches any of
/// these properties or none was submitted, `file_name` will be `None`.
pub file_name: Option<&'r str>,
/// The form field's Content-Type, as submitted, which may or may not
/// reflect on `data`.
pub content_type: ContentType,
/// The request in which the form field was submitted.
pub request: &'r Request<'i>,
/// The raw data stream.
pub data: Data,
}
impl<'v> ValueField<'v> {
/// `raw` must already be URL-decoded. This is weird.
/// Parse a field string, where both the key and value are assumed to be
/// URL-decoded while preserving the `=` delimiter, into a `ValueField`.
///
/// This implements 3.2, 3.3 of [section 5.1 of the WHATWG living standard].
///
/// [section 5.1 of the WHATWG living standard]: https://url.spec.whatwg.org/#urlencoded-parsing
///
/// # Example
///
/// ```rust
/// use rocket::form::ValueField;
///
/// let parsed = ValueField::parse("a cat=an A+ pet");
/// assert_eq!(parsed.name, "a cat");
/// assert_eq!(parsed.value, "an A+ pet");
///
/// let parsed = ValueField::parse("a cat is an A+ pet");
/// assert_eq!(parsed.name, "a cat is an A+ pet");
/// assert_eq!(parsed.value, "");
///
/// let parsed = ValueField::parse("cat.food=yum?");
/// assert_eq!(parsed.name, "cat");
/// assert_eq!(parsed.name.source(), "cat.food");
/// assert_eq!(parsed.value, "yum?");
/// ```
pub fn parse(field: &'v str) -> Self {
// WHATWG URL Living Standard 5.1 steps 3.2, 3.3.
let (name, val) = RawStr::new(field).split_at_byte(b'=');
ValueField::from((name.as_str(), val.as_str()))
}
/// Create a `ValueField` from a value, which is assumed to be URL-decoded.
/// The field `name` will be empty.
///
/// This is equivalent to `ValueField::from(("", value))`. To create a
/// `ValueField` from both a `name` and a `value`, use
/// `ValueField::from((name, value))`.
///
/// # Example
///
/// ```rust
/// use rocket::form::ValueField;
///
/// let parsed = ValueField::from_value("A+=kitten");
/// assert_eq!(parsed.name, "");
/// assert_eq!(parsed.value, "A+=kitten");
/// ```
pub fn from_value(value: &'v str) -> Self {
ValueField::from(("", value))
}
/// Shift the `name` of `self` and return `self` with the shfited `name`.
///
/// See [`NameView::shift()`] for the details on name "shifting".
///
/// # Example
///
/// ```rust
/// use rocket::form::ValueField;
///
/// let parsed = ValueField::parse("cat.food=yum?");
/// assert_eq!(parsed.name, "cat");
/// assert_eq!(parsed.name.source(), "cat.food");
/// assert_eq!(parsed.name.key_lossy(), "cat");
///
/// let shifted = parsed.shift();
/// assert_eq!(shifted.name, "cat.food");
/// assert_eq!(shifted.name.key_lossy(), "food");
/// ```
pub fn shift(mut self) -> Self {
self.name.shift();
self
}
/// Creates a complete unexpected value field [`Error`] from `self`.
///
/// The error will have the following properties:
/// * `kind`: [`ErrorKind::Unexpected`]
/// * `name`: [`self.name.source()`](NameView::source())
/// * `value`: [`self.value`](ValueField::value)
/// * `entity`: [`Entity::ValueField`]
///
/// # Example
///
/// ```rust
/// use rocket::form::ValueField;
/// use rocket::form::error::{ErrorKind, Entity};
///
/// let field = ValueField::parse("cat.food=yum?");
/// let error = field.unexpected();
///
/// assert_eq!(error.name.as_ref().unwrap(), "cat.food");
/// assert_eq!(error.value.as_ref().unwrap(), "yum?");
/// assert_eq!(error.kind, ErrorKind::Unexpected);
/// assert_eq!(error.entity, Entity::ValueField);
/// ```
pub fn unexpected(&self) -> Error<'v> {
Error::from(ErrorKind::Unexpected)
.with_name(NameView::new(self.name.source()))
.with_name(self.name.source())
.with_value(self.value)
.with_entity(Entity::ValueField)
}
/// Creates a complete mising value field [`Error`] from `self`.
///
/// The error will have the following properties:
/// * `kind`: [`ErrorKind::Missing`]
/// * `name`: [`self.name.source()`](NameView::source())
/// * `value`: [`self.value`](ValueField::value)
/// * `entity`: [`Entity::ValueField`]
///
/// # Example
///
/// ```rust
/// use rocket::form::ValueField;
/// use rocket::form::error::{ErrorKind, Entity};
///
/// let field = ValueField::parse("cat.food=yum?");
/// let error = field.missing();
///
/// assert_eq!(error.name.as_ref().unwrap(), "cat.food");
/// assert_eq!(error.value.as_ref().unwrap(), "yum?");
/// assert_eq!(error.kind, ErrorKind::Missing);
/// assert_eq!(error.entity, Entity::ValueField);
/// ```
pub fn missing(&self) -> Error<'v> {
Error::from(ErrorKind::Missing)
.with_name(NameView::new(self.name.source()))
.with_name(self.name.source())
.with_value(self.value)
.with_entity(Entity::ValueField)
}
}
impl<'v> DataField<'v, '_> {
/// Shift the `name` of `self` and return `self` with the shfited `name`.
///
/// This is identical to [`ValueField::shift()`] but for `DataFields`s. See
/// [`NameView::shift()`] for the details on name "shifting".
///
/// # Example
///
/// ```rust
/// use rocket::form::DataField;
///
/// fn push_data(field: DataField<'_, '_>) {
/// let shifted = field.shift();
/// }
/// ```
pub fn shift(mut self) -> Self {
self.name.shift();
self
}
/// Creates a complete unexpected data field [`Error`] from `self`.
///
/// The error will have the following properties:
/// * `kind`: [`ErrorKind::Unexpected`]
/// * `name`: [`self.name.source()`](NameView::source())
/// * `value`: `None`
/// * `entity`: [`Entity::DataField`]
///
/// # Example
///
/// ```rust
/// use rocket::form::DataField;
///
/// fn push_data(field: DataField<'_, '_>) {
/// let error = field.unexpected();
/// }
/// ```
pub fn unexpected(&self) -> Error<'v> {
Error::from(ErrorKind::Unexpected)
.with_name(self.name.source())
.with_entity(Entity::DataField)
}
}
impl<'a> From<(&'a str, &'a str)> for ValueField<'a> {
fn from((name, value): (&'a str, &'a str)) -> Self {
ValueField { name: NameView::new(name), value }
@ -60,16 +233,3 @@ impl<'a, 'b> PartialEq<ValueField<'b>> for ValueField<'a> {
self.name == other.name && self.value == other.value
}
}
impl<'v> DataField<'v, '_> {
pub fn shift(mut self) -> Self {
self.name.shift();
self
}
pub fn unexpected(&self) -> Error<'v> {
Error::from(ErrorKind::Unexpected)
.with_name(self.name)
.with_entity(Entity::DataField)
}
}

View File

@ -9,7 +9,7 @@ use crate::form::prelude::*;
/// A data guard for [`FromForm`] types.
///
/// This type implements the [`FromData`] trait. It provides a generic means to
/// parse arbitrary structures from incoming form data of any kind.
/// parse arbitrary structures from incoming form data.
///
/// See the [forms guide](https://rocket.rs/master/guide/requests#forms) for
/// general form support documentation.
@ -57,19 +57,44 @@ use crate::form::prelude::*;
///
/// ## Data Limits
///
/// The default size limit for incoming form data is 32KiB. Setting a limit
/// protects your application from denial of service (DOS) attacks and from
/// resource exhaustion through high memory consumption. The limit can be
/// modified by setting the `limits.form` configuration parameter. For instance,
/// to increase the forms limit to 512KiB for all environments, you may add the
/// following to your `Rocket.toml`:
/// ### URL-Encoded Forms
///
/// The `form` limit specifies the data limit for an entire url-encoded form
/// data. It defaults to 32KiB. URL-encoded form data is percent-decoded, stored
/// in-memory, and parsed into [`ValueField`]s. If the incoming data exceeds
/// this limit, the `Form` data guard fails without attempting to parse fields
/// with a `413: Payload Too Large` error.
///
/// ### Multipart Forms
///
/// The `data-form` limit specifies the data limit for an entire multipart form
/// data stream. It defaults to 2MiB. Multipart data is streamed, and form
/// fields are processed into [`DataField`]s or [`ValueField`]s as they arrive.
/// If the commulative data received while streaming exceeds the limit, parsing
/// is aborted, an error is created and pushed via [`FromForm::push_error()`],
/// and the form is finalized.
///
/// ### Individual Fields
///
/// Individual fields _may_ have data limits as well. The type of the field
/// determines whether there is a data limit. For instance, the `&str` type
/// imposes the `string` data limit. Consult the type's documentation or
/// [`FromFormField`] for details.
///
/// ### Changing Limits
///
/// To change data limits, set the `limits.form` and/or `limits.data-form`
/// configuration parameters. For instance, to increase the URL-encoded forms
/// limit to 128KiB for all environments, you might add the following to your
/// `Rocket.toml`:
///
/// ```toml
/// [global.limits]
/// form = 524288
/// form = 128KiB
/// ```
///
/// See the [`Limits`](crate::data::Limits) docs for more.
/// See the [`Limits`](crate::data::Limits) and [`config`](crate::config) docs
/// for more.
#[derive(Debug)]
pub struct Form<T>(T);

View File

@ -532,7 +532,7 @@ impl<'v, K, V> MapContext<'v, K, V>
}
_ => {
let error = Error::from(ErrorKind::Missing)
.with_entity(Entity::Indices)
.with_entity(Entity::Key)
.with_name(name);
self.errors.push(error);
@ -566,14 +566,14 @@ impl<'v, K, V> MapContext<'v, K, V>
.zip(key_map.values().map(|(_, name)| name))
.filter_map(|(ctxt, name)| match K::finalize(ctxt) {
Ok(value) => Some(value),
Err(e) => { errors.borrow_mut().extend(e.with_name(*name)); None }
Err(e) => { errors.borrow_mut().extend(e.with_name(*name)); None },
});
let values = values.into_iter()
.zip(key_map.values().map(|(_, name)| name))
.filter_map(|(ctxt, name)| match V::finalize(ctxt) {
Ok(value) => Some(value),
Err(e) => { errors.borrow_mut().extend(e.with_name(*name)); None }
Err(e) => { errors.borrow_mut().extend(e.with_name(*name)); None },
});
let map: T = keys.zip(values).collect();

View File

@ -61,6 +61,7 @@ use crate::form::prelude::*;
///
/// A value is validated successfully if the `from_str` method for the given
/// type returns successfully. Only accepts form _values_, not binary data.
/// No data limit applies.
///
/// * **`bool`**
///
@ -69,17 +70,21 @@ use crate::form::prelude::*;
/// `"off"`, `"no"`, or `"false"`. Defaults to `false` otherwise. Only
/// accepts form _values_, not binary data.
///
/// **No 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.
/// [`TempFile`] for details and data limits.
///
/// * **[`Capped<TempFile>`], [`Capped<String>`]**
/// * **[`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
@ -91,6 +96,8 @@ use crate::form::prelude::*;
/// This is the `"date"` HTML input type. Only accepts form _values_, not
/// binary data.
///
/// **No data limit applies.**
///
/// * **[`time::PrimitiveDateTime`]**
///
/// Parses a date in `%FT%R` or `%FT%T` format, that is, `YYYY-MM-DDTHH:MM`
@ -98,12 +105,16 @@ use crate::form::prelude::*;
/// without support for the millisecond variant. Only accepts form _values_,
/// not binary data.
///
/// **No 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 data limit applies.**
///
/// [`TempFile`]: crate::data::TempFile
///
/// # Implementing
@ -240,7 +251,7 @@ pub struct FromFieldContext<'v, T: FromFormField<'v>> {
}
impl<'v, T: FromFormField<'v>> FromFieldContext<'v, T> {
fn can_push(&mut self) -> bool {
fn should_push(&mut self) -> bool {
self.pushes += 1;
self.value.is_none()
}
@ -273,14 +284,14 @@ impl<'v, T: FromFormField<'v>> FromForm<'v> for T {
}
fn push_value(ctxt: &mut Self::Context, field: ValueField<'v>) {
if ctxt.can_push() {
if ctxt.should_push() {
ctxt.field_value = Some(field.value);
ctxt.push(field.name, Self::from_value(field))
}
}
async fn push_data(ctxt: &mut FromFieldContext<'v, T>, field: DataField<'v, '_>) {
if ctxt.can_push() {
if ctxt.should_push() {
ctxt.push(field.name, Self::from_data(field).await);
}
}

View File

@ -1,5 +1,8 @@
//! Parsing and validation of HTTP forms and fields.
//!
//! See the [forms guide](https://rocket.rs/master/guide/requests#forms) for
//! general form support documentation.
//!
//! # Field Wire Format
//!
//! Rocket's field wire format is a flexible, non-self-descriptive, text-based
@ -17,13 +20,13 @@
//!
//! indices := index (':' index)*
//!
//! index := STRING except ':'
//! index := STRING except ':', ']'
//!
//! value := STRING
//! ```
//!
//! Each field name consists of any number of `key`s and at most one `value`.
//! Keys are delimited by `[]` or `.`. A `key` consists of indices delimited by
//! Keys are delimited by `[` or `.`. A `key` consists of indices delimited by
//! `:`.
//!
//! The meaning of a key or index is type-dependent, hence the format is
@ -51,7 +54,7 @@
//! 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`]s. `ValueField` field names and values are percent-decoded.
//!
//! # Data Limits
//!
@ -68,372 +71,379 @@
//! 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) docs for more.
//! See the [`Limits`](crate::data::Limits) and [`Form#data-limits`] docs for
//! more.
//!
/// # Examples
///
/// The following examples use `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 Value (`T: FormFormValue`)
///
/// The simplest example parses a single value of type `T` from a string with an
/// optional default value: this is `impl<T: FromFormValue> FromForm for T`:
///
/// 1. **Initialization.** The context stores parsing options and an `Option`
/// of `Result<T, T::Error>` for storing the `result` of parsing `T`, which is
/// initially set to `None`.
///
/// ```rust,ignore
/// struct Context<T> {
/// opts: FormOptions,
/// result: Option<Result<T, T::Error>>,
/// }
/// ```
///
/// 2. **Push.** The field is treated as a string. If `context.result` is `None`,
/// `T` is parsed from `field`, and the result is stored in `context.result`.
///
/// ```rust,ignore
/// fn push(this: &mut Self::Context, field: FormField<'v>) {
/// if this.result.is_none() {
/// this.result = Some(Self::from_value(field));
/// }
/// }
/// ```
///
/// 3. **Finalization.** If `context.result` is `None` and `T` has a default,
/// the default is returned; otherwise a `Missing` error is returned. If
/// `context.result` is `Some(v)`, the result `v` is returned.
///
/// ```rust,ignore
/// fn finalize(this: Self::Context) -> Result<Self, Self::Error> {
/// match this.result {
/// Some(value) => Ok(value),
/// None => match <T as FromFormValue>::default() {
/// Some(default) => Ok(default),
/// None => Err(Error::Missing)
/// }
/// }
/// }
/// ```
///
/// This implementation is complete, barring checking for duplicate pushes when
/// paring is requested as `strict`.
///
/// ## Maps w/named Fields (`struct`)
///
/// A `struct` with named fields parses values of multiple types, indexed by the
/// name of its fields:
///
/// ```rust,ignore
/// struct Dog { name: String, barks: bool, friends: Vec<Cat>, }
/// struct Cat { name: String, meows: bool }
/// ```
///
/// Candidates for parsing into a `Dog` include:
///
/// * `name=Fido&barks=0`
///
/// `Dog { "Fido", false }`
///
/// * `name=Fido&barks=1&friends[0]name=Sally&friends[0]meows=0`
/// `name=Fido&barks=1&friends[0].name=Sally&friends[0].meows=0`
/// `name=Fido&barks=1&friends.0.name=Sally&friends.0.meows=0`
///
/// `Dog { "Fido", true, vec![Cat { "Sally", false }] }`
///
/// Parsers for structs are code-generated to proceed as follows:
///
/// 1. **Initialization.** The context stores parsing options, a `T::Context`
/// for each field of type `T`, and a vector called `extra`.
///
/// ```rust,ignore
/// struct Context<'v> {
/// opts: FormOptions,
/// field_a: A::Context,
/// field_b: B::Context,
/// /* ... */
/// extra: Vec<FormField<'v>>
/// }
/// ```
///
/// 2. **Push.** The index of the first key is compared to known field names.
/// If none matches, the index is added to `extra`. Otherwise the key is
/// stripped from the field, and the remaining field is pushed to `T`.
///
/// ```rust,ignore
/// fn push(this: &mut Self::Context, field: FormField<'v>) {
/// match field.key() {
/// "field_a" => A::push(&mut this.field_a, field.next()),
/// "field_b" => B::push(&mut this.field_b, field.next()),
/// /* ... */
/// _ => this.extra.push(field)
/// }
/// }
/// ```
///
/// 3. **Finalization.** Every context is finalized; errors and `Ok` values
/// are collected. If parsing is strict and extras is non-empty, an error
/// added to the collection of errors. If there are no errors, all `Ok`
/// values are used to create the `struct`, and the created struct is
/// returned. Otherwise, `Err(errors)` is returned.
///
/// ```rust,ignore
/// fn finalize(mut this: Self::Context) -> Result<Self, Self::Error> {
/// let mut errors = vec![];
///
/// let field_a = A::finalize(&mut this.field_a)
/// .map_err(|e| errors.push(e))
/// .map(Some).unwrap_or(None);
///
/// let field_b = B::finblize(&mut this.field_b)
/// .map_err(|e| errors.push(e))
/// .map(Some).unwrap_or(None);
///
/// /* .. */
///
/// if !errors.is_empty() {
/// return Err(Values(errors));
/// } else if this.opts.is_strict() && !this.extra.is_empty() {
/// return Err(Extra(this.extra));
/// } else {
/// // NOTE: All unwraps will succeed since `errors.is_empty()`.
/// Struct {
/// field_a: field_a.unwrap(),
/// field_b: field_b.unwrap(),
/// /* .. */
/// }
/// }
/// }
/// ```
///
/// ## Sequences: (`Vec<T: FromForm>`)
///
/// A `Vec<T: FromForm>` invokes `T`'s push-parser on every push, adding instances
/// of `T` to an internal vector. The instance of `T` whose parser is invoked
/// depends on the index of the first key:
///
/// * if it is the first push, the index differs from the previous, or there is no
/// index, a new `T::Context` is `init`ialized and added to the internal vector
/// * if the index matches the previously seen index, the last initialized
/// `T::Context` is `push`ed to.
///
/// For instance, the sequentially pushed values `=1`, `=2`, and `=3` for a
/// `Vec<usize>` (or any other integer) is expected to parse as `vec![1, 2, 3]`. The
/// same is true for `[]=1&[]=2&[]=3`. In the first example (`=1&..`), the fields
/// passed to `Vec`'s push-parser (`=1`, ..) have no key and thus no index. In the
/// second example (`[]=1&..`), the key is `[]` (`[]=1`) without an index. In both
/// cases, there is no index. The `Vec` parser takes this to mean that a _new_ `T`
/// should be parsed using the field's value.
///
/// If, instead, the index was non-empty and equal to the index of the field in the
/// _previous_ push, `Vec` pushes the value to the parser of the previously parsed
/// `T`: `[]=1&[0]=2&[0]=3` results in `vec![1, 2]` and `[0]=1&[0]=2&[]=3` results
/// in `vec![1, 3]` (see [`FromFormValue`]).
///
/// This generalizes. Consider a `Vec<Vec<usize>>` named `x`, so `x` and an
/// optional `=` are stripped before being passed to `Vec`'s push-parser:
///
/// * `x=1&x=2&x=3` parses as `vec![vec![1], vec![2], vec![3]]`
///
/// Every push (`1`, `2`, `3`) has no key, thus no index: a new `T` (here,
/// `Vec<usize>`) is thus initialized for every `push()` and passed the
/// value (here, `1`, `2`, and `3`). Each of these `push`es proceeds
/// recursively: every push again has no key, thus no index, so a new `T` is
/// initialized for every push (now a `usize`), which finally parse as
/// integers `1`, `2`, and `3`.
///
/// Note: `x=1&x=2&x=3` _also_ can also parse as `vec![1, 2, 3]` when viewed
/// as a `Vec<usize>`; this is the non-self-descriptive part of the format.
///
/// * `x[]=1&x[]=2&x[]=3` parses as `vec![vec![1], vec![2], vec![3]]`
///
/// This proceeds nearly identically to the previous example, with the exception
/// that the top-level `Vec` sees the values `[]=1`, `[]=2`, and `[]=3`.
///
/// * `x[0]=1&x[0]=2&x[]=3` parses as `vec![vec![1, 2], vec![3]]`
///
/// The top-level `Vec` sees the values `[0]=1`, `[0]=2`, and `[]=3`. The first
/// value results in a new `Vec<usize>` being initialized, as before, which is
/// pushed a `1`. The second value has the same index as the first, `0`, and so
/// `2` is pushed to the previous `T`, the `Vec` which contains the `1`.
/// Finally, the third value has no index, so a new `Vec<usize>` is initialized
/// and pushed a `3`.
///
/// * `x[0]=1&x[0]=2&x[]=3&x[]=4` parses as `vec![vec![1, 2], vec![3], vec![4]]`
/// * `x[0]=1&x[0]=2&x[1]=3&x[1]=4` parses as `vec![vec![1, 2], vec![3, 4]]`
///
/// The indexing kind `[]` is purely by convention: the first two examples are
/// equivalent to `x.=1&x.=2`, while the third to `x.0=1&x.0=&x.=3`.
///
/// The parser proceeds as follows:
///
/// 1. **Initialization.** The context stores parsing options, the
/// `last_index` encountered in a `push`, an `Option` of a `T::Context` for
/// the `current` value being parsed, a `Vec<T::Errors>` of `errors`, and
/// finally a `Vec<T>` of already parsed `items`.
///
/// ```rust,ignore
/// struct VecContext<'v, T: FromForm<'v>> {
/// opts: FormOptions,
/// last_index: Index<'v>,
/// current: Option<T::Context>,
/// errors: Vec<T::Error>,
/// items: Vec<T>
/// }
/// ```
///
/// 2. **Push.** The index of the first key is compared against `last_index`.
/// If it differs, a new context for `T` is created and the previous is
/// finalized. The `Ok` result from finalization is stored in `items` and
/// the `Err` in `errors`. Otherwise the `index` is the same, the `current`
/// context is retrieved, and the field stripped of the current key is
/// pushed to `T`. `last_index` is updated.
///
/// ```rust,ignore
/// fn push(this: &mut Self::Context, field: FormField<'v>) {
/// if this.last_index != field.index() {
/// this.shift(); // finalize `current`, add to `items`, `errors`
/// let mut context = T::init(this.opts);
/// T::push(&mut context, field.next());
/// this.current = Some(context);
/// } else {
/// let context = this.current.as_mut();
/// T::push(context, field.next())
/// }
///
/// this.last_index = field.index();
/// }
/// ```
///
/// 3. **Finalization.** Any `current` context is finalized, storing the `Ok`
/// or `Err` as before. `Ok(items)` is returned if `errors` is empty,
/// otherwise `Err(errors)` is returned.
///
/// ```rust,ignore
/// fn finalize(mut this: Self::Context) -> Result<Self, Self::Error> {
/// this.shift(); // finalizes `current`, as before.
/// match this.errors.is_empty() {
/// true => Ok(this.items),
/// false => Err(this.errors)
/// }
/// }
/// ```
///
/// ## Arbitrary Maps (`HashMap<K: FromForm, V: FromForm>`)
///
/// A `HashMap<K, V>` can be parsed from keys with one index or, for composite
/// key values, such as structures or sequences, multiple indices. We begin with
/// a discussion of the simpler case: non-composite keys.
///
/// ### Non-Composite Keys
///
/// A non-composite value can be described by a single field with no indices.
/// Strings and integers are examples of non-composite values. The push-parser
/// for `HashMap<K, V>` for a non-composite `K` uses the index of the first key
/// as the value of `K`; the remainder of the field is pushed to `V`'s parser:
///
/// 1. **Initialization.** The context stores a column-based representation of
/// `keys` and `values`, a `key_map` from a string key to the column index,
/// an `errors` vector for storing errors as they arise, and the parsing
/// options.
///
/// ```rust,ignore
/// struct MapContext<'v, K: FromForm<'v>, V: FromForm<'v>> {
/// opts: FormOptions,
/// key_map: HashMap<&'v str, usize>,
/// keys: Vec<K::Context>,
/// values: Vec<V::Context>,
/// errors: Vec<MapError<'v, K::Error, V::Error>>,
/// }
/// ```
///
/// 2. **Push.** The `key_map` index for the key associated with the index of
/// the first key in the field is retrieved. If such a key has not yet been
/// seen, a new key and value context are created, the key is pushed to
/// `K`'s parser, and the field minus the first key is pushed to `V`'s
/// parser.
///
/// ```rust,ignore
/// fn push(this: &mut Self::Context, field: FormField<'v>) {
/// let key = field.index();
/// let value_context = match this.key_map.get(Key) {
/// Some(i) => &mut this.values[i],
/// None => {
/// let i = this.keys.len();
/// this.key_map.insert(key, i);
/// this.keys.push(K::init(this.opts));
/// this.values.push(V::init(this.opts));
/// K::push(&mut this.keys[i], key.into());
/// &mut this.values[i]
/// }
/// };
///
/// V::push(value_context, field.next());
/// }
/// ```
///
/// 3. **Finalization.** All key and value contexts are finalized; any errors
/// are collected in `errors`. If there are no errors, `keys` and `values`
/// are collected into a `HashMap` and returned. Otherwise, the errors are
/// returned.
///
/// ```rust,ignore
/// fn finalize(mut this: Self::Context) -> Result<Self, Self::Error> {
/// this.finalize_keys();
/// this.finalize_values();
/// if this.errors.is_empty() {
/// Ok(this.keys.into_iter().zip(this.values.into_iter()).collect())
/// } else {
/// Err(this.errors)
/// }
/// }
/// ```
///
/// Examples of forms parseable via this parser are:
///
/// * `x[0].name=Bob&x[0].meows=true`as a `HashMap<usize, Cat>` parses with
/// `0` mapping to `Cat { name: "Bob", meows: true }`
/// * `x[0]name=Bob&x[0]meows=true`as a `HashMap<usize, Cat>` parses just as
/// above.
/// * `x[0]=Bob&x[0]=Sally&x[1]=Craig`as a `HashMap<usize, Vec<String>>`
/// just as `{ 0 => vec!["Bob", "Sally"], 1 => vec!["Craig"] }`.
///
/// A `HashMap<K, V>` can be thought of as a vector of key-value pairs: `Vec<(K,
/// V)` (row-based) or equivalently, as two vectors of keys and values: `Vec<K>`
/// and `Vec<V>` (column-based). The implication is that indexing into a
/// specific key or value requires _two_ indexes: the first to determine whether
/// a key or value is being indexed to, and the second to determine _which_ key
/// or value. The push-parser for maps thus optionally accepts two indexes for a
/// single key to allow piece-by-piece build-up of arbitrary keys and values.
///
/// The parser proceeds as follows:
///
/// 1. **Initialization.** The context stores parsing options, a vector of
/// `key_contexts: Vec<K::Context>`, a vector of `value_contexts:
/// Vec<V::Context>`, a `mapping` from a string index to an integer index
/// into the `contexts`, and a vector of `errors`.
/// 2. **Push.** An index is required; an error is emitted and `push` returns
/// if they field's first key does not contain an index. If the first key
/// contains _one_ index, a new `K::Context` and `V::Context` are created.
/// The key is pushed as the value to `K` and the remaining field as the
/// value to `V`. The key and value are finalized; if both succeed, the key
/// and value are stored in `keys` and `values`; otherwise the error(s) is
/// stored in `errors`.
///
/// If the first keys contains _two_ indices, the first must starts with
/// `k` or `v`, while the `second` is arbitrary. `mapping` is indexed by
/// `second`; the integer is retrieved. If none exists, new contexts are
/// created an added to `{key,value}_contexts`, and their index is mapped
/// to `second` in `mapping`. If the first index is `k`, the field,
/// stripped of the first key, is pushed to the key's context; the same is
/// done for the value's context is the first index is `v`.
/// 3. **Finalization.** Every context is finalized; errors and `Ok` values
/// are collected. TODO: FINISH. Split this into two: one for single-index,
/// another for two-indices.
//! # 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 paring is requested as `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.
// ## Maps w/named Fields (`struct`)
//
// A `struct` with named fields parses values of multiple types, indexed by the
// name of its fields:
//
// ```rust,ignore
// struct Dog { name: String, barks: bool, friends: Vec<Cat>, }
// struct Cat { name: String, meows: bool }
// ```
//
// Candidates for parsing into a `Dog` include:
//
// * `name=Fido&barks=0`
//
// `Dog { "Fido", false }`
//
// * `name=Fido&barks=1&friends[0]name=Sally&friends[0]meows=0`
// `name=Fido&barks=1&friends[0].name=Sally&friends[0].meows=0`
// `name=Fido&barks=1&friends.0.name=Sally&friends.0.meows=0`
//
// `Dog { "Fido", true, vec![Cat { "Sally", false }] }`
//
// Parsers for structs are code-generated to proceed as follows:
//
// 1. **Initialization.** The context stores parsing options, a `T::Context`
// for each field of type `T`, and a vector called `extra`.
//
// ```rust,ignore
// struct Context<'v> {
// opts: FormOptions,
// field_a: A::Context,
// field_b: B::Context,
// /* ... */
// extra: Vec<FormField<'v>>
// }
// ```
//
// 2. **Push.** The index of the first key is compared to known field names.
// If none matches, the index is added to `extra`. Otherwise the key is
// stripped from the field, and the remaining field is pushed to `T`.
//
// ```rust,ignore
// fn push(this: &mut Self::Context, field: FormField<'v>) {
// match field.key() {
// "field_a" => A::push(&mut this.field_a, field.next()),
// "field_b" => B::push(&mut this.field_b, field.next()),
// /* ... */
// _ => this.extra.push(field)
// }
// }
// ```
//
// 3. **Finalization.** Every context is finalized; errors and `Ok` values
// are collected. If parsing is strict and extras is non-empty, an error
// added to the collection of errors. If there are no errors, all `Ok`
// values are used to create the `struct`, and the created struct is
// returned. Otherwise, `Err(errors)` is returned.
//
// ```rust,ignore
// fn finalize(mut this: Self::Context) -> Result<Self, Self::Error> {
// let mut errors = vec![];
//
// let field_a = A::finalize(&mut this.field_a)
// .map_err(|e| errors.push(e))
// .map(Some).unwrap_or(None);
//
// let field_b = B::finblize(&mut this.field_b)
// .map_err(|e| errors.push(e))
// .map(Some).unwrap_or(None);
//
// /* .. */
//
// if !errors.is_empty() {
// return Err(Values(errors));
// } else if this.opts.is_strict() && !this.extra.is_empty() {
// return Err(Extra(this.extra));
// } else {
// // NOTE: All unwraps will succeed since `errors.is_empty()`.
// Struct {
// field_a: field_a.unwrap(),
// field_b: field_b.unwrap(),
// /* .. */
// }
// }
// }
// ```
//
// ## Sequences: (`Vec<T: FromForm>`)
//
// A `Vec<T: FromForm>` invokes `T`'s push-parser on every push, adding instances
// of `T` to an internal vector. The instance of `T` whose parser is invoked
// depends on the index of the first key:
//
// * if it is the first push, the index differs from the previous, or there is no
// index, a new `T::Context` is `init`ialized and added to the internal vector
// * if the index matches the previously seen index, the last initialized
// `T::Context` is `push`ed to.
//
// For instance, the sequentially pushed values `=1`, `=2`, and `=3` for a
// `Vec<usize>` (or any other integer) is expected to parse as `vec![1, 2, 3]`. The
// same is true for `[]=1&[]=2&[]=3`. In the first example (`=1&..`), the fields
// passed to `Vec`'s push-parser (`=1`, ..) have no key and thus no index. In the
// second example (`[]=1&..`), the key is `[]` (`[]=1`) without an index. In both
// cases, there is no index. The `Vec` parser takes this to mean that a _new_ `T`
// should be parsed using the field's value.
//
// If, instead, the index was non-empty and equal to the index of the field in the
// _previous_ push, `Vec` pushes the value to the parser of the previously parsed
// `T`: `[]=1&[0]=2&[0]=3` results in `vec![1, 2]` and `[0]=1&[0]=2&[]=3` results
// in `vec![1, 3]` (see [`FromFormValue`]).
//
// This generalizes. Consider a `Vec<Vec<usize>>` named `x`, so `x` and an
// optional `=` are stripped before being passed to `Vec`'s push-parser:
//
// * `x=1&x=2&x=3` parses as `vec![vec![1], vec![2], vec![3]]`
//
// Every push (`1`, `2`, `3`) has no key, thus no index: a new `T` (here,
// `Vec<usize>`) is thus initialized for every `push()` and passed the
// value (here, `1`, `2`, and `3`). Each of these `push`es proceeds
// recursively: every push again has no key, thus no index, so a new `T` is
// initialized for every push (now a `usize`), which finally parse as
// integers `1`, `2`, and `3`.
//
// Note: `x=1&x=2&x=3` _also_ can also parse as `vec![1, 2, 3]` when viewed
// as a `Vec<usize>`; this is the non-self-descriptive part of the format.
//
// * `x[]=1&x[]=2&x[]=3` parses as `vec![vec![1], vec![2], vec![3]]`
//
// This proceeds nearly identically to the previous example, with the exception
// that the top-level `Vec` sees the values `[]=1`, `[]=2`, and `[]=3`.
//
// * `x[0]=1&x[0]=2&x[]=3` parses as `vec![vec![1, 2], vec![3]]`
//
// The top-level `Vec` sees the values `[0]=1`, `[0]=2`, and `[]=3`. The first
// value results in a new `Vec<usize>` being initialized, as before, which is
// pushed a `1`. The second value has the same index as the first, `0`, and so
// `2` is pushed to the previous `T`, the `Vec` which contains the `1`.
// Finally, the third value has no index, so a new `Vec<usize>` is initialized
// and pushed a `3`.
//
// * `x[0]=1&x[0]=2&x[]=3&x[]=4` parses as `vec![vec![1, 2], vec![3], vec![4]]`
// * `x[0]=1&x[0]=2&x[1]=3&x[1]=4` parses as `vec![vec![1, 2], vec![3, 4]]`
//
// The indexing kind `[]` is purely by convention: the first two examples are
// equivalent to `x.=1&x.=2`, while the third to `x.0=1&x.0=&x.=3`.
//
// The parser proceeds as follows:
//
// 1. **Initialization.** The context stores parsing options, the
// `last_index` encountered in a `push`, an `Option` of a `T::Context` for
// the `current` value being parsed, a `Vec<T::Errors>` of `errors`, and
// finally a `Vec<T>` of already parsed `items`.
//
// ```rust,ignore
// struct VecContext<'v, T: FromForm<'v>> {
// opts: FormOptions,
// last_index: Index<'v>,
// current: Option<T::Context>,
// errors: Vec<T::Error>,
// items: Vec<T>
// }
// ```
//
// 2. **Push.** The index of the first key is compared against `last_index`.
// If it differs, a new context for `T` is created and the previous is
// finalized. The `Ok` result from finalization is stored in `items` and
// the `Err` in `errors`. Otherwise the `index` is the same, the `current`
// context is retrieved, and the field stripped of the current key is
// pushed to `T`. `last_index` is updated.
//
// ```rust,ignore
// fn push(this: &mut Self::Context, field: FormField<'v>) {
// if this.last_index != field.index() {
// this.shift(); // finalize `current`, add to `items`, `errors`
// let mut context = T::init(this.opts);
// T::push(&mut context, field.next());
// this.current = Some(context);
// } else {
// let context = this.current.as_mut();
// T::push(context, field.next())
// }
//
// this.last_index = field.index();
// }
// ```
//
// 3. **Finalization.** Any `current` context is finalized, storing the `Ok`
// or `Err` as before. `Ok(items)` is returned if `errors` is empty,
// otherwise `Err(errors)` is returned.
//
// ```rust,ignore
// fn finalize(mut this: Self::Context) -> Result<Self, Self::Error> {
// this.shift(); // finalizes `current`, as before.
// match this.errors.is_empty() {
// true => Ok(this.items),
// false => Err(this.errors)
// }
// }
// ```
//
// ## Arbitrary Maps (`HashMap<K: FromForm, V: FromForm>`)
//
// A `HashMap<K, V>` can be parsed from keys with one index or, for composite
// key values, such as structures or sequences, multiple indices. We begin with
// a discussion of the simpler case: non-composite keys.
//
// ### Non-Composite Keys
//
// A non-composite value can be described by a single field with no indices.
// Strings and integers are examples of non-composite values. The push-parser
// for `HashMap<K, V>` for a non-composite `K` uses the index of the first key
// as the value of `K`; the remainder of the field is pushed to `V`'s parser:
//
// 1. **Initialization.** The context stores a column-based representation of
// `keys` and `values`, a `key_map` from a string key to the column index,
// an `errors` vector for storing errors as they arise, and the parsing
// options.
//
// ```rust,ignore
// struct MapContext<'v, K: FromForm<'v>, V: FromForm<'v>> {
// opts: FormOptions,
// key_map: HashMap<&'v str, usize>,
// keys: Vec<K::Context>,
// values: Vec<V::Context>,
// errors: Vec<MapError<'v, K::Error, V::Error>>,
// }
// ```
//
// 2. **Push.** The `key_map` index for the key associated with the index of
// the first key in the field is retrieved. If such a key has not yet been
// seen, a new key and value context are created, the key is pushed to
// `K`'s parser, and the field minus the first key is pushed to `V`'s
// parser.
//
// ```rust,ignore
// fn push(this: &mut Self::Context, field: FormField<'v>) {
// let key = field.index();
// let value_context = match this.key_map.get(Key) {
// Some(i) => &mut this.values[i],
// None => {
// let i = this.keys.len();
// this.key_map.insert(key, i);
// this.keys.push(K::init(this.opts));
// this.values.push(V::init(this.opts));
// K::push(&mut this.keys[i], key.into());
// &mut this.values[i]
// }
// };
//
// V::push(value_context, field.next());
// }
// ```
//
// 3. **Finalization.** All key and value contexts are finalized; any errors
// are collected in `errors`. If there are no errors, `keys` and `values`
// are collected into a `HashMap` and returned. Otherwise, the errors are
// returned.
//
// ```rust,ignore
// fn finalize(mut this: Self::Context) -> Result<Self, Self::Error> {
// this.finalize_keys();
// this.finalize_values();
// if this.errors.is_empty() {
// Ok(this.keys.into_iter().zip(this.values.into_iter()).collect())
// } else {
// Err(this.errors)
// }
// }
// ```
//
// Examples of forms parseable via this parser are:
//
// * `x[0].name=Bob&x[0].meows=true`as a `HashMap<usize, Cat>` parses with
// `0` mapping to `Cat { name: "Bob", meows: true }`
// * `x[0]name=Bob&x[0]meows=true`as a `HashMap<usize, Cat>` parses just as
// above.
// * `x[0]=Bob&x[0]=Sally&x[1]=Craig`as a `HashMap<usize, Vec<String>>`
// just as `{ 0 => vec!["Bob", "Sally"], 1 => vec!["Craig"] }`.
//
// A `HashMap<K, V>` can be thought of as a vector of key-value pairs: `Vec<(K,
// V)` (row-based) or equivalently, as two vectors of keys and values: `Vec<K>`
// and `Vec<V>` (column-based). The implication is that indexing into a
// specific key or value requires _two_ indexes: the first to determine whether
// a key or value is being indexed to, and the second to determine _which_ key
// or value. The push-parser for maps thus optionally accepts two indexes for a
// single key to allow piece-by-piece build-up of arbitrary keys and values.
//
// The parser proceeds as follows:
//
// 1. **Initialization.** The context stores parsing options, a vector of
// `key_contexts: Vec<K::Context>`, a vector of `value_contexts:
// Vec<V::Context>`, a `mapping` from a string index to an integer index
// into the `contexts`, and a vector of `errors`.
// 2. **Push.** An index is required; an error is emitted and `push` returns
// if they field's first key does not contain an index. If the first key
// contains _one_ index, a new `K::Context` and `V::Context` are created.
// The key is pushed as the value to `K` and the remaining field as the
// value to `V`. The key and value are finalized; if both succeed, the key
// and value are stored in `keys` and `values`; otherwise the error(s) is
// stored in `errors`.
//
// If the first keys contains _two_ indices, the first must starts with
// `k` or `v`, while the `second` is arbitrary. `mapping` is indexed by
// `second`; the integer is retrieved. If none exists, new contexts are
// created an added to `{key,value}_contexts`, and their index is mapped
// to `second` in `mapping`. If the first index is `k`, the field,
// stripped of the first key, is pushed to the key's context; the same is
// done for the value's context is the first index is `v`.
// 3. **Finalization.** Every context is finalized; errors and `Ok` values
// are collected. TODO: FINISH. Split this into two: one for single-index,
// another for two-indices.
mod field;
mod options;
@ -450,6 +460,7 @@ pub mod error;
#[cfg(test)]
mod tests;
/// Type alias for `Result` with an error type of [`Errors`].
pub type Result<'v, T> = std::result::Result<T, Errors<'v>>;
#[doc(hidden)]

View File

@ -66,7 +66,7 @@ impl Name {
type Item = &'v Key;
fn next(&mut self) -> Option<Self::Item> {
if self.0.is_terminal() {
if self.0.exhausted() {
return None;
}
@ -102,7 +102,7 @@ impl Name {
type Item = &'v Name;
fn next(&mut self) -> Option<Self::Item> {
if self.0.is_terminal() {
if self.0.exhausted() {
return None;
}
@ -476,16 +476,55 @@ impl<'v> NameView<'v> {
/// Shifts the current key once to the right.
///
/// # Example
/// # Examples
///
/// ```rust
/// use rocket::form::name::NameView;
///
/// let mut view = NameView::new("a.b[c:d]");
/// let mut view = NameView::new("a.b[c:d][d.e]");
/// assert_eq!(view.key().unwrap(), "a");
///
/// view.shift();
/// assert_eq!(view.key().unwrap(), "b");
///
/// view.shift();
/// assert_eq!(view.key().unwrap(), "c:d");
///
/// view.shift();
/// assert_eq!(view.key().unwrap(), "d.e");
/// ```
///
/// Malformed strings can have interesting results:
///
/// ```rust
/// use rocket::form::name::NameView;
///
/// let mut view = NameView::new("a[c.d");
/// assert_eq!(view.key_lossy(), "a");
///
/// view.shift();
/// assert_eq!(view.key_lossy(), "c.d");
///
/// let mut view = NameView::new("a[c[.d]");
/// assert_eq!(view.key_lossy(), "a");
///
/// view.shift();
/// assert_eq!(view.key_lossy(), "c[.d");
///
/// view.shift();
/// assert_eq!(view.key(), None);
///
/// let mut view = NameView::new("foo[c[.d]]");
/// assert_eq!(view.key_lossy(), "foo");
///
/// view.shift();
/// assert_eq!(view.key_lossy(), "c[.d");
///
/// view.shift();
/// assert_eq!(view.key_lossy(), "]");
///
/// view.shift();
/// assert_eq!(view.key(), None);
/// ```
pub fn shift(&mut self) {
const START_DELIMS: &'static [char] = &['.', '['];
@ -494,13 +533,10 @@ impl<'v> NameView<'v> {
let bytes = string.as_bytes();
let shift = match bytes.get(0) {
None | Some(b'=') => 0,
Some(b'[') => match string[1..].find(&[']', '.'][..]) {
Some(j) => match string[1..].as_bytes()[j] {
b']' => j + 2,
_ => j + 1,
}
Some(b'[') => match memchr::memchr(b']', bytes) {
Some(j) => j + 1,
None => bytes.len(),
}
},
Some(b'.') => match string[1..].find(START_DELIMS) {
Some(j) => j + 1,
None => bytes.len(),
@ -569,6 +605,7 @@ impl<'v> NameView<'v> {
let key = match view.as_bytes().get(0) {
Some(b'.') => &view[1..],
Some(b'[') if view.ends_with(']') => &view[1..view.len() - 1],
Some(b'[') if self.is_at_last() => &view[1..],
_ => view
};
@ -643,7 +680,13 @@ impl<'v> NameView<'v> {
self.name
}
fn is_terminal(&self) -> bool {
// This is the last key. The next `shift()` will exhaust `self`.
fn is_at_last(&self) -> bool {
self.end == self.name.len()
}
// There are no more keys. A `shift` will do nothing.
fn exhausted(&self) -> bool {
self.start == self.name.len()
}
}

View File

@ -1,11 +1,17 @@
/// Form guard options.
///
/// See [`Form#leniency`](crate::form::Form#leniency) for details.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct Options {
/// Whether parsing should be strict (no extra parameters) or not.
pub strict: bool,
}
#[allow(non_upper_case_globals, dead_code)]
impl Options {
/// `Options` with `strict` set to `false`.
pub const Lenient: Self = Options { strict: false };
/// `Options` with `strict` set to `true`.
pub const Strict: Self = Options { strict: true };
}

View File

@ -148,6 +148,8 @@ mod raw_str_parse_tests {
}
impl<'r, 'i> MultipartParser<'r, 'i> {
/// Returns `None` when there are no further fields. Otherwise tries to
/// parse the next multipart form field and returns the result.
async fn next(&mut self) -> Option<Result<'r, Field<'r, 'i>>> {
if self.done {
return None;

View File

@ -1,68 +1,332 @@
use std::{borrow::Cow, convert::TryInto, fmt::Display, ops::{RangeBounds, Bound}};
//! Form field validation routines.
//!
//! Each function in this module can be used as the target of the
//! `field(validate)` field attribute of the `FromForm` derive.
//!
//! ```rust
//! use rocket::form::FromForm;
//!
//! #[derive(FromForm)]
//! struct MyForm<'r> {
//! #[field(validate = range(2..10))]
//! id: usize,
//! #[field(validate = omits("password"))]
//! password: &'r str,
//! }
//! ```
//!
//! The `validate` parameter takes any expression that returns a
//! [`form::Result<()>`](crate::form::Result). If the expression is a function
//! call, a reference to the field is inserted as the first parameter. Thus,
//! functions calls to `validate` must take a reference to _some_ type,
//! typically a generic with some bounds, as their first argument.
//!
//! ## Custom Error Messages
//!
//! To set a custom error messages, it is useful to chain results:
//!
//! ```rust
//! use rocket::form::{Errors, Error, FromForm};
//!
//! #[derive(FromForm)]
//! struct MyForm<'r> {
//! // By defining another function...
//! #[field(validate = omits("password").map_err(pass_help))]
//! password: &'r str,
//! // or inline using the `msg` helper.
//! #[field(validate = omits("password").or_else(msg!("please omit `password`")))]
//! password2: &'r str,
//! // You can even refer to the field in the message...
//! #[field(validate = range(1..).or_else(msg!("`{}` < 1", self.n)))]
//! n: isize,
//! // ..or other fields!
//! #[field(validate = range(..self.n).or_else(msg!("`{}` > `{}`", self.z, self.n)))]
//! z: isize,
//! }
//!
//! fn pass_help<'a>(errors: Errors<'_>) -> Errors<'a> {
//! Error::validation("passwords can't contain the text \"password\"").into()
//! }
//! ```
//!
//! ## Custom Validation
//!
//! Custom validation routines can be defined as regular functions. Consider a
//! routine that tries to validate a credit card number:
//!
//! ```rust
//! extern crate time;
//!
//! use rocket::form::{self, FromForm, Error};
//!
//! #[derive(FromForm)]
//! struct CreditCard {
//! #[field(validate = luhn(self.cvv, &self.expiration))]
//! number: u64,
//! cvv: u16,
//! expiration: time::Date,
//! }
//!
//! // Implementation of Luhn validator.
//! fn luhn<'v>(number: &u64, cvv: u16, exp: &time::Date) -> form::Result<'v, ()> {
//! # let valid = false;
//! if !valid {
//! Err(Error::validation("invalid credit card number"))?;
//! }
//!
//! Ok(())
//! }
//! ```
use std::borrow::Cow;
use std::convert::TryInto;
use std::ops::{RangeBounds, Bound};
use std::fmt::Debug;
use crate::data::ByteUnit;
use rocket_http::ContentType;
use crate::{data::TempFile, form::error::{Error, Errors}};
use crate::{data::TempFile, form::{Result, Error}};
pub fn eq<'v, A, B>(a: &A, b: B) -> Result<(), Errors<'v>>
/// A helper macro for custom validation error messages.
///
/// The macro works identically to [`std::format!`] except it does not allocate
/// when the expression is a string literal. It returns a function (a closure)
/// that takes one parameter and evaluates to an `Err` of validation [`Error`]
/// with the formatted message. While useful in other contexts, it is designed
/// to be chained to validation results via `.or_else()` and `.and_then()`.
///
/// Note that the macro never needs to be imported when used with a `FromForm`
/// derive; all items in [`form::validate`](crate::form::validate) are already
/// in scope.
///
/// # Example
///
/// ```rust
/// use rocket::form::FromForm;
///
/// #[derive(FromForm)]
/// struct Person<'r> {
/// #[field(validate = len(3..).or_else(msg!("that's a short name...")))]
/// name: &'r str,
/// #[field(validate = contains('f').and_then(msg!("please, no `f`!")))]
/// non_f_name: &'r str,
/// }
/// ```
///
/// See the [top-level docs](crate::form::validate) for more examples.
#[macro_export]
macro_rules! msg {
($e:expr) => (
|_| {
Err($crate::form::Errors::from($crate::form::Error::validation($e)))
as $crate::form::Result<()>
}
);
($($arg:tt)*) => (
|_| {
Err($crate::form::Errors::from($crate::form::Error::validation(format!($($arg)*))))
as $crate::form::Result<()>
}
);
}
#[doc(inline)]
pub use msg;
/// Equality validator: succeeds exactly when `a` == `b`, using [`PartialEq`].
///
/// On failure, returns a validation error with the following message:
///
/// ```text
/// value does not match expected value
/// ```
///
/// # Example
///
/// ```rust
/// use rocket::form::{FromForm, FromFormField};
///
/// #[derive(FromFormField, PartialEq)]
/// enum Kind {
/// Car,
/// Truck
/// }
///
/// #[derive(FromForm)]
/// struct Foo<'r> {
/// #[field(validate = eq("Bob Marley"))]
/// name: &'r str,
/// #[field(validate = eq(Kind::Car))]
/// vehicle: Kind,
/// #[field(validate = eq(&[5, 7, 8]))]
/// numbers: Vec<usize>,
/// }
/// ```
pub fn eq<'v, A, B>(a: &A, b: B) -> Result<'v, ()>
where A: PartialEq<B>
{
if a != &b {
Err(Error::validation("value does not match"))?
Err(Error::validation("value does not match expected value"))?
}
Ok(())
}
pub trait Len {
fn len(&self) -> usize;
fn len_u64(&self) -> u64 {
self.len() as u64
}
}
impl Len for str {
fn len(&self) -> usize { self.len() }
}
impl Len for String {
fn len(&self) -> usize { self.len() }
}
impl<T> Len for Vec<T> {
fn len(&self) -> usize { <Vec<T>>::len(self) }
}
impl Len for TempFile<'_> {
fn len(&self) -> usize { TempFile::len(self) as usize }
fn len_u64(&self) -> u64 { TempFile::len(self) }
}
impl<K, V> Len for std::collections::HashMap<K, V> {
fn len(&self) -> usize { <std::collections::HashMap<K, V>>::len(self) }
}
impl<T: Len + ?Sized> Len for &T {
fn len(&self) -> usize {
<T as Len>::len(self)
}
}
pub fn len<'v, V, R>(value: V, range: R) -> Result<(), Errors<'v>>
where V: Len, R: RangeBounds<u64>
/// Negative equality validator: succeeds exactly when `a` != `b`, using
/// [`PartialEq`].
///
/// On failure, returns a validation error with the following message:
///
/// ```text
/// value is equal to an invalid value
/// ```
///
/// # Example
///
/// ```rust
/// use rocket::form::{FromForm, FromFormField};
///
/// #[derive(FromFormField, PartialEq)]
/// enum Kind {
/// Car,
/// Truck
/// }
///
/// #[derive(FromForm)]
/// struct Foo<'r> {
/// #[field(validate = neq("Bob Marley"))]
/// name: &'r str,
/// #[field(validate = neq(Kind::Car))]
/// vehicle: Kind,
/// #[field(validate = neq(&[5, 7, 8]))]
/// numbers: Vec<usize>,
/// }
/// ```
pub fn neq<'v, A, B>(a: &A, b: B) -> Result<'v, ()>
where A: PartialEq<B>
{
if !range.contains(&value.len_u64()) {
if a == &b {
Err(Error::validation("value is equal to an invalid value"))?
}
Ok(())
}
/// Types for values that have a length.
///
/// At present, these are:
///
/// | type | length description |
/// |-----------------------------------|--------------------------------------|
/// | `&str`, `String` | length in bytes |
/// | `Vec<T>` | number of elements in the vector |
/// | `HashMap<K, V>`, `BTreeMap<K, V>` | number of key/value pairs in the map |
/// | [`TempFile`] | length of the file in bytes |
/// | `Option<T>` where `T: Len` | length of `T` or 0 if `None` |
/// | [`form::Result<'_, T>`] | length of `T` or 0 if `Err` |
///
/// [`form::Result<'_, T>`]: crate::form::Result
pub trait Len<L> {
/// The length of the value.
fn len(&self) -> L;
/// Convert `len` into `u64`.
fn len_into_u64(len: L) -> u64;
/// The zero value for `L`.
fn zero_len() -> L;
}
macro_rules! impl_len {
(<$($gen:ident),*> $T:ty => $L:ty) => (
impl <$($gen),*> Len<$L> for $T {
fn len(&self) -> $L { self.len() }
fn len_into_u64(len: $L) -> u64 { len as u64 }
fn zero_len() -> $L { 0 }
}
)
}
impl_len!(<> str => usize);
impl_len!(<> String => usize);
impl_len!(<T> Vec<T> => usize);
impl_len!(<> TempFile<'_> => u64);
impl_len!(<K, V> std::collections::HashMap<K, V> => usize);
impl_len!(<K, V> std::collections::BTreeMap<K, V> => usize);
impl Len<ByteUnit> for TempFile<'_> {
fn len(&self) -> ByteUnit { self.len().into() }
fn len_into_u64(len: ByteUnit) -> u64 { len.into() }
fn zero_len() -> ByteUnit { ByteUnit::from(0) }
}
impl<L, T: Len<L> + ?Sized> Len<L> for &T {
fn len(&self) -> L { <T as Len<L>>::len(self) }
fn len_into_u64(len: L) -> u64 { T::len_into_u64(len) }
fn zero_len() -> L { T::zero_len() }
}
impl<L, T: Len<L>> Len<L> for Option<T> {
fn len(&self) -> L { self.as_ref().map(|v| v.len()).unwrap_or(T::zero_len()) }
fn len_into_u64(len: L) -> u64 { T::len_into_u64(len) }
fn zero_len() -> L { T::zero_len() }
}
impl<L, T: Len<L>> Len<L> for Result<'_, T> {
fn len(&self) -> L { self.as_ref().ok().len() }
fn len_into_u64(len: L) -> u64 { T::len_into_u64(len) }
fn zero_len() -> L { T::zero_len() }
}
/// Length validator: succeeds when the length of a value is within a `range`.
///
/// The value must implement [`Len`]. On failure, returns an [`InvalidLength`]
/// error. See [`Len`] for supported types and how their length is computed.
///
/// [`InvalidLength`]: rocket::form::error::ErrorKind::InvalidLength
///
/// # Data Limits
///
/// All form types are constrained by a data limit. As such, the `len()`
/// validator should be used only when a data limit is insufficiently specific.
/// For example, prefer to use data [`Limits`](crate::data::Limits) to validate
/// the length of files as not doing so will result in writing more data to disk
/// than necessary.
///
/// # Example
///
/// ```rust
/// use rocket::http::ContentType;
/// use rocket::form::{FromForm, FromFormField};
/// use rocket::data::{TempFile, ToByteUnit};
///
/// #[derive(FromForm)]
/// struct Foo<'r> {
/// #[field(validate = len(5..20))]
/// name: &'r str,
/// #[field(validate = len(..=200))]
/// maybe_name: Option<&'r str>,
/// #[field(validate = len(..=2.mebibytes()))]
/// #[field(validate = ext(ContentType::Plain))]
/// file: TempFile<'r>,
/// }
/// ```
pub fn len<'v, V, L, R>(value: V, range: R) -> Result<'v, ()>
where V: Len<L>,
L: Copy + PartialOrd,
R: RangeBounds<L>
{
if !range.contains(&value.len()) {
let start = match range.start_bound() {
Bound::Included(v) => Some(*v),
Bound::Excluded(v) => Some(v.saturating_add(1)),
Bound::Included(v) => Some(V::len_into_u64(*v)),
Bound::Excluded(v) => Some(V::len_into_u64(*v).saturating_add(1)),
Bound::Unbounded => None
};
let end = match range.end_bound() {
Bound::Included(v) => Some(*v),
Bound::Excluded(v) => Some(v.saturating_sub(1)),
Bound::Included(v) => Some(V::len_into_u64(*v)),
Bound::Excluded(v) => Some(V::len_into_u64(*v).saturating_sub(1)),
Bound::Unbounded => None,
};
@ -72,87 +336,42 @@ pub fn len<'v, V, R>(value: V, range: R) -> Result<(), Errors<'v>>
Ok(())
}
/// Types for values that contain items.
///
/// At present, these are:
///
/// | type | contains |
/// |-------------------------|----------------------------|
/// | `&str`, `String` | `&str`, `char` |
/// | `Vec<T>` | `T`, `&T` |
/// | `Option<T>` | `I` where `T: Contains<I>` |
/// | [`form::Result<'_, T>`] | `I` where `T: Contains<I>` |
///
/// [`form::Result<'_, T>`]: crate::form::Result
pub trait Contains<I> {
/// Returns `true` if `self` contains `item`.
fn contains(&self, item: I) -> bool;
}
impl<I, T: Contains<I>> Contains<I> for &T {
fn contains(&self, item: I) -> bool {
<T as Contains<I>>::contains(self, item)
macro_rules! impl_contains {
([$($gen:tt)*] $T:ty [contains] $I:ty [via] $P:ty) => {
impl_contains!([$($gen)*] $T [contains] $I [via] $P [with] |v| v);
};
([$($gen:tt)*] $T:ty [contains] $I:ty [via] $P:ty [with] $f:expr) => {
impl<$($gen)*> Contains<$I> for $T {
fn contains(&self, item: $I) -> bool {
<$P>::contains(self, $f(item))
}
}
};
}
impl Contains<&str> for str {
fn contains(&self, string: &str) -> bool {
<str>::contains(self, string)
}
}
impl Contains<&&str> for str {
fn contains(&self, string: &&str) -> bool {
<str>::contains(self, string)
}
}
impl Contains<char> for str {
fn contains(&self, c: char) -> bool {
<str>::contains(self, c)
}
}
impl Contains<&char> for str {
fn contains(&self, c: &char) -> bool {
<str>::contains(self, *c)
}
}
impl Contains<&str> for &str {
fn contains(&self, string: &str) -> bool {
<str>::contains(self, string)
}
}
impl Contains<&&str> for &str {
fn contains(&self, string: &&str) -> bool {
<str>::contains(self, string)
}
}
impl Contains<char> for &str {
fn contains(&self, c: char) -> bool {
<str>::contains(self, c)
}
}
impl Contains<&char> for &str {
fn contains(&self, c: &char) -> bool {
<str>::contains(self, *c)
}
}
impl Contains<&str> for String {
fn contains(&self, string: &str) -> bool {
<str>::contains(self, string)
}
}
impl Contains<&&str> for String {
fn contains(&self, string: &&str) -> bool {
<str>::contains(self, string)
}
}
impl Contains<char> for String {
fn contains(&self, c: char) -> bool {
<str>::contains(self, c)
}
}
impl Contains<&char> for String {
fn contains(&self, c: &char) -> bool {
<str>::contains(self, *c)
}
}
impl_contains!([] str [contains] &str [via] str);
impl_contains!([] str [contains] char [via] str);
impl_contains!([] String [contains] &str [via] str);
impl_contains!([] String [contains] char [via] str);
impl_contains!([T: PartialEq] Vec<T> [contains] &T [via] [T]);
impl<T: PartialEq> Contains<T> for Vec<T> {
fn contains(&self, item: T) -> bool {
@ -160,33 +379,202 @@ impl<T: PartialEq> Contains<T> for Vec<T> {
}
}
impl<T: PartialEq> Contains<&T> for Vec<T> {
fn contains(&self, item: &T) -> bool {
<[T]>::contains(self, item)
impl<I, T: Contains<I>> Contains<I> for Option<T> {
fn contains(&self, item: I) -> bool {
self.as_ref().map(|v| v.contains(item)).unwrap_or(false)
}
}
pub fn contains<'v, V, I>(value: V, item: I) -> Result<(), Errors<'v>>
where V: for<'a> Contains<&'a I>, I: std::fmt::Debug
impl<I, T: Contains<I>> Contains<I> for Result<'_, T> {
fn contains(&self, item: I) -> bool {
self.as_ref().map(|v| v.contains(item)).unwrap_or(false)
}
}
impl<I, T: Contains<I> + ?Sized> Contains<I> for &T {
fn contains(&self, item: I) -> bool {
<T as Contains<I>>::contains(self, item)
}
}
/// Contains validator: succeeds when a value contains `item`.
///
/// The value must implement [`Contains<I>`](Contains) where `I` is the type of
/// the `item`. See [`Contains`] for supported types and items.
///
/// On failure, returns a validation error with the following message:
///
/// ```text
/// value is equal to an invalid value
/// ```
///
/// # Example
///
/// ```rust
/// use rocket::form::{FromForm, FromFormField};
///
/// #[derive(PartialEq, FromFormField)]
/// enum Pet { Cat, Dog }
///
/// #[derive(FromForm)]
/// struct Foo<'r> {
/// best_pet: Pet,
/// #[field(validate = contains(Pet::Cat))]
/// #[field(validate = contains(&self.best_pet))]
/// pets: Vec<Pet>,
/// #[field(validate = contains('/'))]
/// license: &'r str,
/// #[field(validate = contains("@rust-lang.org"))]
/// rust_lang_email: &'r str,
/// }
/// ```
pub fn contains<'v, V, I>(value: V, item: I) -> Result<'v, ()>
where V: Contains<I>
{
if !value.contains(&item) {
Err(Error::validation(format!("must contain {:?}", item)))?
if !value.contains(item) {
Err(Error::validation("value does not contain expected item"))?
}
Ok(())
}
pub fn omits<'v, V, I>(value: V, item: I) -> Result<(), Errors<'v>>
where V: for<'a> Contains<&'a I>, I: std::fmt::Debug
/// Verbose contains validator: like `contains` but mentions `item` in the
/// error message.
///
/// The is identical to [`contains()`] except that `item` must be `Debug + Copy`
/// and the error message is as follows, where `$item` is the [`Debug`]
/// representation of `item`:
///
/// ```text
/// values must contains $item
/// ```
///
/// # Example
///
/// ```rust
/// use rocket::form::{FromForm, FromFormField};
///
/// #[derive(PartialEq, Debug, Clone, Copy, FromFormField)]
/// enum Pet { Cat, Dog }
///
/// #[derive(FromForm)]
/// struct Foo {
/// best_pet: Pet,
/// #[field(validate = verbose_contains(Pet::Dog))]
/// #[field(validate = verbose_contains(&self.best_pet))]
/// pets: Vec<Pet>,
/// }
/// ```
pub fn verbose_contains<'v, V, I>(value: V, item: I) -> Result<'v, ()>
where V: Contains<I>, I: Debug + Copy
{
if value.contains(&item) {
Err(Error::validation(format!("cannot contain {:?}", item)))?
if !value.contains(item) {
Err(Error::validation(format!("value must contain {:?}", item)))?
}
Ok(())
}
pub fn range<'v, V, R>(value: &V, range: R) -> Result<(), Errors<'v>>
/// Omits validator: succeeds when a value _does not_ contains `item`.
/// error message.
///
/// The value must implement [`Contains<I>`](Contains) where `I` is the type of
/// the `item`. See [`Contains`] for supported types and items.
///
/// On failure, returns a validation error with the following message:
///
/// ```text
/// value contains a disallowed item
/// ```
///
/// # Example
///
/// ```rust
/// use rocket::form::{FromForm, FromFormField};
///
/// #[derive(PartialEq, FromFormField)]
/// enum Pet { Cat, Dog }
///
/// #[derive(FromForm)]
/// struct Foo<'r> {
/// #[field(validate = omits(Pet::Cat))]
/// pets: Vec<Pet>,
/// #[field(validate = omits('@'))]
/// not_email: &'r str,
/// #[field(validate = omits("@gmail.com"))]
/// non_gmail_email: &'r str,
/// }
/// ```
pub fn omits<'v, V, I>(value: V, item: I) -> Result<'v, ()>
where V: Contains<I>
{
if value.contains(item) {
Err(Error::validation("value contains a disallowed item"))?
}
Ok(())
}
/// Verbose omits validator: like `omits` but mentions `item` in the error
/// message.
///
/// The is identical to [`omits()`] except that `item` must be `Debug + Copy`
/// and the error message is as follows, where `$item` is the [`Debug`]
/// representation of `item`:
///
/// ```text
/// value cannot contain $item
/// ```
///
/// # Example
///
/// ```rust
/// use rocket::form::{FromForm, FromFormField};
///
/// #[derive(PartialEq, Debug, Clone, Copy, FromFormField)]
/// enum Pet { Cat, Dog }
///
/// #[derive(FromForm)]
/// struct Foo<'r> {
/// #[field(validate = verbose_omits(Pet::Cat))]
/// pets: Vec<Pet>,
/// #[field(validate = verbose_omits('@'))]
/// not_email: &'r str,
/// #[field(validate = verbose_omits("@gmail.com"))]
/// non_gmail_email: &'r str,
/// }
/// ```
pub fn verbose_omits<'v, V, I>(value: V, item: I) -> Result<'v, ()>
where V: Contains<I>, I: Copy + Debug
{
if value.contains(item) {
Err(Error::validation(format!("value cannot contain {:?}", item)))?
}
Ok(())
}
/// Integer range validator: succeeds when an integer value is within a range.
///
/// The value must be an integer type that implement `TryInto<isize> + Copy`. On
/// failure, returns an [`OutOfRange`] error.
///
/// [`OutOfRange`]: rocket::form::error::ErrorKind::OutOfRange
///
/// # Example
///
/// ```rust
/// use rocket::form::FromForm;
///
/// #[derive(FromForm)]
/// struct Foo {
/// #[field(validate = range(0..))]
/// non_negative: isize,
/// #[field(validate = range(18..=130))]
/// maybe_adult: u8,
/// }
/// ```
pub fn range<'v, V, R>(value: &V, range: R) -> Result<'v, ()>
where V: TryInto<isize> + Copy, R: RangeBounds<isize>
{
if let Ok(v) = (*value).try_into() {
@ -211,36 +599,93 @@ pub fn range<'v, V, R>(value: &V, range: R) -> Result<(), Errors<'v>>
Err((start, end))?
}
pub fn one_of<'v, V, I>(value: V, items: &[I]) -> Result<(), Errors<'v>>
where V: for<'a> Contains<&'a I>, I: Display
/// Contains one of validator: succeeds when a value contains at least one item
/// in an `items` iterator.
///
/// The value must implement [`Contains<I>`](Contains) where `I` is the type of
/// the `item`. The iterator must be [`Clone`]. See [`Contains`] for supported
/// types and items. The item must be [`Debug`].
///
/// On failure, returns a [`InvalidChoice`] error with the debug representation
/// of each item in `items`.
///
/// [`InvalidChoice`]: rocket::form::error::ErrorKind::InvalidChoice
///
/// # Example
///
/// ```rust
/// use rocket::form::FromForm;
///
/// #[derive(FromForm)]
/// struct Foo<'r> {
/// #[field(validate = one_of(&[3, 5, 7]))]
/// single_digit_primes: Vec<u8>,
/// #[field(validate = one_of(" \t\n".chars()))]
/// has_space_char: &'r str,
/// #[field(validate = one_of(" \t\n".chars()).and_then(msg!("no spaces")))]
/// no_space: &'r str,
/// }
/// ```
pub fn one_of<'v, V, I, R>(value: V, items: R) -> Result<'v, ()>
where V: Contains<I>,
I: Debug,
R: IntoIterator<Item = I>,
<R as IntoIterator>::IntoIter: Clone
{
for item in items {
let items = items.into_iter();
for item in items.clone() {
if value.contains(item) {
return Ok(());
}
}
let choices = items.iter()
.map(|item| item.to_string().into())
.collect::<Vec<Cow<'v, str>>>();
let choices: Vec<Cow<'_, str>> = items
.map(|item| format!("{:?}", item).into())
.collect();
Err(choices)?
}
pub fn ext<'v>(file: &TempFile<'_>, ext: &str) -> Result<(), Errors<'v>> {
/// File type validator: succeeds when a [`TempFile`] has the Content-Type
/// `content_type`.
///
/// On failure, returns a validation error with one of the following messages:
///
/// ```text
/// // the file has an incorrect extension
/// file type was .$file_ext but must be $type
///
/// // the file does not have an extension
/// file type must be $type
/// ```
///
/// # Example
///
/// ```rust
/// use rocket::form::FromForm;
/// use rocket::data::{ToByteUnit, TempFile};
/// use rocket::http::ContentType;
///
/// #[derive(FromForm)]
/// struct Foo<'r> {
/// #[field(validate = ext(ContentType::PDF))]
/// #[field(validate = len(..1.mebibytes()))]
/// document: TempFile<'r>,
/// }
/// ```
pub fn ext<'v>(file: &TempFile<'_>, r#type: ContentType) -> Result<'v, ()> {
if let Some(file_ct) = file.content_type() {
if let Some(ext_ct) = ContentType::from_extension(ext) {
if file_ct == &ext_ct {
if file_ct == &r#type {
return Ok(());
}
let m = file_ct.extension()
.map(|fext| format!("file type was .{} but must be .{}", fext, ext))
.unwrap_or_else(|| format!("file type must be .{}", ext));
Err(Error::validation(m))?
}
}
Err(Error::validation(format!("invalid extension: expected {}", ext)))?
let msg = match (file.content_type().and_then(|c| c.extension()), r#type.extension()) {
(Some(a), Some(b)) => format!("invalid file type: .{}, must be .{}", a, b),
(Some(a), None) => format!("invalid file type: .{}, must be {}", a, r#type),
(None, Some(b)) => format!("file type must be .{}", b),
(None, None) => format!("file type must be {}", r#type),
};
Err(Error::validation(msg))?
}

View File

@ -832,30 +832,35 @@ means that validation was successful while an `Err` of [`Errors<'_>`] indicates
the error(s) that occured. For instance, if you wanted to implement an ad-hoc
Luhn validator for credit-card-like numbers, you might write:
```rust
# #[macro_use] extern crate rocket;
extern crate time;
use rocket::form::{self, Error};
#[derive(FromForm)]
struct CreditCard<'v> {
#[field(validate = luhn())]
number: &'v str,
# #[field(validate = luhn())]
# other: String,
struct CreditCard {
#[field(validate = luhn(self.cvv, &self.expiration))]
number: u64,
#[field(validate = range(..9999))]
cvv: u16,
expiration: time::Date,
}
fn luhn<'v, S: AsRef<str>>(field: S) -> rocket::form::Result<'v, ()> {
let num = field.as_ref().parse::<u64>()?;
fn luhn<'v>(number: &u64, cvv: u16, exp: &time::Date) -> form::Result<'v, ()> {
# let valid = false;
if !valid {
Err(Error::validation("invalid credit card number"))?;
}
/* implementation of Luhn validator... */
# Ok(())
Ok(())
}
```
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