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; pub use json;

View File

@ -1,7 +1,10 @@
use devise::{*, ext::{TypeExt, SpanDiagnosticExt}}; use devise::{*, ext::{TypeExt, SpanDiagnosticExt}};
use syn::visit_mut::VisitMut;
use syn::visit::Visit;
use crate::exports::*; use crate::exports::*;
use crate::proc_macro2::Span; use crate::proc_macro2::{Span, TokenStream, TokenTree};
use crate::name::Name; use crate::name::Name;
pub struct FormField { pub struct FormField {
@ -86,62 +89,139 @@ impl FieldExt for Field<'_> {
fn name_view(&self) -> Result<syn::Expr> { fn name_view(&self) -> Result<syn::Expr> {
let field_name = self.field_name()?; 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>( pub fn validators<'v>(
field: Field<'v>, field: Field<'v>,
out: &'v syn::Ident, parent: &'v syn::Ident, // field ident (if local) or form ident (if !local)
local: bool, local: bool,
) -> Result<impl Iterator<Item = syn::Expr> + 'v> { ) -> 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)? Ok(FieldAttr::from_attrs(FieldAttr::NAME, &field.attrs)?
.into_iter() .into_iter()
.filter_map(|a| a.validate) .filter_map(|a| a.validate)
.map(move |mut expr| { .map(move |expr| {
let mut mutator = ValidationMutator::new(field.ident(), out); let mut members = RecordMemberAccesses(vec![]);
mutator.visit_expr_mut(&mut expr); members.visit_expr(&expr);
(expr, mutator.rec)
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) .filter(move |(_, is_local)| *is_local == local)
.map(|(e, _)| e)) .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 { pub fn derive_from_form(input: proc_macro::TokenStream) -> TokenStream {
DeriveGenerator::build_for(input, quote!(impl<'__f> #_form::FromForm<'__f>)) 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) .support(Support::NamedStruct | Support::Lifetime | Support::Type)
.replace_generic(0, 0) .replace_generic(0, 0)
.type_bound(quote!(#_form::FromForm<'__f> + '__f)) .type_bound(quote!(#_form::FromForm<'__f> + '__f))
@ -189,13 +190,6 @@ pub fn derive_from_form(input: proc_macro::TokenStream) -> TokenStream {
.split2(); .split2();
Ok(quote_spanned! { fields.span() => 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 { #(let #ident = match #finalize_field {
#_ok(#ident) => #_some(#ident), #_ok(#ident) => #_some(#ident),
#_err(_e) => { __c.__errors.extend(_e); #_none } #_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() .unwrap_or_else(|| <#ty as #_form::FromForm<'__f>>::default()
.ok_or_else(|| #_form::ErrorKind::Missing.into()) .ok_or_else(|| #_form::ErrorKind::Missing.into())
) )
// <#ty as #_form::FromForm<'__f>>::finalize(__c.#ident)
.and_then(|#ident| { .and_then(|#ident| {
let mut _es = #_form::Errors::new(); let mut _es = #_form::Errors::new();
#(if let #_err(_e) = #validator { _es.extend(_e); })* #(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::http::{ContentType, RawStr};
use crate::{Request, Data}; 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)] #[derive(Debug, Clone)]
pub struct ValueField<'r> { pub struct ValueField<'r> {
/// The (decoded) name of the form field.
pub name: NameView<'r>, pub name: NameView<'r>,
/// The (decoded) value of the form field.
pub value: &'r str, 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> { pub struct DataField<'r, 'i> {
/// The (decoded) name of the form field.
pub name: NameView<'r>, 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>, 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, pub content_type: ContentType,
/// The request in which the form field was submitted.
pub request: &'r Request<'i>, pub request: &'r Request<'i>,
/// The raw data stream.
pub data: Data, pub data: Data,
} }
impl<'v> ValueField<'v> { 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 { pub fn parse(field: &'v str) -> Self {
// WHATWG URL Living Standard 5.1 steps 3.2, 3.3. // WHATWG URL Living Standard 5.1 steps 3.2, 3.3.
let (name, val) = RawStr::new(field).split_at_byte(b'='); let (name, val) = RawStr::new(field).split_at_byte(b'=');
ValueField::from((name.as_str(), val.as_str())) 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 { pub fn from_value(value: &'v str) -> Self {
ValueField::from(("", value)) 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 { pub fn shift(mut self) -> Self {
self.name.shift(); self.name.shift();
self 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> { pub fn unexpected(&self) -> Error<'v> {
Error::from(ErrorKind::Unexpected) Error::from(ErrorKind::Unexpected)
.with_name(NameView::new(self.name.source())) .with_name(self.name.source())
.with_value(self.value) .with_value(self.value)
.with_entity(Entity::ValueField) .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> { pub fn missing(&self) -> Error<'v> {
Error::from(ErrorKind::Missing) Error::from(ErrorKind::Missing)
.with_name(NameView::new(self.name.source())) .with_name(self.name.source())
.with_value(self.value) .with_value(self.value)
.with_entity(Entity::ValueField) .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> { impl<'a> From<(&'a str, &'a str)> for ValueField<'a> {
fn from((name, value): (&'a str, &'a str)) -> Self { fn from((name, value): (&'a str, &'a str)) -> Self {
ValueField { name: NameView::new(name), value } 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 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. /// A data guard for [`FromForm`] types.
/// ///
/// This type implements the [`FromData`] trait. It provides a generic means to /// 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 /// See the [forms guide](https://rocket.rs/master/guide/requests#forms) for
/// general form support documentation. /// general form support documentation.
@ -57,19 +57,44 @@ use crate::form::prelude::*;
/// ///
/// ## Data Limits /// ## Data Limits
/// ///
/// The default size limit for incoming form data is 32KiB. Setting a limit /// ### URL-Encoded Forms
/// protects your application from denial of service (DOS) attacks and from ///
/// resource exhaustion through high memory consumption. The limit can be /// The `form` limit specifies the data limit for an entire url-encoded form
/// modified by setting the `limits.form` configuration parameter. For instance, /// data. It defaults to 32KiB. URL-encoded form data is percent-decoded, stored
/// to increase the forms limit to 512KiB for all environments, you may add the /// in-memory, and parsed into [`ValueField`]s. If the incoming data exceeds
/// following to your `Rocket.toml`: /// 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 /// ```toml
/// [global.limits] /// [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)] #[derive(Debug)]
pub struct Form<T>(T); 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) let error = Error::from(ErrorKind::Missing)
.with_entity(Entity::Indices) .with_entity(Entity::Key)
.with_name(name); .with_name(name);
self.errors.push(error); self.errors.push(error);
@ -566,14 +566,14 @@ impl<'v, K, V> MapContext<'v, K, V>
.zip(key_map.values().map(|(_, name)| name)) .zip(key_map.values().map(|(_, name)| name))
.filter_map(|(ctxt, name)| match K::finalize(ctxt) { .filter_map(|(ctxt, name)| match K::finalize(ctxt) {
Ok(value) => Some(value), 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() let values = values.into_iter()
.zip(key_map.values().map(|(_, name)| name)) .zip(key_map.values().map(|(_, name)| name))
.filter_map(|(ctxt, name)| match V::finalize(ctxt) { .filter_map(|(ctxt, name)| match V::finalize(ctxt) {
Ok(value) => Some(value), 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(); 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 /// A value is validated successfully if the `from_str` method for the given
/// type returns successfully. Only accepts form _values_, not binary data. /// type returns successfully. Only accepts form _values_, not binary data.
/// No data limit applies.
/// ///
/// * **`bool`** /// * **`bool`**
/// ///
@ -69,17 +70,21 @@ use crate::form::prelude::*;
/// `"off"`, `"no"`, or `"false"`. Defaults to `false` otherwise. Only /// `"off"`, `"no"`, or `"false"`. Defaults to `false` otherwise. Only
/// accepts form _values_, not binary data. /// accepts form _values_, not binary data.
/// ///
/// **No data limit applies.**
///
/// * **`&str`, `String`** /// * **`&str`, `String`**
/// ///
/// The decoded form value or data is returned directly without /// The decoded form value or data is returned directly without
/// modification. /// modification.
/// ///
/// **The data limit `string` applies.**
///
/// * **[`TempFile`]** /// * **[`TempFile`]**
/// ///
/// Streams the form field value or data to a temporary file. See /// 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 /// 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 /// 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 /// This is the `"date"` HTML input type. Only accepts form _values_, not
/// binary data. /// binary data.
/// ///
/// **No data limit applies.**
///
/// * **[`time::PrimitiveDateTime`]** /// * **[`time::PrimitiveDateTime`]**
/// ///
/// Parses a date in `%FT%R` or `%FT%T` format, that is, `YYYY-MM-DDTHH:MM` /// 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_, /// without support for the millisecond variant. Only accepts form _values_,
/// not binary data. /// not binary data.
/// ///
/// **No data limit applies.**
///
/// * **[`time::Time`]** /// * **[`time::Time`]**
/// ///
/// Parses a time in `%R` or `%T` format, that is, `HH:MM` or `HH:MM:SS`. /// 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 /// This is the `"time"` HTML input type without support for the millisecond
/// variant. Only accepts form _values_, not binary data. /// variant. Only accepts form _values_, not binary data.
/// ///
/// **No data limit applies.**
///
/// [`TempFile`]: crate::data::TempFile /// [`TempFile`]: crate::data::TempFile
/// ///
/// # Implementing /// # Implementing
@ -240,7 +251,7 @@ pub struct FromFieldContext<'v, T: FromFormField<'v>> {
} }
impl<'v, T: FromFormField<'v>> FromFieldContext<'v, T> { impl<'v, T: FromFormField<'v>> FromFieldContext<'v, T> {
fn can_push(&mut self) -> bool { fn should_push(&mut self) -> bool {
self.pushes += 1; self.pushes += 1;
self.value.is_none() 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>) { 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.field_value = Some(field.value);
ctxt.push(field.name, Self::from_value(field)) ctxt.push(field.name, Self::from_value(field))
} }
} }
async fn push_data(ctxt: &mut FromFieldContext<'v, T>, field: DataField<'v, '_>) { 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); ctxt.push(field.name, Self::from_data(field).await);
} }
} }

View File

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

View File

@ -66,7 +66,7 @@ impl Name {
type Item = &'v Key; type Item = &'v Key;
fn next(&mut self) -> Option<Self::Item> { fn next(&mut self) -> Option<Self::Item> {
if self.0.is_terminal() { if self.0.exhausted() {
return None; return None;
} }
@ -102,7 +102,7 @@ impl Name {
type Item = &'v Name; type Item = &'v Name;
fn next(&mut self) -> Option<Self::Item> { fn next(&mut self) -> Option<Self::Item> {
if self.0.is_terminal() { if self.0.exhausted() {
return None; return None;
} }
@ -476,16 +476,55 @@ impl<'v> NameView<'v> {
/// Shifts the current key once to the right. /// Shifts the current key once to the right.
/// ///
/// # Example /// # Examples
/// ///
/// ```rust /// ```rust
/// use rocket::form::name::NameView; /// 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"); /// assert_eq!(view.key().unwrap(), "a");
/// ///
/// view.shift(); /// view.shift();
/// assert_eq!(view.key().unwrap(), "b"); /// 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) { pub fn shift(&mut self) {
const START_DELIMS: &'static [char] = &['.', '[']; const START_DELIMS: &'static [char] = &['.', '['];
@ -494,13 +533,10 @@ impl<'v> NameView<'v> {
let bytes = string.as_bytes(); let bytes = string.as_bytes();
let shift = match bytes.get(0) { let shift = match bytes.get(0) {
None | Some(b'=') => 0, None | Some(b'=') => 0,
Some(b'[') => match string[1..].find(&[']', '.'][..]) { Some(b'[') => match memchr::memchr(b']', bytes) {
Some(j) => match string[1..].as_bytes()[j] { Some(j) => j + 1,
b']' => j + 2,
_ => j + 1,
}
None => bytes.len(), None => bytes.len(),
} },
Some(b'.') => match string[1..].find(START_DELIMS) { Some(b'.') => match string[1..].find(START_DELIMS) {
Some(j) => j + 1, Some(j) => j + 1,
None => bytes.len(), None => bytes.len(),
@ -569,6 +605,7 @@ impl<'v> NameView<'v> {
let key = match view.as_bytes().get(0) { let key = match view.as_bytes().get(0) {
Some(b'.') => &view[1..], Some(b'.') => &view[1..],
Some(b'[') if view.ends_with(']') => &view[1..view.len() - 1], Some(b'[') if view.ends_with(']') => &view[1..view.len() - 1],
Some(b'[') if self.is_at_last() => &view[1..],
_ => view _ => view
}; };
@ -643,7 +680,13 @@ impl<'v> NameView<'v> {
self.name 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() 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)] #[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct Options { pub struct Options {
/// Whether parsing should be strict (no extra parameters) or not.
pub strict: bool, pub strict: bool,
} }
#[allow(non_upper_case_globals, dead_code)] #[allow(non_upper_case_globals, dead_code)]
impl Options { impl Options {
/// `Options` with `strict` set to `false`.
pub const Lenient: Self = Options { strict: false }; pub const Lenient: Self = Options { strict: false };
/// `Options` with `strict` set to `true`.
pub const Strict: Self = Options { strict: 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> { 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>>> { async fn next(&mut self) -> Option<Result<'r, Field<'r, 'i>>> {
if self.done { if self.done {
return None; 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 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> where A: PartialEq<B>
{ {
if a != &b { if a != &b {
Err(Error::validation("value does not match"))? Err(Error::validation("value does not match expected value"))?
} }
Ok(()) Ok(())
} }
pub trait Len { /// Negative equality validator: succeeds exactly when `a` != `b`, using
fn len(&self) -> usize; /// [`PartialEq`].
///
fn len_u64(&self) -> u64 { /// On failure, returns a validation error with the following message:
self.len() as u64 ///
} /// ```text
} /// value is equal to an invalid value
/// ```
impl Len for str { ///
fn len(&self) -> usize { self.len() } /// # Example
} ///
/// ```rust
impl Len for String { /// use rocket::form::{FromForm, FromFormField};
fn len(&self) -> usize { self.len() } ///
} /// #[derive(FromFormField, PartialEq)]
/// enum Kind {
impl<T> Len for Vec<T> { /// Car,
fn len(&self) -> usize { <Vec<T>>::len(self) } /// Truck
} /// }
///
impl Len for TempFile<'_> { /// #[derive(FromForm)]
fn len(&self) -> usize { TempFile::len(self) as usize } /// struct Foo<'r> {
/// #[field(validate = neq("Bob Marley"))]
fn len_u64(&self) -> u64 { TempFile::len(self) } /// name: &'r str,
} /// #[field(validate = neq(Kind::Car))]
/// vehicle: Kind,
impl<K, V> Len for std::collections::HashMap<K, V> { /// #[field(validate = neq(&[5, 7, 8]))]
fn len(&self) -> usize { <std::collections::HashMap<K, V>>::len(self) } /// numbers: Vec<usize>,
} /// }
/// ```
impl<T: Len + ?Sized> Len for &T { pub fn neq<'v, A, B>(a: &A, b: B) -> Result<'v, ()>
fn len(&self) -> usize { where A: PartialEq<B>
<T as Len>::len(self)
}
}
pub fn len<'v, V, R>(value: V, range: R) -> Result<(), Errors<'v>>
where V: Len, R: RangeBounds<u64>
{ {
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() { let start = match range.start_bound() {
Bound::Included(v) => Some(*v), Bound::Included(v) => Some(V::len_into_u64(*v)),
Bound::Excluded(v) => Some(v.saturating_add(1)), Bound::Excluded(v) => Some(V::len_into_u64(*v).saturating_add(1)),
Bound::Unbounded => None Bound::Unbounded => None
}; };
let end = match range.end_bound() { let end = match range.end_bound() {
Bound::Included(v) => Some(*v), Bound::Included(v) => Some(V::len_into_u64(*v)),
Bound::Excluded(v) => Some(v.saturating_sub(1)), Bound::Excluded(v) => Some(V::len_into_u64(*v).saturating_sub(1)),
Bound::Unbounded => None, Bound::Unbounded => None,
}; };
@ -72,87 +336,42 @@ pub fn len<'v, V, R>(value: V, range: R) -> Result<(), Errors<'v>>
Ok(()) 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> { pub trait Contains<I> {
/// Returns `true` if `self` contains `item`.
fn contains(&self, item: I) -> bool; fn contains(&self, item: I) -> bool;
} }
impl<I, T: Contains<I>> Contains<I> for &T { macro_rules! impl_contains {
fn contains(&self, item: I) -> bool { ([$($gen:tt)*] $T:ty [contains] $I:ty [via] $P:ty) => {
<T as Contains<I>>::contains(self, item) 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 { impl_contains!([] str [contains] &str [via] str);
fn contains(&self, string: &str) -> bool { impl_contains!([] str [contains] char [via] str);
<str>::contains(self, string) 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 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<T: PartialEq> Contains<T> for Vec<T> { impl<T: PartialEq> Contains<T> for Vec<T> {
fn contains(&self, item: T) -> bool { 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> { impl<I, T: Contains<I>> Contains<I> for Option<T> {
fn contains(&self, item: &T) -> bool { fn contains(&self, item: I) -> bool {
<[T]>::contains(self, item) self.as_ref().map(|v| v.contains(item)).unwrap_or(false)
} }
} }
pub fn contains<'v, V, I>(value: V, item: I) -> Result<(), Errors<'v>> impl<I, T: Contains<I>> Contains<I> for Result<'_, T> {
where V: for<'a> Contains<&'a I>, I: std::fmt::Debug 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) { if !value.contains(item) {
Err(Error::validation(format!("must contain {:?}", item)))? Err(Error::validation("value does not contain expected item"))?
} }
Ok(()) Ok(())
} }
pub fn omits<'v, V, I>(value: V, item: I) -> Result<(), Errors<'v>> /// Verbose contains validator: like `contains` but mentions `item` in the
where V: for<'a> Contains<&'a I>, I: std::fmt::Debug /// 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) { if !value.contains(item) {
Err(Error::validation(format!("cannot contain {:?}", item)))? Err(Error::validation(format!("value must contain {:?}", item)))?
} }
Ok(()) 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> where V: TryInto<isize> + Copy, R: RangeBounds<isize>
{ {
if let Ok(v) = (*value).try_into() { 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))? Err((start, end))?
} }
pub fn one_of<'v, V, I>(value: V, items: &[I]) -> Result<(), Errors<'v>> /// Contains one of validator: succeeds when a value contains at least one item
where V: for<'a> Contains<&'a I>, I: Display /// 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) { if value.contains(item) {
return Ok(()); return Ok(());
} }
} }
let choices = items.iter() let choices: Vec<Cow<'_, str>> = items
.map(|item| item.to_string().into()) .map(|item| format!("{:?}", item).into())
.collect::<Vec<Cow<'v, str>>>(); .collect();
Err(choices)? 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(file_ct) = file.content_type() {
if let Some(ext_ct) = ContentType::from_extension(ext) { if file_ct == &r#type {
if file_ct == &ext_ct {
return Ok(()); 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 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: Luhn validator for credit-card-like numbers, you might write:
```rust ```rust
# #[macro_use] extern crate rocket; # #[macro_use] extern crate rocket;
extern crate time; extern crate time;
use rocket::form::{self, Error};
#[derive(FromForm)] #[derive(FromForm)]
struct CreditCard<'v> { struct CreditCard {
#[field(validate = luhn())] #[field(validate = luhn(self.cvv, &self.expiration))]
number: &'v str, number: u64,
# #[field(validate = luhn())]
# other: String,
#[field(validate = range(..9999))] #[field(validate = range(..9999))]
cvv: u16, cvv: u16,
expiration: time::Date, expiration: time::Date,
} }
fn luhn<'v, S: AsRef<str>>(field: S) -> rocket::form::Result<'v, ()> { fn luhn<'v>(number: &u64, cvv: u16, exp: &time::Date) -> form::Result<'v, ()> {
let num = field.as_ref().parse::<u64>()?; # 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 ### Defaults
The [`FromForm`] trait allows types to specify a default value if one isn't The [`FromForm`] trait allows types to specify a default value if one isn't