mirror of https://github.com/rwf2/Rocket.git
parent
ebb9f3cfdd
commit
1e4db983e8
|
@ -26,6 +26,7 @@ glob = "0.3"
|
|||
|
||||
[dev-dependencies]
|
||||
rocket = { version = "0.5.0-dev", path = "../lib", features = ["json"] }
|
||||
pretty_assertions = "0.7"
|
||||
version_check = "0.9"
|
||||
trybuild = "1.0"
|
||||
time = "0.2.11"
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
use devise::{*, ext::{TypeExt, SpanDiagnosticExt}};
|
||||
|
||||
use syn::visit_mut::VisitMut;
|
||||
use syn::visit::Visit;
|
||||
use syn::{visit_mut::VisitMut, visit::Visit};
|
||||
use proc_macro2::{TokenStream, TokenTree, Span};
|
||||
use quote::{ToTokens, TokenStreamExt};
|
||||
|
||||
use crate::syn_ext::IdentExt;
|
||||
use crate::name::Name;
|
||||
|
@ -17,7 +17,8 @@ pub enum FieldName {
|
|||
pub struct FieldAttr {
|
||||
pub name: Option<FieldName>,
|
||||
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 {
|
||||
|
@ -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) {
|
||||
(self as &Name).to_tokens(tokens)
|
||||
}
|
||||
|
@ -208,7 +209,6 @@ struct ValidationMutator<'a> {
|
|||
|
||||
impl ValidationMutator<'_> {
|
||||
fn visit_token_stream(&mut self, tt: TokenStream) -> TokenStream {
|
||||
use quote::{ToTokens, TokenStreamExt};
|
||||
use TokenTree::*;
|
||||
|
||||
let mut iter = tt.into_iter();
|
||||
|
@ -329,43 +329,62 @@ pub fn validators<'v>(
|
|||
Ok(exprs)
|
||||
}
|
||||
|
||||
pub fn default<'v>(field: Field<'v>) -> Result<Option<syn::Expr>> {
|
||||
let mut exprs = FieldAttr::from_attrs(FieldAttr::NAME, &field.attrs)?
|
||||
.into_iter()
|
||||
.filter_map(|a| a.default)
|
||||
.map(move |expr| {
|
||||
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()
|
||||
});
|
||||
/// Take an $expr in `default = $expr` and turn it into a `Some($expr.into())`.
|
||||
///
|
||||
/// As a result of calling `into()`, type inference fails for two common
|
||||
/// expressions: integer literals and the bare `None`. As a result, we cheat: if
|
||||
/// the expr matches either condition, we pass them through unchanged.
|
||||
fn default_expr(expr: &syn::Expr) -> TokenStream {
|
||||
use syn::{Expr, Lit, ExprLit};
|
||||
|
||||
let first: Option<syn::Expr> = exprs.next();
|
||||
if let Some(expr) = exprs.next() {
|
||||
return Err(expr.span()
|
||||
.error("duplicate `default` form field attribute")
|
||||
.help("form fields can have at most one `default`"));
|
||||
if matches!(expr, Expr::Path(e) if e.path.is_ident("None")) {
|
||||
quote!(#expr)
|
||||
} else if matches!(expr, Expr::Lit(ExprLit { lit: Lit::Int(_), .. })) {
|
||||
quote_spanned!(expr.span() => Some(#expr))
|
||||
} 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>(
|
||||
|
|
|
@ -230,36 +230,33 @@ pub fn derive_from_form(input: proc_macro::TokenStream) -> TokenStream {
|
|||
.try_field_map(|_, f| {
|
||||
let (ident, ty, name_view) = (f.ident(), f.stripped_ty(), f.name_view()?);
|
||||
let validator = validators(f, &ident, true)?;
|
||||
let user_default = default(f)?;
|
||||
let default = user_default
|
||||
.map(|expr| quote_spanned!(ty.span() =>
|
||||
#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 default = default(f)?
|
||||
.unwrap_or_else(|| quote_spanned!(ty.span() => {
|
||||
<#ty as #_form::FromForm<'__f>>::default(__opts)
|
||||
}));
|
||||
|
||||
let _err = _Err;
|
||||
Ok(quote_spanned! { ty.span() => {
|
||||
let _name = #name_view;
|
||||
let _opts = __c.__opts;
|
||||
let __name = #name_view;
|
||||
let __opts = __c.__opts;
|
||||
__c.#ident
|
||||
.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| {
|
||||
let mut _es = #_form::Errors::new();
|
||||
#(if let #_err(_e) = #validator { _es.extend(_e); })*
|
||||
let mut __es = #_form::Errors::new();
|
||||
#(if let #_err(__e) = #validator { __es.extend(__e); })*
|
||||
|
||||
match _es.is_empty() {
|
||||
match __es.is_empty() {
|
||||
true => #_Ok(#ident),
|
||||
false => #_Err(_es)
|
||||
false => #_Err(__es)
|
||||
}
|
||||
})
|
||||
.map_err(|_e| _e.with_name(_name))
|
||||
.map_err(|_e| match _e.is_empty() {
|
||||
.map_err(|__e| __e.with_name(__name))
|
||||
.map_err(|__e| match __e.is_empty() {
|
||||
true => #_form::ErrorKind::Unknown.into(),
|
||||
false => _e,
|
||||
false => __e,
|
||||
})
|
||||
}})
|
||||
})
|
||||
|
|
|
@ -549,7 +549,6 @@ pub fn derive_from_form_field(input: TokenStream) -> TokenStream {
|
|||
///
|
||||
/// ```rust
|
||||
/// # #[macro_use] extern crate rocket;
|
||||
/// #
|
||||
/// #[derive(FromForm)]
|
||||
/// struct MyStruct<'r> {
|
||||
/// field: usize,
|
||||
|
@ -558,7 +557,7 @@ pub fn derive_from_form_field(input: TokenStream) -> TokenStream {
|
|||
/// other: &'r str,
|
||||
/// #[field(validate = range(1..), default = 3)]
|
||||
/// r#type: usize,
|
||||
/// #[field(default = true)]
|
||||
/// #[field(default = None)]
|
||||
/// 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
|
||||
/// structure on which the derive was applied. Each field's value is parsed with
|
||||
/// the [`FromForm`] implementation of the field's type. The `FromForm`
|
||||
/// implementation succeeds only when all of the field parses succeed or return
|
||||
/// a default. Errors are collected into a [`form::Errors`] and return if
|
||||
/// implementation succeeds only when all fields parse successfully or return a
|
||||
/// default. Errors are collected into a [`form::Errors`] and returned if
|
||||
/// non-empty after parsing all fields.
|
||||
///
|
||||
/// 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
|
||||
/// field := name? default? validate*
|
||||
///
|
||||
/// name := 'name' '=' name_val
|
||||
/// name := 'name' '=' name_val ','?
|
||||
/// name_val := '"' 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
|
||||
/// EXPR := valid expression, as defined by Rust
|
||||
/// ```
|
||||
///
|
||||
/// The attribute can be applied any number of times on a field. When applied,
|
||||
/// the attribute looks as follows:
|
||||
/// The attribute can be applied any number of times on a field as long as at
|
||||
/// most _one_ of `default` or `default_with` is present per field:
|
||||
///
|
||||
/// ```rust
|
||||
/// # #[macro_use] extern crate rocket;
|
||||
/// #
|
||||
/// #[derive(FromForm)]
|
||||
/// struct MyStruct {
|
||||
/// #[field(name = uncased("number"))]
|
||||
/// #[field(default = 42)]
|
||||
/// field: usize,
|
||||
/// #[field(name = "renamed_field")]
|
||||
/// #[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
|
||||
/// will match against _any_ of the names.
|
||||
///
|
||||
/// **`validate`**
|
||||
/// **`validate = expr`**
|
||||
///
|
||||
/// The validation expression will be run if the field type parses successfully.
|
||||
/// The expression must return a value of type `Result<(), form::Errors>`. On
|
||||
/// `Err`, the errors are added to the thus-far collected errors. If more than
|
||||
/// one `validate` attribute is applied, _all_ validations are run.
|
||||
/// The validation `expr` is run if the field type parses successfully. The
|
||||
/// expression must return a value of type `Result<(), form::Errors>`. On `Err`,
|
||||
/// the errors are added to the thus-far collected errors. If more than one
|
||||
/// `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
|
||||
/// [`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};
|
||||
|
||||
|
@ -615,153 +617,90 @@ fn test_multipart() {
|
|||
assert!(response.status().class().is_success());
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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 {
|
||||
#[test]
|
||||
fn test_default_removed() {
|
||||
#[derive(FromForm, PartialEq, Debug)]
|
||||
struct FormNoDefault {
|
||||
#[field(default = None)]
|
||||
field: usize,
|
||||
}
|
||||
|
||||
#[derive(rocket::FromForm)]
|
||||
struct Form6 {
|
||||
field1: bool,
|
||||
#[field(default = None)]
|
||||
field: usize,
|
||||
}
|
||||
|
||||
#[derive(rocket::FromForm)]
|
||||
struct Form7 {
|
||||
field2: Option<usize>,
|
||||
#[field(default = None)]
|
||||
field: String,
|
||||
field3: Option<Option<usize>>,
|
||||
#[field(default_with = None)]
|
||||
field4: bool,
|
||||
}
|
||||
|
||||
#[derive(rocket::FromForm)]
|
||||
struct Form8<'a> {
|
||||
#[field(default = None)]
|
||||
field: &'a str,
|
||||
}
|
||||
let form_string = &["field1=false", "field2=10", "field3=23", "field4"].join("&");
|
||||
let form1: Option<FormNoDefault> = lenient(&form_string).ok();
|
||||
assert_eq!(form1, Some(FormNoDefault {
|
||||
field1: false,
|
||||
field2: Some(10),
|
||||
field3: Some(Some(23)),
|
||||
field4: true,
|
||||
}));
|
||||
|
||||
// #[derive(rocket::FromForm)]
|
||||
// struct Form9<'a> {
|
||||
// #[field(default = None)]
|
||||
// field: Cow<'a, str>,
|
||||
// }
|
||||
let form_string = &["field1=true", "field2=10", "field3=23", "field4"].join("&");
|
||||
let form1: Option<FormNoDefault> = lenient(&form_string).ok();
|
||||
assert_eq!(form1, Some(FormNoDefault {
|
||||
field1: true,
|
||||
field2: Some(10),
|
||||
field3: Some(Some(23)),
|
||||
field4: true,
|
||||
}));
|
||||
|
||||
#[derive(rocket::FromForm)]
|
||||
struct Form10 {
|
||||
#[field(default = "string")]
|
||||
field: String,
|
||||
}
|
||||
// Field 1 missing.
|
||||
let form_string = &["field2=20", "field3=10", "field4"].join("&");
|
||||
assert!(lenient::<FormNoDefault>(&form_string).is_err());
|
||||
|
||||
#[derive(rocket::FromForm)]
|
||||
struct Form11<'a> {
|
||||
#[field(default = "string")]
|
||||
field: &'a str,
|
||||
}
|
||||
// Field 2 missing.
|
||||
let form_string = &["field1=true", "field3=10", "field4"].join("&");
|
||||
assert!(lenient::<FormNoDefault>(&form_string).is_err());
|
||||
|
||||
// #[derive(rocket::FromForm)]
|
||||
// struct Form12 {
|
||||
// #[field(default = "string")]
|
||||
// field: Cow<'static, str>,
|
||||
// }
|
||||
// Field 3 missing.
|
||||
let form_string = &["field1=true", "field2=10", "field4=false"].join("&");
|
||||
assert!(lenient::<FormNoDefault>(&form_string).is_err());
|
||||
|
||||
// #[derive(rocket::FromForm)]
|
||||
// struct Form13<'a> {
|
||||
// #[field(default = "string".to_string())]
|
||||
// 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,
|
||||
}
|
||||
// Field 4 missing.
|
||||
let form_string = &["field1=true", "field2=10", "field3=23"].join("&");
|
||||
assert!(lenient::<FormNoDefault>(&form_string).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
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)]
|
||||
struct FormWithDefaults<'a> {
|
||||
field2: i128,
|
||||
field5: bool,
|
||||
|
||||
#[field(default = 100)]
|
||||
field1: usize,
|
||||
field2: i128,
|
||||
|
||||
#[field(default = true)]
|
||||
field3: bool,
|
||||
#[field(default = false)]
|
||||
field4: bool,
|
||||
field5: bool,
|
||||
|
||||
#[field(default = 254 + 1)]
|
||||
field6: u8,
|
||||
#[field(default = Some(true))]
|
||||
opt: Option<bool>,
|
||||
#[field(default = Ok("hello".to_string()))]
|
||||
opt1: Option<bool>,
|
||||
#[field(default = false)]
|
||||
opt2: Option<bool>,
|
||||
#[field(default = Ok("hello".into()))]
|
||||
res: form::Result<'a, String>,
|
||||
#[field(default = Ok("hello"))]
|
||||
res2: form::Result<'a, &'a str>,
|
||||
#[field(default = vec![1, 2, 3])]
|
||||
vec_num: Vec<usize>,
|
||||
#[field(default = vec!["wow", "a", "string", "nice"])]
|
||||
|
@ -770,12 +709,14 @@ fn test_defaults() {
|
|||
hashmap: HashMap<&'a str, &'a str>,
|
||||
#[field(default = test_btreemap())]
|
||||
btreemap: BTreeMap<&'a str, &'a str>,
|
||||
#[field(default = false)]
|
||||
#[field(default_with = Some(false))]
|
||||
boolean: bool,
|
||||
#[field(default = 3)]
|
||||
#[field(default_with = (|| Some(777))())]
|
||||
unsigned: usize,
|
||||
#[field(default = NonZeroI32::new(3).unwrap())]
|
||||
nonzero: NonZeroI32,
|
||||
#[field(default = std::num::NonZeroI32::new(3).unwrap())]
|
||||
nonzero: std::num::NonZeroI32,
|
||||
#[field(default_with = std::num::NonZeroI32::new(9001))]
|
||||
nonzero2: std::num::NonZeroI32,
|
||||
#[field(default = 3.0)]
|
||||
float: f64,
|
||||
#[field(default = "wow")]
|
||||
|
@ -797,24 +738,31 @@ fn test_defaults() {
|
|||
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();
|
||||
assert_eq!(form1, Some(FormWithDefaults {
|
||||
field1: 101,
|
||||
field1: 100,
|
||||
field2: 102,
|
||||
field3: true,
|
||||
field4: false,
|
||||
field5: false,
|
||||
opt: Some(true),
|
||||
res: Ok("hello".to_string()),
|
||||
field6: 255,
|
||||
opt1: Some(true),
|
||||
opt2: Some(false),
|
||||
res: Ok("hello".into()),
|
||||
res2: Ok("hello"),
|
||||
vec_num: vec![1, 2, 3],
|
||||
vec_str: vec!["wow", "a", "string", "nice"],
|
||||
hashmap: test_hashmap(),
|
||||
btreemap: test_btreemap(),
|
||||
boolean: false,
|
||||
unsigned: 3,
|
||||
nonzero: NonZeroI32::new(3).unwrap(),
|
||||
unsigned: 777,
|
||||
nonzero: std::num::NonZeroI32::new(3).unwrap(),
|
||||
nonzero2: std::num::NonZeroI32::new(9001).unwrap(),
|
||||
float: 3.0,
|
||||
str_ref: "wow",
|
||||
string: "wowie".to_string(),
|
||||
|
@ -824,16 +772,12 @@ fn test_defaults() {
|
|||
time: 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();
|
||||
assert!(form2.is_none());
|
||||
|
||||
let form_string = &[
|
||||
"field1=101",
|
||||
"field2=102",
|
||||
"field3=true",
|
||||
"field5=true"
|
||||
].join("&");
|
||||
|
||||
// Ensure actual form field values take precendence.
|
||||
let form_string = &["field1=101", "field2=102", "field3=true", "field5=true"].join("&");
|
||||
let form3: Option<FormWithDefaults> = lenient(&form_string).ok();
|
||||
assert_eq!(form3, Some(FormWithDefaults {
|
||||
field1: 101,
|
||||
|
@ -841,8 +785,10 @@ fn test_defaults() {
|
|||
field3: true,
|
||||
field4: false,
|
||||
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();
|
||||
assert_eq!(form4, Some(FormWithDefaults {
|
||||
field1: 101,
|
||||
|
@ -850,6 +796,49 @@ fn test_defaults() {
|
|||
field3: true,
|
||||
field4: false,
|
||||
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)
|
||||
|
||||
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
|
||||
--> $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>>
|
||||
and 5 others
|
||||
= 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)
|
||||
|
||||
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
|
||||
--> $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>>
|
||||
and 5 others
|
||||
= 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)]
|
||||
struct Default1 {
|
||||
#[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() { }
|
||||
|
|
|
@ -56,23 +56,6 @@ use crate::form::prelude::*;
|
|||
/// can access fields of `T` transparently through a `Form<T>`, as seen above
|
||||
/// 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
|
||||
///
|
||||
/// 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, "");
|
||||
```
|
||||
|
||||
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
|
||||
[`form::Result`]: @api/rocket/form/type.Result.html
|
||||
[`FromForm` derive]: @api/rocket/derive.FromForm.html
|
||||
|
||||
### 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
|
||||
`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
|
||||
|
||||
Rocket's form support allows your application to express _any_ structure with
|
||||
|
|
Loading…
Reference in New Issue