mirror of https://github.com/rwf2/Rocket.git
parent
ebb9f3cfdd
commit
1e4db983e8
|
@ -26,6 +26,7 @@ glob = "0.3"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
rocket = { version = "0.5.0-dev", path = "../lib", features = ["json"] }
|
rocket = { version = "0.5.0-dev", path = "../lib", features = ["json"] }
|
||||||
|
pretty_assertions = "0.7"
|
||||||
version_check = "0.9"
|
version_check = "0.9"
|
||||||
trybuild = "1.0"
|
trybuild = "1.0"
|
||||||
time = "0.2.11"
|
time = "0.2.11"
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
use devise::{*, ext::{TypeExt, SpanDiagnosticExt}};
|
use devise::{*, ext::{TypeExt, SpanDiagnosticExt}};
|
||||||
|
|
||||||
use syn::visit_mut::VisitMut;
|
use syn::{visit_mut::VisitMut, visit::Visit};
|
||||||
use syn::visit::Visit;
|
|
||||||
use proc_macro2::{TokenStream, TokenTree, Span};
|
use proc_macro2::{TokenStream, TokenTree, Span};
|
||||||
|
use quote::{ToTokens, TokenStreamExt};
|
||||||
|
|
||||||
use crate::syn_ext::IdentExt;
|
use crate::syn_ext::IdentExt;
|
||||||
use crate::name::Name;
|
use crate::name::Name;
|
||||||
|
@ -17,7 +17,8 @@ pub enum FieldName {
|
||||||
pub struct FieldAttr {
|
pub struct FieldAttr {
|
||||||
pub name: Option<FieldName>,
|
pub name: Option<FieldName>,
|
||||||
pub validate: Option<SpanWrapped<syn::Expr>>,
|
pub validate: Option<SpanWrapped<syn::Expr>>,
|
||||||
pub default: Option<SpanWrapped<syn::Expr>>,
|
pub default: Option<syn::Expr>,
|
||||||
|
pub default_with: Option<syn::Expr>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FieldAttr {
|
impl FieldAttr {
|
||||||
|
@ -127,7 +128,7 @@ impl std::ops::Deref for FieldName {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl quote::ToTokens for FieldName {
|
impl ToTokens for FieldName {
|
||||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||||
(self as &Name).to_tokens(tokens)
|
(self as &Name).to_tokens(tokens)
|
||||||
}
|
}
|
||||||
|
@ -208,7 +209,6 @@ struct ValidationMutator<'a> {
|
||||||
|
|
||||||
impl ValidationMutator<'_> {
|
impl ValidationMutator<'_> {
|
||||||
fn visit_token_stream(&mut self, tt: TokenStream) -> TokenStream {
|
fn visit_token_stream(&mut self, tt: TokenStream) -> TokenStream {
|
||||||
use quote::{ToTokens, TokenStreamExt};
|
|
||||||
use TokenTree::*;
|
use TokenTree::*;
|
||||||
|
|
||||||
let mut iter = tt.into_iter();
|
let mut iter = tt.into_iter();
|
||||||
|
@ -329,43 +329,62 @@ pub fn validators<'v>(
|
||||||
Ok(exprs)
|
Ok(exprs)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn default<'v>(field: Field<'v>) -> Result<Option<syn::Expr>> {
|
/// Take an $expr in `default = $expr` and turn it into a `Some($expr.into())`.
|
||||||
let mut exprs = FieldAttr::from_attrs(FieldAttr::NAME, &field.attrs)?
|
///
|
||||||
.into_iter()
|
/// As a result of calling `into()`, type inference fails for two common
|
||||||
.filter_map(|a| a.default)
|
/// expressions: integer literals and the bare `None`. As a result, we cheat: if
|
||||||
.map(move |expr| {
|
/// the expr matches either condition, we pass them through unchanged.
|
||||||
|
fn default_expr(expr: &syn::Expr) -> TokenStream {
|
||||||
use syn::{Expr, Lit, ExprLit};
|
use syn::{Expr, Lit, ExprLit};
|
||||||
// As a result of calling `#expr.into()`, type inference fails for
|
|
||||||
// two common expressions: integer literals and the bare `None`. As
|
|
||||||
// a result, we cheat: if the syntax matches either of these two
|
|
||||||
// conditions, we provide the field type as a hint.
|
|
||||||
let is_int_lit = matches!(*expr, Expr::Lit(ExprLit { lit: Lit::Int(_), .. }));
|
|
||||||
let is_none = matches!(*expr, Expr::Path(ref e) if e.path.is_ident("None"));
|
|
||||||
let ty = field.stripped_ty();
|
|
||||||
let ty_hint = (is_int_lit || is_none)
|
|
||||||
.then(|| quote!(#ty))
|
|
||||||
.unwrap_or_else(|| quote!(_));
|
|
||||||
let opt_expr = if is_none {
|
|
||||||
quote_spanned!(expr.span => None)
|
|
||||||
} else if is_int_lit {
|
|
||||||
quote_spanned!(expr.span => Some(#expr))
|
|
||||||
} else {
|
|
||||||
quote_spanned!(expr.span => Some({ #expr }.into()))
|
|
||||||
};
|
|
||||||
syn::parse2(quote_spanned!(expr.span => {
|
|
||||||
let __default: Option<#ty_hint> = #opt_expr;
|
|
||||||
__default
|
|
||||||
})).unwrap()
|
|
||||||
});
|
|
||||||
|
|
||||||
let first: Option<syn::Expr> = exprs.next();
|
if matches!(expr, Expr::Path(e) if e.path.is_ident("None")) {
|
||||||
if let Some(expr) = exprs.next() {
|
quote!(#expr)
|
||||||
return Err(expr.span()
|
} else if matches!(expr, Expr::Lit(ExprLit { lit: Lit::Int(_), .. })) {
|
||||||
.error("duplicate `default` form field attribute")
|
quote_spanned!(expr.span() => Some(#expr))
|
||||||
.help("form fields can have at most one `default`"));
|
} else {
|
||||||
|
quote_spanned!(expr.span() => Some({ #expr }.into()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn default<'v>(field: Field<'v>) -> Result<Option<TokenStream>> {
|
||||||
|
let attrs = FieldAttr::from_attrs(FieldAttr::NAME, &field.attrs)?;
|
||||||
|
|
||||||
|
// Expressions in `default = `, except for `None`, are wrapped in `Some()`.
|
||||||
|
let mut expr = attrs.iter().filter_map(|a| a.default.as_ref()).map(default_expr);
|
||||||
|
|
||||||
|
// Expressions in `default_with` are passed through directly.
|
||||||
|
let mut expr_with = attrs.iter()
|
||||||
|
.filter_map(|a| a.default_with.as_ref())
|
||||||
|
.map(|e| e.to_token_stream());
|
||||||
|
|
||||||
|
// Pull the first `default` and `default_with` expressions.
|
||||||
|
let (default, default_with) = (expr.next(), expr_with.next());
|
||||||
|
|
||||||
|
// If there are any more of either, emit an error.
|
||||||
|
if let (Some(e), _) | (_, Some(e)) = (expr.next(), expr_with.next()) {
|
||||||
|
return Err(e.span()
|
||||||
|
.error("duplicate default field expression")
|
||||||
|
.help("at most one `default` or `default_with` is allowed"));
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(first)
|
// Emit the final expression of type `Option<#ty>` unless both `default` and
|
||||||
|
// `default_with` were provided in which case we error.
|
||||||
|
let ty = field.stripped_ty();
|
||||||
|
match (default, default_with) {
|
||||||
|
(Some(e1), Some(e2)) => {
|
||||||
|
return Err(e1.span()
|
||||||
|
.error("duplicate default expressions")
|
||||||
|
.help("only one of `default` or `default_with` must be used")
|
||||||
|
.span_note(e2.span(), "other default expression is here"));
|
||||||
|
},
|
||||||
|
(Some(e), None) | (None, Some(e)) => {
|
||||||
|
Ok(Some(quote_spanned!(e.span() => {
|
||||||
|
let __default: Option<#ty> = #e;
|
||||||
|
__default
|
||||||
|
})))
|
||||||
|
},
|
||||||
|
(None, None) => Ok(None)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn first_duplicate<K: Spanned, V: PartialEq + Spanned>(
|
pub fn first_duplicate<K: Spanned, V: PartialEq + Spanned>(
|
||||||
|
|
|
@ -230,36 +230,33 @@ pub fn derive_from_form(input: proc_macro::TokenStream) -> TokenStream {
|
||||||
.try_field_map(|_, f| {
|
.try_field_map(|_, f| {
|
||||||
let (ident, ty, name_view) = (f.ident(), f.stripped_ty(), f.name_view()?);
|
let (ident, ty, name_view) = (f.ident(), f.stripped_ty(), f.name_view()?);
|
||||||
let validator = validators(f, &ident, true)?;
|
let validator = validators(f, &ident, true)?;
|
||||||
let user_default = default(f)?;
|
let default = default(f)?
|
||||||
let default = user_default
|
.unwrap_or_else(|| quote_spanned!(ty.span() => {
|
||||||
.map(|expr| quote_spanned!(ty.span() =>
|
<#ty as #_form::FromForm<'__f>>::default(__opts)
|
||||||
#expr.or_else(|| <#ty as #_form::FromForm<'__f>>::default(_opts))
|
}));
|
||||||
.ok_or_else(|| #_form::ErrorKind::Missing.into())
|
|
||||||
))
|
|
||||||
.unwrap_or_else(|| quote_spanned!(ty.span() =>
|
|
||||||
<#ty as #_form::FromForm<'__f>>::default(_opts)
|
|
||||||
.ok_or_else(|| #_form::ErrorKind::Missing.into())
|
|
||||||
));
|
|
||||||
let _err = _Err;
|
let _err = _Err;
|
||||||
Ok(quote_spanned! { ty.span() => {
|
Ok(quote_spanned! { ty.span() => {
|
||||||
let _name = #name_view;
|
let __name = #name_view;
|
||||||
let _opts = __c.__opts;
|
let __opts = __c.__opts;
|
||||||
__c.#ident
|
__c.#ident
|
||||||
.map(<#ty as #_form::FromForm<'__f>>::finalize)
|
.map(<#ty as #_form::FromForm<'__f>>::finalize)
|
||||||
.unwrap_or_else(|| #default)
|
.unwrap_or_else(|| {
|
||||||
|
#default.ok_or_else(|| #_form::ErrorKind::Missing.into())
|
||||||
|
})
|
||||||
.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); })*
|
||||||
|
|
||||||
match _es.is_empty() {
|
match __es.is_empty() {
|
||||||
true => #_Ok(#ident),
|
true => #_Ok(#ident),
|
||||||
false => #_Err(_es)
|
false => #_Err(__es)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.map_err(|_e| _e.with_name(_name))
|
.map_err(|__e| __e.with_name(__name))
|
||||||
.map_err(|_e| match _e.is_empty() {
|
.map_err(|__e| match __e.is_empty() {
|
||||||
true => #_form::ErrorKind::Unknown.into(),
|
true => #_form::ErrorKind::Unknown.into(),
|
||||||
false => _e,
|
false => __e,
|
||||||
})
|
})
|
||||||
}})
|
}})
|
||||||
})
|
})
|
||||||
|
|
|
@ -549,7 +549,6 @@ pub fn derive_from_form_field(input: TokenStream) -> TokenStream {
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # #[macro_use] extern crate rocket;
|
/// # #[macro_use] extern crate rocket;
|
||||||
/// #
|
|
||||||
/// #[derive(FromForm)]
|
/// #[derive(FromForm)]
|
||||||
/// struct MyStruct<'r> {
|
/// struct MyStruct<'r> {
|
||||||
/// field: usize,
|
/// field: usize,
|
||||||
|
@ -558,7 +557,7 @@ pub fn derive_from_form_field(input: TokenStream) -> TokenStream {
|
||||||
/// other: &'r str,
|
/// other: &'r str,
|
||||||
/// #[field(validate = range(1..), default = 3)]
|
/// #[field(validate = range(1..), default = 3)]
|
||||||
/// r#type: usize,
|
/// r#type: usize,
|
||||||
/// #[field(default = true)]
|
/// #[field(default = None)]
|
||||||
/// is_nice: bool,
|
/// is_nice: bool,
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
@ -569,8 +568,8 @@ pub fn derive_from_form_field(input: TokenStream) -> TokenStream {
|
||||||
/// implementation parses a form whose field names match the field names of the
|
/// implementation parses a form whose field names match the field names of the
|
||||||
/// structure on which the derive was applied. Each field's value is parsed with
|
/// structure on which the derive was applied. Each field's value is parsed with
|
||||||
/// the [`FromForm`] implementation of the field's type. The `FromForm`
|
/// the [`FromForm`] implementation of the field's type. The `FromForm`
|
||||||
/// implementation succeeds only when all of the field parses succeed or return
|
/// implementation succeeds only when all fields parse successfully or return a
|
||||||
/// a default. Errors are collected into a [`form::Errors`] and return if
|
/// default. Errors are collected into a [`form::Errors`] and returned if
|
||||||
/// non-empty after parsing all fields.
|
/// non-empty after parsing all fields.
|
||||||
///
|
///
|
||||||
/// The derive accepts one field attribute: `field`, with the following syntax:
|
/// The derive accepts one field attribute: `field`, with the following syntax:
|
||||||
|
@ -578,26 +577,28 @@ pub fn derive_from_form_field(input: TokenStream) -> TokenStream {
|
||||||
/// ```text
|
/// ```text
|
||||||
/// field := name? default? validate*
|
/// field := name? default? validate*
|
||||||
///
|
///
|
||||||
/// name := 'name' '=' name_val
|
/// name := 'name' '=' name_val ','?
|
||||||
/// name_val := '"' FIELD_NAME '"'
|
/// name_val := '"' FIELD_NAME '"'
|
||||||
/// | 'uncased(' '"' FIELD_NAME '"' ')
|
/// | 'uncased(' '"' FIELD_NAME '"' ')
|
||||||
///
|
///
|
||||||
/// default := 'default' '=' EXPR
|
/// default := 'default' '=' EXPR ','?
|
||||||
|
/// | 'default_with' '=' EXPR ','?
|
||||||
///
|
///
|
||||||
/// validate := 'validate' '=' EXPR
|
/// validate := 'validate' '=' EXPR ','?
|
||||||
///
|
///
|
||||||
/// FIELD_NAME := valid field name, according to the HTML5 spec
|
/// FIELD_NAME := valid field name, according to the HTML5 spec
|
||||||
/// EXPR := valid expression, as defined by Rust
|
/// EXPR := valid expression, as defined by Rust
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// The attribute can be applied any number of times on a field. When applied,
|
/// The attribute can be applied any number of times on a field as long as at
|
||||||
/// the attribute looks as follows:
|
/// most _one_ of `default` or `default_with` is present per field:
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # #[macro_use] extern crate rocket;
|
/// # #[macro_use] extern crate rocket;
|
||||||
/// #
|
|
||||||
/// #[derive(FromForm)]
|
/// #[derive(FromForm)]
|
||||||
/// struct MyStruct {
|
/// struct MyStruct {
|
||||||
|
/// #[field(name = uncased("number"))]
|
||||||
|
/// #[field(default = 42)]
|
||||||
/// field: usize,
|
/// field: usize,
|
||||||
/// #[field(name = "renamed_field")]
|
/// #[field(name = "renamed_field")]
|
||||||
/// #[field(name = uncased("anotherName"))]
|
/// #[field(name = uncased("anotherName"))]
|
||||||
|
@ -615,12 +616,42 @@ pub fn derive_from_form_field(input: TokenStream) -> TokenStream {
|
||||||
/// case-preserving. When more than one `name` attribute is applied, the field
|
/// case-preserving. When more than one `name` attribute is applied, the field
|
||||||
/// will match against _any_ of the names.
|
/// will match against _any_ of the names.
|
||||||
///
|
///
|
||||||
/// **`validate`**
|
/// **`validate = expr`**
|
||||||
///
|
///
|
||||||
/// The validation expression will be run if the field type parses successfully.
|
/// The validation `expr` is run if the field type parses successfully. The
|
||||||
/// The expression must return a value of type `Result<(), form::Errors>`. On
|
/// expression must return a value of type `Result<(), form::Errors>`. On `Err`,
|
||||||
/// `Err`, the errors are added to the thus-far collected errors. If more than
|
/// the errors are added to the thus-far collected errors. If more than one
|
||||||
/// one `validate` attribute is applied, _all_ validations are run.
|
/// `validate` attribute is applied, _all_ validations are run.
|
||||||
|
///
|
||||||
|
/// **`default = expr`**
|
||||||
|
///
|
||||||
|
/// If `expr` is not literally `None`, the parameter sets the default value of
|
||||||
|
/// the field to be `expr.into()`. If `expr` _is_ `None`, the parameter _unsets_
|
||||||
|
/// the default value of the field, if any. The expression is only evaluated if
|
||||||
|
/// the attributed field is missing in the incoming form.
|
||||||
|
///
|
||||||
|
/// Except when `expr` is `None`, `expr` must be of type `T: Into<F>` where `F`
|
||||||
|
/// is the field's type.
|
||||||
|
///
|
||||||
|
/// **`default_with = expr`**
|
||||||
|
///
|
||||||
|
/// The parameter sets the default value of the field to be exactly `expr` which
|
||||||
|
/// must be of type `Option<F>` where `F` is the field's type. If the expression
|
||||||
|
/// evaluates to `None`, there is no default. Otherwise the value wrapped in
|
||||||
|
/// `Some` is used. The expression is only evaluated if the attributed field is
|
||||||
|
/// missing in the incoming form.
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # #[macro_use] extern crate rocket;
|
||||||
|
/// use std::num::NonZeroUsize;
|
||||||
|
///
|
||||||
|
/// #[derive(FromForm)]
|
||||||
|
/// struct MyForm {
|
||||||
|
/// // `NonZeroUsize::new()` return an `Option<NonZeroUsize>`.
|
||||||
|
/// #[field(default_with = NonZeroUsize::new(42))]
|
||||||
|
/// num: NonZeroUsize,
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
///
|
///
|
||||||
/// [`FromForm`]: rocket::form::FromForm
|
/// [`FromForm`]: rocket::form::FromForm
|
||||||
/// [`form::Errors`]: rocket::form::Errors
|
/// [`form::Errors`]: rocket::form::Errors
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
use std::{collections::{BTreeMap, HashMap}, net::{IpAddr, SocketAddr}, num::NonZeroI32};
|
use std::net::{IpAddr, SocketAddr};
|
||||||
|
use std::collections::{BTreeMap, HashMap};
|
||||||
|
use pretty_assertions::assert_eq;
|
||||||
|
|
||||||
use rocket::form::{self, Form, Strict, FromForm, FromFormField, Errors};
|
use rocket::form::{self, Form, Strict, FromForm, FromFormField, Errors};
|
||||||
|
|
||||||
|
@ -615,153 +617,90 @@ fn test_multipart() {
|
||||||
assert!(response.status().class().is_success());
|
assert!(response.status().class().is_success());
|
||||||
}
|
}
|
||||||
|
|
||||||
fn test_hashmap() -> HashMap<&'static str, &'static str> {
|
#[test]
|
||||||
let mut map = HashMap::new();
|
fn test_default_removed() {
|
||||||
map.insert("key", "value");
|
#[derive(FromForm, PartialEq, Debug)]
|
||||||
map
|
struct FormNoDefault {
|
||||||
}
|
|
||||||
|
|
||||||
fn test_btreemap() -> BTreeMap<&'static str, &'static str> {
|
|
||||||
let mut map = BTreeMap::new();
|
|
||||||
map.insert("key", "value");
|
|
||||||
map
|
|
||||||
}
|
|
||||||
|
|
||||||
mod default_macro_tests {
|
|
||||||
#![allow(dead_code)]
|
|
||||||
|
|
||||||
#[derive(rocket::FromForm)]
|
|
||||||
struct Form1 {
|
|
||||||
#[field(default = 10)]
|
|
||||||
field: i8,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(rocket::FromForm)]
|
|
||||||
struct Form2 {
|
|
||||||
#[field(default = 10)]
|
|
||||||
field: u8,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(rocket::FromForm)]
|
|
||||||
struct Form3 {
|
|
||||||
#[field(default = 10)]
|
|
||||||
field: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(rocket::FromForm)]
|
|
||||||
struct Form4 {
|
|
||||||
#[field(default = 10)]
|
|
||||||
field: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(rocket::FromForm)]
|
|
||||||
struct Form5 {
|
|
||||||
#[field(default = None)]
|
#[field(default = None)]
|
||||||
field: usize,
|
field1: bool,
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(rocket::FromForm)]
|
|
||||||
struct Form6 {
|
|
||||||
#[field(default = None)]
|
#[field(default = None)]
|
||||||
field: usize,
|
field2: Option<usize>,
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(rocket::FromForm)]
|
|
||||||
struct Form7 {
|
|
||||||
#[field(default = None)]
|
#[field(default = None)]
|
||||||
field: String,
|
field3: Option<Option<usize>>,
|
||||||
|
#[field(default_with = None)]
|
||||||
|
field4: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(rocket::FromForm)]
|
let form_string = &["field1=false", "field2=10", "field3=23", "field4"].join("&");
|
||||||
struct Form8<'a> {
|
let form1: Option<FormNoDefault> = lenient(&form_string).ok();
|
||||||
#[field(default = None)]
|
assert_eq!(form1, Some(FormNoDefault {
|
||||||
field: &'a str,
|
field1: false,
|
||||||
}
|
field2: Some(10),
|
||||||
|
field3: Some(Some(23)),
|
||||||
|
field4: true,
|
||||||
|
}));
|
||||||
|
|
||||||
// #[derive(rocket::FromForm)]
|
let form_string = &["field1=true", "field2=10", "field3=23", "field4"].join("&");
|
||||||
// struct Form9<'a> {
|
let form1: Option<FormNoDefault> = lenient(&form_string).ok();
|
||||||
// #[field(default = None)]
|
assert_eq!(form1, Some(FormNoDefault {
|
||||||
// field: Cow<'a, str>,
|
field1: true,
|
||||||
// }
|
field2: Some(10),
|
||||||
|
field3: Some(Some(23)),
|
||||||
|
field4: true,
|
||||||
|
}));
|
||||||
|
|
||||||
#[derive(rocket::FromForm)]
|
// Field 1 missing.
|
||||||
struct Form10 {
|
let form_string = &["field2=20", "field3=10", "field4"].join("&");
|
||||||
#[field(default = "string")]
|
assert!(lenient::<FormNoDefault>(&form_string).is_err());
|
||||||
field: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(rocket::FromForm)]
|
// Field 2 missing.
|
||||||
struct Form11<'a> {
|
let form_string = &["field1=true", "field3=10", "field4"].join("&");
|
||||||
#[field(default = "string")]
|
assert!(lenient::<FormNoDefault>(&form_string).is_err());
|
||||||
field: &'a str,
|
|
||||||
}
|
|
||||||
|
|
||||||
// #[derive(rocket::FromForm)]
|
// Field 3 missing.
|
||||||
// struct Form12 {
|
let form_string = &["field1=true", "field2=10", "field4=false"].join("&");
|
||||||
// #[field(default = "string")]
|
assert!(lenient::<FormNoDefault>(&form_string).is_err());
|
||||||
// field: Cow<'static, str>,
|
|
||||||
// }
|
|
||||||
|
|
||||||
// #[derive(rocket::FromForm)]
|
// Field 4 missing.
|
||||||
// struct Form13<'a> {
|
let form_string = &["field1=true", "field2=10", "field3=23"].join("&");
|
||||||
// #[field(default = "string".to_string())]
|
assert!(lenient::<FormNoDefault>(&form_string).is_err());
|
||||||
// field: Cow<'a, str>,
|
|
||||||
// }
|
|
||||||
|
|
||||||
#[derive(rocket::FromForm)]
|
|
||||||
struct Form14 {
|
|
||||||
#[field(default = 'a')]
|
|
||||||
field: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(rocket::FromForm)]
|
|
||||||
struct Form15 {
|
|
||||||
#[field(default = 10 + 2)]
|
|
||||||
field: u8,
|
|
||||||
}
|
|
||||||
#[derive(rocket::FromForm)]
|
|
||||||
struct Form16 {
|
|
||||||
#[field(default = 10 + 2)]
|
|
||||||
field: i8,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(rocket::FromForm)]
|
|
||||||
struct Form17 {
|
|
||||||
#[field(default = 10 + 2)]
|
|
||||||
field: i64,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(rocket::FromForm)]
|
|
||||||
struct Form18 {
|
|
||||||
#[field(default = 10 + 2usize)]
|
|
||||||
field: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(rocket::FromForm)]
|
|
||||||
struct Form19 {
|
|
||||||
#[field(default = 10 + 2usize)]
|
|
||||||
field: usize,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_defaults() {
|
fn test_defaults() {
|
||||||
|
fn test_hashmap() -> HashMap<&'static str, &'static str> {
|
||||||
|
let mut map = HashMap::new();
|
||||||
|
map.insert("key", "value");
|
||||||
|
map
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_btreemap() -> BTreeMap<&'static str, &'static str> {
|
||||||
|
let mut map = BTreeMap::new();
|
||||||
|
map.insert("key", "value");
|
||||||
|
map
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(FromForm, PartialEq, Debug)]
|
#[derive(FromForm, PartialEq, Debug)]
|
||||||
struct FormWithDefaults<'a> {
|
struct FormWithDefaults<'a> {
|
||||||
|
field2: i128,
|
||||||
|
field5: bool,
|
||||||
|
|
||||||
#[field(default = 100)]
|
#[field(default = 100)]
|
||||||
field1: usize,
|
field1: usize,
|
||||||
field2: i128,
|
|
||||||
|
|
||||||
#[field(default = true)]
|
#[field(default = true)]
|
||||||
field3: bool,
|
field3: bool,
|
||||||
#[field(default = false)]
|
#[field(default = false)]
|
||||||
field4: bool,
|
field4: bool,
|
||||||
field5: bool,
|
#[field(default = 254 + 1)]
|
||||||
|
field6: u8,
|
||||||
#[field(default = Some(true))]
|
#[field(default = Some(true))]
|
||||||
opt: Option<bool>,
|
opt1: Option<bool>,
|
||||||
#[field(default = Ok("hello".to_string()))]
|
#[field(default = false)]
|
||||||
|
opt2: Option<bool>,
|
||||||
|
#[field(default = Ok("hello".into()))]
|
||||||
res: form::Result<'a, String>,
|
res: form::Result<'a, String>,
|
||||||
|
#[field(default = Ok("hello"))]
|
||||||
|
res2: form::Result<'a, &'a str>,
|
||||||
#[field(default = vec![1, 2, 3])]
|
#[field(default = vec![1, 2, 3])]
|
||||||
vec_num: Vec<usize>,
|
vec_num: Vec<usize>,
|
||||||
#[field(default = vec!["wow", "a", "string", "nice"])]
|
#[field(default = vec!["wow", "a", "string", "nice"])]
|
||||||
|
@ -770,12 +709,14 @@ fn test_defaults() {
|
||||||
hashmap: HashMap<&'a str, &'a str>,
|
hashmap: HashMap<&'a str, &'a str>,
|
||||||
#[field(default = test_btreemap())]
|
#[field(default = test_btreemap())]
|
||||||
btreemap: BTreeMap<&'a str, &'a str>,
|
btreemap: BTreeMap<&'a str, &'a str>,
|
||||||
#[field(default = false)]
|
#[field(default_with = Some(false))]
|
||||||
boolean: bool,
|
boolean: bool,
|
||||||
#[field(default = 3)]
|
#[field(default_with = (|| Some(777))())]
|
||||||
unsigned: usize,
|
unsigned: usize,
|
||||||
#[field(default = NonZeroI32::new(3).unwrap())]
|
#[field(default = std::num::NonZeroI32::new(3).unwrap())]
|
||||||
nonzero: NonZeroI32,
|
nonzero: std::num::NonZeroI32,
|
||||||
|
#[field(default_with = std::num::NonZeroI32::new(9001))]
|
||||||
|
nonzero2: std::num::NonZeroI32,
|
||||||
#[field(default = 3.0)]
|
#[field(default = 3.0)]
|
||||||
float: f64,
|
float: f64,
|
||||||
#[field(default = "wow")]
|
#[field(default = "wow")]
|
||||||
|
@ -797,24 +738,31 @@ fn test_defaults() {
|
||||||
datetime: time::PrimitiveDateTime,
|
datetime: time::PrimitiveDateTime,
|
||||||
}
|
}
|
||||||
|
|
||||||
let form_string = &["field1=101", "field2=102"].join("&");
|
// `field2` has no default.
|
||||||
|
assert!(lenient::<FormWithDefaults>("").is_err());
|
||||||
|
|
||||||
|
// every other field should.
|
||||||
|
let form_string = &["field2=102"].join("&");
|
||||||
let form1: Option<FormWithDefaults> = lenient(&form_string).ok();
|
let form1: Option<FormWithDefaults> = lenient(&form_string).ok();
|
||||||
assert_eq!(form1, Some(FormWithDefaults {
|
assert_eq!(form1, Some(FormWithDefaults {
|
||||||
field1: 101,
|
field1: 100,
|
||||||
field2: 102,
|
field2: 102,
|
||||||
field3: true,
|
field3: true,
|
||||||
field4: false,
|
field4: false,
|
||||||
field5: false,
|
field5: false,
|
||||||
opt: Some(true),
|
field6: 255,
|
||||||
res: Ok("hello".to_string()),
|
opt1: Some(true),
|
||||||
|
opt2: Some(false),
|
||||||
|
res: Ok("hello".into()),
|
||||||
|
res2: Ok("hello"),
|
||||||
vec_num: vec![1, 2, 3],
|
vec_num: vec![1, 2, 3],
|
||||||
vec_str: vec!["wow", "a", "string", "nice"],
|
vec_str: vec!["wow", "a", "string", "nice"],
|
||||||
hashmap: test_hashmap(),
|
hashmap: test_hashmap(),
|
||||||
btreemap: test_btreemap(),
|
btreemap: test_btreemap(),
|
||||||
boolean: false,
|
boolean: false,
|
||||||
unsigned: 3,
|
unsigned: 777,
|
||||||
nonzero: NonZeroI32::new(3).unwrap(),
|
nonzero: std::num::NonZeroI32::new(3).unwrap(),
|
||||||
|
nonzero2: std::num::NonZeroI32::new(9001).unwrap(),
|
||||||
float: 3.0,
|
float: 3.0,
|
||||||
str_ref: "wow",
|
str_ref: "wow",
|
||||||
string: "wowie".to_string(),
|
string: "wowie".to_string(),
|
||||||
|
@ -824,16 +772,12 @@ fn test_defaults() {
|
||||||
time: time::time!(01:15:00),
|
time: time::time!(01:15:00),
|
||||||
datetime: time::PrimitiveDateTime::new(time::date!(2021-05-27), time::time!(01:15:00)),
|
datetime: time::PrimitiveDateTime::new(time::date!(2021-05-27), time::time!(01:15:00)),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
let form2: Option<FormWithDefaults> = strict(&form_string).ok();
|
let form2: Option<FormWithDefaults> = strict(&form_string).ok();
|
||||||
assert!(form2.is_none());
|
assert!(form2.is_none());
|
||||||
|
|
||||||
let form_string = &[
|
// Ensure actual form field values take precendence.
|
||||||
"field1=101",
|
let form_string = &["field1=101", "field2=102", "field3=true", "field5=true"].join("&");
|
||||||
"field2=102",
|
|
||||||
"field3=true",
|
|
||||||
"field5=true"
|
|
||||||
].join("&");
|
|
||||||
|
|
||||||
let form3: Option<FormWithDefaults> = lenient(&form_string).ok();
|
let form3: Option<FormWithDefaults> = lenient(&form_string).ok();
|
||||||
assert_eq!(form3, Some(FormWithDefaults {
|
assert_eq!(form3, Some(FormWithDefaults {
|
||||||
field1: 101,
|
field1: 101,
|
||||||
|
@ -841,8 +785,10 @@ fn test_defaults() {
|
||||||
field3: true,
|
field3: true,
|
||||||
field4: false,
|
field4: false,
|
||||||
field5: true,
|
field5: true,
|
||||||
..form1.unwrap() // cannot fail due to assertio and keeps test more concise
|
..form1.unwrap()
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
// And that strict parsing still works.
|
||||||
let form4: Option<FormWithDefaults> = strict(&form_string).ok();
|
let form4: Option<FormWithDefaults> = strict(&form_string).ok();
|
||||||
assert_eq!(form4, Some(FormWithDefaults {
|
assert_eq!(form4, Some(FormWithDefaults {
|
||||||
field1: 101,
|
field1: 101,
|
||||||
|
@ -850,6 +796,49 @@ fn test_defaults() {
|
||||||
field3: true,
|
field3: true,
|
||||||
field4: false,
|
field4: false,
|
||||||
field5: true,
|
field5: true,
|
||||||
..form3.unwrap() // cannot fail due to assertio and keeps test more concise
|
..form3.unwrap()
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_lazy_default() {
|
||||||
|
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||||
|
|
||||||
|
static VAL: AtomicUsize = AtomicUsize::new(40);
|
||||||
|
|
||||||
|
fn f() -> usize {
|
||||||
|
VAL.fetch_add(1, Ordering::Relaxed)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn opt_f() -> Option<usize> {
|
||||||
|
Some(f())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, FromForm)]
|
||||||
|
struct MyForm {
|
||||||
|
#[field(default = f())]
|
||||||
|
missing0: usize,
|
||||||
|
#[field(default = VAL.load(Ordering::Relaxed))]
|
||||||
|
missing1: usize,
|
||||||
|
#[field(default_with = opt_f())]
|
||||||
|
missing2: usize,
|
||||||
|
#[field(default_with = opt_f())]
|
||||||
|
a: usize,
|
||||||
|
#[field(default = f())]
|
||||||
|
b: usize,
|
||||||
|
#[field(default = VAL.load(Ordering::Relaxed))]
|
||||||
|
missing3: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure actual form field values take precendence.
|
||||||
|
let form_string = &["a=100", "b=300"].join("&");
|
||||||
|
let form3: Option<MyForm> = lenient(&form_string).ok();
|
||||||
|
assert_eq!(form3, Some(MyForm {
|
||||||
|
missing0: 40,
|
||||||
|
missing1: 41,
|
||||||
|
missing2: 41,
|
||||||
|
a: 100,
|
||||||
|
b: 300,
|
||||||
|
missing3: 42
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
|
@ -376,6 +376,58 @@ note: error occurred while deriving `FromForm`
|
||||||
| ^^^^^^^^
|
| ^^^^^^^^
|
||||||
= note: this error originates in the derive macro `FromForm` (in Nightly builds, run with -Z macro-backtrace for more info)
|
= note: this error originates in the derive macro `FromForm` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||||
|
|
||||||
|
error: duplicate default field expression
|
||||||
|
--> $DIR/from_form.rs:181:23
|
||||||
|
|
|
||||||
|
181 | #[field(default = 2)]
|
||||||
|
| ^
|
||||||
|
|
|
||||||
|
= help: at most one `default` or `default_with` is allowed
|
||||||
|
note: error occurred while deriving `FromForm`
|
||||||
|
--> $DIR/from_form.rs:178:10
|
||||||
|
|
|
||||||
|
178 | #[derive(FromForm)]
|
||||||
|
| ^^^^^^^^
|
||||||
|
= note: this error originates in the derive macro `FromForm` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||||
|
|
||||||
|
error: duplicate default expressions
|
||||||
|
--> $DIR/from_form.rs:187:23
|
||||||
|
|
|
||||||
|
187 | #[field(default = 1, default_with = None)]
|
||||||
|
| ^
|
||||||
|
|
|
||||||
|
= help: only one of `default` or `default_with` must be used
|
||||||
|
note: other default expression is here
|
||||||
|
--> $DIR/from_form.rs:187:41
|
||||||
|
|
|
||||||
|
187 | #[field(default = 1, default_with = None)]
|
||||||
|
| ^^^^
|
||||||
|
note: error occurred while deriving `FromForm`
|
||||||
|
--> $DIR/from_form.rs:185:10
|
||||||
|
|
|
||||||
|
185 | #[derive(FromForm)]
|
||||||
|
| ^^^^^^^^
|
||||||
|
= note: this error originates in the derive macro `FromForm` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||||
|
|
||||||
|
error: duplicate default expressions
|
||||||
|
--> $DIR/from_form.rs:194:23
|
||||||
|
|
|
||||||
|
194 | #[field(default = 1)]
|
||||||
|
| ^
|
||||||
|
|
|
||||||
|
= help: only one of `default` or `default_with` must be used
|
||||||
|
note: other default expression is here
|
||||||
|
--> $DIR/from_form.rs:193:28
|
||||||
|
|
|
||||||
|
193 | #[field(default_with = None)]
|
||||||
|
| ^^^^
|
||||||
|
note: error occurred while deriving `FromForm`
|
||||||
|
--> $DIR/from_form.rs:191:10
|
||||||
|
|
|
||||||
|
191 | #[derive(FromForm)]
|
||||||
|
| ^^^^^^^^
|
||||||
|
= note: this error originates in the derive macro `FromForm` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||||
|
|
||||||
error[E0425]: cannot find function `unknown` in this scope
|
error[E0425]: cannot find function `unknown` in this scope
|
||||||
--> $DIR/from_form.rs:150:24
|
--> $DIR/from_form.rs:150:24
|
||||||
|
|
|
|
||||||
|
@ -430,3 +482,16 @@ error[E0277]: the trait bound `i32: From<&str>` is not satisfied
|
||||||
<i32 as From<i8>>
|
<i32 as From<i8>>
|
||||||
and 5 others
|
and 5 others
|
||||||
= note: required because of the requirements on the impl of `Into<i32>` for `&str`
|
= note: required because of the requirements on the impl of `Into<i32>` for `&str`
|
||||||
|
|
||||||
|
error[E0308]: mismatched types
|
||||||
|
--> $DIR/from_form.rs:200:33
|
||||||
|
|
|
||||||
|
200 | #[field(default_with = Some("hi"))]
|
||||||
|
| ^^^^ expected struct `std::string::String`, found `&str`
|
||||||
|
|
|
||||||
|
help: try using a conversion method
|
||||||
|
|
|
||||||
|
200 | #[field(default_with = Some("hi".to_string()))]
|
||||||
|
| ^^^^^^^^^^^^^^^^
|
||||||
|
200 | #[field(default_with = Some("hi".to_string()))]
|
||||||
|
| ^^^^^^^^^^^^^^^^
|
||||||
|
|
|
@ -403,6 +403,63 @@ error: [note] error occurred while deriving `FromForm`
|
||||||
|
|
|
|
||||||
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
|
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||||
|
|
||||||
|
error: duplicate default field expression
|
||||||
|
--- help: at most one `default` or `default_with` is allowed
|
||||||
|
--> $DIR/from_form.rs:181:23
|
||||||
|
|
|
||||||
|
181 | #[field(default = 2)]
|
||||||
|
| ^
|
||||||
|
|
||||||
|
error: [note] error occurred while deriving `FromForm`
|
||||||
|
--> $DIR/from_form.rs:178:10
|
||||||
|
|
|
||||||
|
178 | #[derive(FromForm)]
|
||||||
|
| ^^^^^^^^
|
||||||
|
|
|
||||||
|
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||||
|
|
||||||
|
error: duplicate default expressions
|
||||||
|
--- help: only one of `default` or `default_with` must be used
|
||||||
|
--> $DIR/from_form.rs:187:23
|
||||||
|
|
|
||||||
|
187 | #[field(default = 1, default_with = None)]
|
||||||
|
| ^
|
||||||
|
|
||||||
|
error: [note] other default expression is here
|
||||||
|
--> $DIR/from_form.rs:187:41
|
||||||
|
|
|
||||||
|
187 | #[field(default = 1, default_with = None)]
|
||||||
|
| ^^^^
|
||||||
|
|
||||||
|
error: [note] error occurred while deriving `FromForm`
|
||||||
|
--> $DIR/from_form.rs:185:10
|
||||||
|
|
|
||||||
|
185 | #[derive(FromForm)]
|
||||||
|
| ^^^^^^^^
|
||||||
|
|
|
||||||
|
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||||
|
|
||||||
|
error: duplicate default expressions
|
||||||
|
--- help: only one of `default` or `default_with` must be used
|
||||||
|
--> $DIR/from_form.rs:194:23
|
||||||
|
|
|
||||||
|
194 | #[field(default = 1)]
|
||||||
|
| ^
|
||||||
|
|
||||||
|
error: [note] other default expression is here
|
||||||
|
--> $DIR/from_form.rs:193:28
|
||||||
|
|
|
||||||
|
193 | #[field(default_with = None)]
|
||||||
|
| ^^^^
|
||||||
|
|
||||||
|
error: [note] error occurred while deriving `FromForm`
|
||||||
|
--> $DIR/from_form.rs:191:10
|
||||||
|
|
|
||||||
|
191 | #[derive(FromForm)]
|
||||||
|
| ^^^^^^^^
|
||||||
|
|
|
||||||
|
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||||
|
|
||||||
error[E0425]: cannot find function `unknown` in this scope
|
error[E0425]: cannot find function `unknown` in this scope
|
||||||
--> $DIR/from_form.rs:150:24
|
--> $DIR/from_form.rs:150:24
|
||||||
|
|
|
|
||||||
|
@ -457,3 +514,16 @@ error[E0277]: the trait bound `i32: From<&str>` is not satisfied
|
||||||
<i32 as From<i8>>
|
<i32 as From<i8>>
|
||||||
and 5 others
|
and 5 others
|
||||||
= note: required because of the requirements on the impl of `Into<i32>` for `&str`
|
= note: required because of the requirements on the impl of `Into<i32>` for `&str`
|
||||||
|
|
||||||
|
error[E0308]: mismatched types
|
||||||
|
--> $DIR/from_form.rs:200:33
|
||||||
|
|
|
||||||
|
200 | #[field(default_with = Some("hi"))]
|
||||||
|
| ^^^^ expected struct `std::string::String`, found `&str`
|
||||||
|
|
|
||||||
|
help: try using a conversion method
|
||||||
|
|
|
||||||
|
200 | #[field(default_with = Some("hi".to_string()))]
|
||||||
|
| ^^^^^^^^^^^^^^^^
|
||||||
|
200 | #[field(default_with = Some("hi".to_string()))]
|
||||||
|
| ^^^^^^^^^^^^^^^^
|
||||||
|
|
|
@ -172,7 +172,33 @@ struct Default0 {
|
||||||
#[derive(FromForm)]
|
#[derive(FromForm)]
|
||||||
struct Default1 {
|
struct Default1 {
|
||||||
#[field(default = 1, default = 2)]
|
#[field(default = 1, default = 2)]
|
||||||
double_default: i32,
|
double_default: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(FromForm)]
|
||||||
|
struct Default2 {
|
||||||
|
#[field(default = 1)]
|
||||||
|
#[field(default = 2)]
|
||||||
|
double_default: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(FromForm)]
|
||||||
|
struct Default3 {
|
||||||
|
#[field(default = 1, default_with = None)]
|
||||||
|
double_default: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(FromForm)]
|
||||||
|
struct Default4 {
|
||||||
|
#[field(default_with = None)]
|
||||||
|
#[field(default = 1)]
|
||||||
|
double_default: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(FromForm)]
|
||||||
|
struct Default5 {
|
||||||
|
#[field(default_with = Some("hi"))]
|
||||||
|
no_conversion_from_with: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() { }
|
fn main() { }
|
||||||
|
|
|
@ -56,23 +56,6 @@ use crate::form::prelude::*;
|
||||||
/// can access fields of `T` transparently through a `Form<T>`, as seen above
|
/// can access fields of `T` transparently through a `Form<T>`, as seen above
|
||||||
/// with `user_input.value`.
|
/// with `user_input.value`.
|
||||||
///
|
///
|
||||||
/// ### Defaults
|
|
||||||
/// When deriving a `FromForm` implementation it is possible to provide a
|
|
||||||
/// default that is then used when no value is provided. For a form field of
|
|
||||||
/// type `T`, the provided default can be any expression of type `U` such that
|
|
||||||
/// `U` implements `Into<T>`.
|
|
||||||
///
|
|
||||||
/// ```rust
|
|
||||||
/// # #[macro_use] extern crate rocket;
|
|
||||||
/// use rocket::form::Form;
|
|
||||||
///
|
|
||||||
/// #[derive(FromForm)]
|
|
||||||
/// struct UserInput<'r> {
|
|
||||||
/// #[field(default = "anonymous")]
|
|
||||||
/// value: &'r str
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// ## Data Limits
|
/// ## Data Limits
|
||||||
///
|
///
|
||||||
/// The total amount of data accepted by the `Form` data guard is limited by the
|
/// The total amount of data accepted by the `Form` data guard is limited by the
|
||||||
|
|
|
@ -810,8 +810,35 @@ struct MyForm<'v> {
|
||||||
# rocket_guide_tests::assert_form_parses_ok!(MyForm, "");
|
# rocket_guide_tests::assert_form_parses_ok!(MyForm, "");
|
||||||
```
|
```
|
||||||
|
|
||||||
|
The default can be overridden or unset using the `#[field(default = expr)` field
|
||||||
|
attribute. If `expr` is not literally `None`, the parameter sets the default
|
||||||
|
value of the field to be `expr.into()`. If `expr` _is_ `None`, the parameter
|
||||||
|
_unsets_ the default value of the field, if any.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
# use rocket::form::FromForm;
|
||||||
|
|
||||||
|
#[derive(FromForm)]
|
||||||
|
struct MyForm {
|
||||||
|
// Set the default value to be `"hello"`.
|
||||||
|
//
|
||||||
|
// Note how an `&str` is automatically converted into a `String`.
|
||||||
|
#[field(default = "hello")]
|
||||||
|
greeting: String,
|
||||||
|
// Remove the default value of `false`, requiring all parses of `MyForm`
|
||||||
|
// to contain an `is_friendly` field.
|
||||||
|
#[field(default = None)]
|
||||||
|
is_friendly: bool,
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
See the [`FromForm` derive] documentation for full details on the `default`
|
||||||
|
attribute parameter as well documentation on the more expressive `default_with`
|
||||||
|
parameter option.
|
||||||
|
|
||||||
[`Errors<'_>`]: @api/rocket/form/struct.Errors.html
|
[`Errors<'_>`]: @api/rocket/form/struct.Errors.html
|
||||||
[`form::Result`]: @api/rocket/form/type.Result.html
|
[`form::Result`]: @api/rocket/form/type.Result.html
|
||||||
|
[`FromForm` derive]: @api/rocket/derive.FromForm.html
|
||||||
|
|
||||||
### Field Renaming
|
### Field Renaming
|
||||||
|
|
||||||
|
@ -956,47 +983,6 @@ 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
|
it is validated prior to those fields that do. For `CreditCard`, `cvv` and
|
||||||
`expiration` will be validated prior to `number`.
|
`expiration` will be validated prior to `number`.
|
||||||
|
|
||||||
### Defaults
|
|
||||||
|
|
||||||
The [`FromForm`] trait allows types to specify a default value if one isn't
|
|
||||||
provided in a submitted form. This includes types such as `bool`, useful for
|
|
||||||
checkboxes, and `Option<T>`. Additionally, `FromForm` is implemented for
|
|
||||||
`Result<T, Errors<'_>>` where the error value is [`Errors<'_>`]. All of these
|
|
||||||
types can be used just like any other form field:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
# use rocket::form::FromForm;
|
|
||||||
use rocket::form::Errors;
|
|
||||||
|
|
||||||
#[derive(FromForm)]
|
|
||||||
struct MyForm<'v> {
|
|
||||||
maybe_string: Option<String>,
|
|
||||||
ok_or_error: Result<Vec<String>, Errors<'v>>,
|
|
||||||
here: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
# rocket_guide_tests::assert_form_parses_ok!(MyForm, "");
|
|
||||||
```
|
|
||||||
|
|
||||||
Additionally, this default may be overridden when usng the `FromForm` derive
|
|
||||||
macro. The default specified in the macro is applied both in `Lenient` and
|
|
||||||
`Strict` mode. Furthermore, in `Lenient` mode, it takes precendence over the
|
|
||||||
default specified by the `FromForm` trait implementation of the field.
|
|
||||||
|
|
||||||
```rust
|
|
||||||
# use rocket::form::FromForm;
|
|
||||||
|
|
||||||
#[derive(FromForm)]
|
|
||||||
struct MyForm {
|
|
||||||
#[field(default = "hello")]
|
|
||||||
greet: String, // <String as FromForm> does not provide a default, so we set one here
|
|
||||||
#[field(default = true)]
|
|
||||||
is_friendly: bool, // The default for bool is `false`, but we override it here
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
[`Errors<'_>`]: @api/rocket/form/struct.Errors.html
|
|
||||||
|
|
||||||
### Collections
|
### Collections
|
||||||
|
|
||||||
Rocket's form support allows your application to express _any_ structure with
|
Rocket's form support allows your application to express _any_ structure with
|
||||||
|
|
Loading…
Reference in New Issue