Fix, finish 'FromForm' derive field defaults.

Resolves #1536.
This commit is contained in:
Sergio Benitez 2021-06-02 22:35:40 -07:00
parent ebb9f3cfdd
commit 1e4db983e8
10 changed files with 443 additions and 276 deletions

View File

@ -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"

View File

@ -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>(

View File

@ -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,
}) })
}}) }})
}) })

View File

@ -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

View File

@ -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
})); }));
} }

View File

@ -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()))]
| ^^^^^^^^^^^^^^^^

View File

@ -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()))]
| ^^^^^^^^^^^^^^^^

View File

@ -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() { }

View File

@ -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

View File

@ -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