Support type generics, unit structs in 'FromForm'.

The 'FromFrom' derive now allows type generics in all positions using
the same automatic discovery technique as with 'Responder'. (In fact,
the technique was created for this derive.) Furthermore, 'FromForm' can
now be derived for unit structs.

Also adds a new 'try_with' form field validator.

Resolves #1695.
This commit is contained in:
Sergio Benitez 2021-06-29 03:36:48 -07:00
parent dcbb1941c5
commit 793f421712
13 changed files with 779 additions and 491 deletions

View File

@ -26,11 +26,13 @@ impl FieldAttr {
}
pub(crate) trait FieldExt {
fn ident(&self) -> &syn::Ident;
fn ident(&self) -> Option<&syn::Ident>;
fn member(&self) -> syn::Member;
fn context_ident(&self) -> syn::Ident;
fn field_names(&self) -> Result<Vec<FieldName>>;
fn first_field_name(&self) -> Result<FieldName>;
fn first_field_name(&self) -> Result<Option<FieldName>>;
fn stripped_ty(&self) -> syn::Type;
fn name_view(&self) -> Result<syn::Expr>;
fn name_buf_opt(&self) -> Result<TokenStream>;
}
#[derive(FromMeta)]
@ -49,13 +51,13 @@ pub(crate) trait VariantExt {
impl VariantExt for Variant<'_> {
fn first_form_field_value(&self) -> Result<FieldName> {
let first = VariantAttr::from_attrs(VariantAttr::NAME, &self.attrs)?
let value = VariantAttr::from_attrs(VariantAttr::NAME, &self.attrs)?
.into_iter()
.next();
.next()
.map(|attr| FieldName::Uncased(attr.value))
.unwrap_or_else(|| FieldName::Uncased(Name::from(&self.ident)));
Ok(first.map_or_else(
|| FieldName::Uncased(Name::from(&self.ident)),
|attr| FieldName::Uncased(attr.value)))
Ok(value)
}
fn form_field_values(&self) -> Result<Vec<FieldName>> {
@ -147,10 +149,27 @@ impl PartialEq for FieldName {
}
impl FieldExt for Field<'_> {
fn ident(&self) -> &syn::Ident {
self.ident.as_ref().expect("named")
fn ident(&self) -> Option<&syn::Ident> {
self.ident.as_ref()
}
fn member(&self) -> syn::Member {
match self.ident().cloned() {
Some(ident) => syn::Member::Named(ident),
None => syn::Member::Unnamed(syn::Index {
index: self.index as u32,
span: self.ty.span()
})
}
}
fn context_ident(&self) -> syn::Ident {
self.ident()
.map(|i| i.clone())
.unwrap_or_else(|| syn::Ident::new("__form_field", self.span()))
}
// With named existentials, this could return an `impl Iterator`...
fn field_names(&self) -> Result<Vec<FieldName>> {
let attr_names = FieldAttr::from_attrs(FieldAttr::NAME, &self.attrs)?
.into_iter()
@ -158,31 +177,29 @@ impl FieldExt for Field<'_> {
.collect::<Vec<_>>();
if attr_names.is_empty() {
let ident_name = Name::from(self.ident());
return Ok(vec![FieldName::Cased(ident_name)]);
if let Some(ident) = self.ident() {
return Ok(vec![FieldName::Cased(Name::from(ident))]);
}
}
Ok(attr_names)
}
fn first_field_name(&self) -> Result<FieldName> {
let mut names = self.field_names()?.into_iter();
Ok(names.next().expect("always have >= 1 name"))
fn first_field_name(&self) -> Result<Option<FieldName>> {
Ok(self.field_names()?.into_iter().next())
}
fn stripped_ty(&self) -> syn::Type {
self.ty.with_stripped_lifetimes()
}
fn name_view(&self) -> Result<syn::Expr> {
let field_names = self.field_names()?;
let field_name = field_names.first().expect("always have name");
define_spanned_export!(self.span() => _form);
let name_view = quote_spanned! { self.span() =>
#_form::NameBuf::from((__c.__parent, #field_name))
};
fn name_buf_opt(&self) -> Result<TokenStream> {
let (span, field_names) = (self.span(), self.field_names()?);
define_spanned_export!(span => _form);
Ok(syn::parse2(name_view).unwrap())
Ok(field_names.first()
.map(|name| quote_spanned!(span => Some(#_form::NameBuf::from((__c.__parent, #name)))))
.unwrap_or_else(|| quote_spanned!(span => None::<#_form::NameBuf>)))
}
}
@ -278,7 +295,7 @@ impl VisitMut for ValidationMutator<'_> {
if let syn::Expr::Field(e) = i {
if let syn::Expr::Path(e) = &*e.base {
if e.path.is_ident("self") && self.local {
let new_expr = &self.field;
let new_expr = self.field;
*i = syn::parse_quote!(#new_expr);
}
}
@ -291,43 +308,34 @@ impl VisitMut for ValidationMutator<'_> {
pub fn validators<'v>(
field: Field<'v>,
parent: &'v syn::Ident, // field ident (if local) or form ident (if !local)
local: bool, // whether to emit local or global (w/self) validations
local: bool, // whether to emit local (true) or global (w/self) validations
) -> Result<impl Iterator<Item = syn::Expr> + 'v> {
let exprs = FieldAttr::from_attrs(FieldAttr::NAME, &field.attrs)?
Ok(FieldAttr::from_attrs(FieldAttr::NAME, &field.attrs)?
.into_iter()
.chain(FieldAttr::from_attrs(FieldAttr::NAME, field.parent.attrs())?)
.filter_map(|a| a.validate)
.map(move |expr| {
let mut members = RecordMemberAccesses(vec![]);
members.visit_expr(&expr);
let field_ident = field.ident();
let is_local_validation = members.0.iter()
.all(|member| match member {
syn::Member::Named(i) => i == field_ident,
_ => false
});
let field_member = field.member();
let is_local_validation = members.0.iter().all(|m| m == &field_member);
(expr, is_local_validation)
})
.filter(move |(_, is_local)| *is_local == local)
.map(move |(mut expr, _)| {
let field_span = field.ident().span()
.join(field.ty.span())
.unwrap_or(field.ty.span());
let field = &field.ident().clone().with_span(field_span);
let ty_span = field.ty.span();
let field = &field.context_ident().with_span(ty_span);
let mut v = ValidationMutator { parent, local, field, visited: false };
v.visit_expr_mut(&mut expr);
let span = expr.key_span.unwrap_or(field_span);
let span = expr.key_span.unwrap_or(ty_span);
define_spanned_export!(span => _form);
syn::parse2(quote_spanned!(span => {
let __result: #_form::Result<'_, ()> = #expr;
__result
})).unwrap()
});
Ok(exprs)
}))
}
/// Take an $expr in `default = $expr` and turn it into a `Some($expr.into())`.
@ -348,13 +356,17 @@ fn default_expr(expr: &syn::Expr) -> TokenStream {
}
pub fn default<'v>(field: Field<'v>) -> Result<Option<TokenStream>> {
let attrs = FieldAttr::from_attrs(FieldAttr::NAME, &field.attrs)?;
let field_attrs = FieldAttr::from_attrs(FieldAttr::NAME, &field.attrs)?;
let parent_attrs = FieldAttr::from_attrs(FieldAttr::NAME, field.parent.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);
let mut expr = field_attrs.iter()
.chain(parent_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()
let mut expr_with = field_attrs.iter()
.chain(parent_attrs.iter())
.filter_map(|a| a.default_with.as_ref())
.map(|e| e.to_token_stream());
@ -380,7 +392,13 @@ pub fn default<'v>(field: Field<'v>) -> Result<Option<TokenStream>> {
},
(Some(e), None) | (None, Some(e)) => {
Ok(Some(quote_spanned!(e.span() => {
let __default: Option<#ty> = #e;
let __default: Option<#ty>;
if __opts.strict {
__default = None;
} else {
__default = #e;
}
__default
})))
},

View File

@ -1,8 +1,13 @@
use devise::{*, ext::{TypeExt, SpanDiagnosticExt, GenericsExt, Split2, quote_respanned}};
use proc_macro2::TokenStream;
use devise::ext::{TypeExt, SpanDiagnosticExt, GenericsExt, quote_respanned};
use syn::parse::Parser;
use devise::*;
use crate::exports::*;
use crate::derive::form_field::{*, FieldName::*};
use crate::syn_ext::{GenericsExt as _, TypeExt as _};
type WherePredicates = syn::punctuated::Punctuated<syn::WherePredicate, syn::Token![,]>;
// F: fn(field_ty: Ty, field_context: Expr)
fn fields_map<F>(fields: Fields<'_>, map_f: F) -> Result<TokenStream>
@ -10,21 +15,26 @@ fn fields_map<F>(fields: Fields<'_>, map_f: F) -> Result<TokenStream>
{
let mut matchers = vec![];
for field in fields.iter() {
let (ident, ty) = (field.ident(), field.stripped_ty());
let field_context = quote_spanned!(ty.span() => {
let (ident, ty) = (field.context_ident(), field.stripped_ty());
let field_context: syn::Expr = syn::parse2(quote_spanned!(ty.span() => {
let __o = __c.__opts;
__c.#ident.get_or_insert_with(|| <#ty as #_form::FromForm<'__f>>::init(__o))
});
__c.#ident.get_or_insert_with(|| <#ty as #_form::FromForm<'r>>::init(__o))
})).expect("form context expression");
let field_names = field.field_names()?;
let field_context = syn::parse2(field_context).expect("valid expr");
let push = map_f(&ty, &field_context);
let field_matchers = field_names.iter().map(|f| match f {
if fields.are_unnamed() {
// If we have unnamed fields, then we have exactly one by virtue of
// the earlier validation. Push directly to it and return.
return Ok(quote_spanned!(ident.span() =>
__c.__parent = __f.name.parent();
#push
));
}
matchers.extend(field.field_names()?.into_iter().map(|f| match f {
Cased(name) => quote!(#name => { #push }),
Uncased(name) => quote!(__n if __n.as_uncased() == #name => { #push }),
});
matchers.extend(field_matchers);
}));
}
Ok(quote! {
@ -38,28 +48,44 @@ fn fields_map<F>(fields: Fields<'_>, map_f: F) -> Result<TokenStream>
})
}
fn context_type(input: Input<'_>) -> (TokenStream, Option<syn::WhereClause>) {
fn generic_bounds_tokens(input: Input<'_>) -> Result<TokenStream> {
MapperBuild::new()
.try_enum_map(|m, e| mapper::enum_null(m, e))
.try_fields_map(|_, fields| {
let generic_idents = fields.parent.input().generics().type_idents();
let bounds = fields.iter()
.filter(|f| !f.ty.is_concrete(&generic_idents))
.map(|f| f.ty.with_replaced_lifetimes(syn::Lifetime::new("'r", f.ty.span())))
.map(|ty| quote_spanned!(ty.span() => #ty: #_form::FromForm<'r>));
Ok(quote!(#(#bounds),*))
})
.map_input(input)
}
fn generic_bounds(input: Input<'_>) -> Result<WherePredicates> {
Ok(WherePredicates::parse_terminated.parse2(generic_bounds_tokens(input)?)?)
}
fn context_type(input: Input<'_>) -> Result<(TokenStream, syn::Generics)> {
let mut gen = input.generics().clone();
let lifetime = syn::parse_quote!('__f);
let lifetime = syn::parse_quote!('r);
if !gen.replace_lifetime(0, &lifetime) {
gen.insert_lifetime(syn::LifetimeDef::new(lifetime.clone()));
}
let span = input.ident().span();
gen.add_type_bound(syn::parse_quote!(#_form::FromForm<#lifetime>));
gen.add_type_bound(syn::TypeParamBound::from(lifetime));
let (_, ty_gen, where_clause) = gen.split_for_impl();
(quote_spanned!(span => FromFormGeneratedContext #ty_gen), where_clause.cloned())
gen.add_where_predicates(generic_bounds(input)?);
let ty = quote_spanned!(input.ident().span() => FromFormGeneratedContext);
Ok((ty, gen))
}
pub fn derive_from_form(input: proc_macro::TokenStream) -> TokenStream {
DeriveGenerator::build_for(input, quote!(impl<'__f> #_form::FromForm<'__f>))
// NOTE: If support is widened, fix `FieldExt::ident()` `expect()`.
.support(Support::NamedStruct | Support::Lifetime | Support::Type)
DeriveGenerator::build_for(input, quote!(impl<'r> #_form::FromForm<'r>))
.support(Support::Struct | Support::Lifetime | Support::Type)
.replace_generic(0, 0)
.type_bound(quote!(#_form::FromForm<'__f> + '__f))
.type_bound_mapper(MapperBuild::new().try_input_map(|_, i| generic_bounds_tokens(i)))
.validator(ValidatorBuild::new()
.input_validate(|_, i| match i.generics().lifetimes().enumerate().last() {
Some((i, lt)) if i >= 1 => Err(lt.span().error("only one lifetime is supported")),
@ -68,9 +94,9 @@ pub fn derive_from_form(input: proc_macro::TokenStream) -> TokenStream {
.fields_validate(|_, fields| {
if fields.is_empty() {
return Err(fields.span().error("at least one field is required"));
}
if let Some(d) = first_duplicate(fields.iter(), |f| f.field_names())? {
} else if fields.are_unnamed() && fields.count() != 1 {
return Err(fields.span().error("tuple struct must have exactly one field"));
} else if let Some(d) = first_duplicate(fields.iter(), |f| f.field_names())? {
let (field_a_i, field_a, name_a) = d.0;
let (field_b_i, field_b, name_b) = d.1;
@ -90,25 +116,29 @@ pub fn derive_from_form(input: proc_macro::TokenStream) -> TokenStream {
)
.outer_mapper(MapperBuild::new()
.try_input_map(|mapper, input| {
let (ctxt_ty, where_clause) = context_type(input);
let vis = input.vis();
let (ctxt_ty, gen) = context_type(input)?;
let (impl_gen, _, where_clause) = gen.split_for_impl();
let output = mapper::input_default(mapper, input)?;
Ok(quote! {
Ok(quote_spanned! { input.span() =>
/// Rocket generated FormForm context.
#[doc(hidden)]
pub struct #ctxt_ty #where_clause {
#[allow(private_in_public)]
#vis struct #ctxt_ty #impl_gen #where_clause {
__opts: #_form::Options,
__errors: #_form::Errors<'__f>,
__parent: #_Option<&'__f #_form::Name>,
__errors: #_form::Errors<'r>,
__parent: #_Option<&'r #_form::Name>,
#output
}
})
})
.try_fields_map(|m, f| mapper::fields_null(m, f))
.field_map(|_, field| {
let (ident, mut ty) = (field.ident(), field.stripped_ty());
ty.replace_lifetimes(syn::parse_quote!('__f));
let ident = field.context_ident();
let mut ty = field.stripped_ty();
ty.replace_lifetimes(syn::parse_quote!('r));
let field_ty = quote_respanned!(ty.span() =>
#_Option<<#ty as #_form::FromForm<'__f>>::Context>
#_Option<<#ty as #_form::FromForm<'r>>::Context>
);
quote_spanned!(ty.span() => #ident: #field_ty,)
@ -118,13 +148,15 @@ pub fn derive_from_form(input: proc_macro::TokenStream) -> TokenStream {
#[allow(unused_imports)]
use #_http::uncased::AsUncased;
})
.outer_mapper(quote!(#[allow(private_in_public)]))
.outer_mapper(quote!(#[rocket::async_trait]))
.inner_mapper(MapperBuild::new()
.try_input_map(|mapper, input| {
let (ctxt_ty, _) = context_type(input);
let (ctxt_ty, gen) = context_type(input)?;
let (_, ty_gen, _) = gen.split_for_impl();
let output = mapper::input_default(mapper, input)?;
Ok(quote! {
type Context = #ctxt_ty;
type Context = #ctxt_ty #ty_gen;
fn init(__opts: #_form::Options) -> Self::Context {
Self::Context {
@ -138,47 +170,45 @@ pub fn derive_from_form(input: proc_macro::TokenStream) -> TokenStream {
})
.try_fields_map(|m, f| mapper::fields_null(m, f))
.field_map(|_, field| {
let ident = field.ident.as_ref().expect("named");
let ident = field.context_ident();
let ty = field.ty.with_stripped_lifetimes();
quote_spanned!(ty.span() =>
#ident: #_None,
// #ident: <#ty as #_form::FromForm<'__f>>::init(__opts),
)
quote_spanned!(ty.span() => #ident: #_None,)
})
)
.inner_mapper(MapperBuild::new()
.with_output(|_, output| quote! {
fn push_value(__c: &mut Self::Context, __f: #_form::ValueField<'__f>) {
fn push_value(__c: &mut Self::Context, __f: #_form::ValueField<'r>) {
#output
}
})
.try_fields_map(|_, f| fields_map(f, |ty, ctxt| quote_spanned!(ty.span() => {
<#ty as #_form::FromForm<'__f>>::push_value(#ctxt, __f.shift());
<#ty as #_form::FromForm<'r>>::push_value(#ctxt, __f.shift());
})))
)
.inner_mapper(MapperBuild::new()
.try_input_map(|mapper, input| {
let (ctxt_ty, _) = context_type(input);
let (ctxt_ty, gen) = context_type(input)?;
let (_, ty_gen, _) = gen.split_for_impl();
let output = mapper::input_default(mapper, input)?;
Ok(quote! {
async fn push_data(
__c: &mut #ctxt_ty,
__f: #_form::DataField<'__f, '_>
__c: &mut #ctxt_ty #ty_gen,
__f: #_form::DataField<'r, '_>
) {
#output
}
})
})
// Without the `let _fut`, we get a wild lifetime error. It don't
// make no sense, Rust async/await, it don't make no sense.
// make no sense, Rust async/await: it don't make no sense.
.try_fields_map(|_, f| fields_map(f, |ty, ctxt| quote_spanned!(ty.span() => {
let _fut = <#ty as #_form::FromForm<'__f>>::push_data(#ctxt, __f.shift());
let _fut = <#ty as #_form::FromForm<'r>>::push_data(#ctxt, __f.shift());
_fut.await;
})))
)
.inner_mapper(MapperBuild::new()
.with_output(|_, output| quote! {
fn finalize(mut __c: Self::Context) -> #_Result<Self, #_form::Errors<'__f>> {
fn finalize(mut __c: Self::Context) -> #_Result<Self, #_form::Errors<'r>> {
#[allow(unused_imports)]
use #_form::validate::*;
@ -186,21 +216,24 @@ pub fn derive_from_form(input: proc_macro::TokenStream) -> TokenStream {
}
})
.try_fields_map(|mapper, fields| {
// This validates the attributes so we can `unwrap()` later.
let finalize_field = fields.iter()
.map(|f| mapper.map_field(f))
.collect::<Result<Vec<TokenStream>>>()?;
let ident: Vec<_> = fields.iter()
.map(|f| f.ident().clone())
.collect();
let o = syn::Ident::new("__o", fields.span());
let (_ok, _some, _err, _none) = (_Ok, _Some, _Err, _None);
let (name_view, validate) = fields.iter()
.map(|f| (f.name_view().unwrap(), validators(f, &o, false).unwrap()))
.map(|(nv, vs)| vs.map(move |v| (nv.clone(), v)))
.flatten()
.split2();
let validate = fields.iter().flat_map(|f| validators(f, &o, false).unwrap());
let name_buf_opt = fields.iter().map(|f| f.name_buf_opt().unwrap());
let ident: Vec<_> = fields.iter()
.map(|f| f.context_ident())
.collect();
let builder = fields.builder(|f| {
let ident = f.context_ident();
quote!(#ident.unwrap())
});
Ok(quote_spanned! { fields.span() =>
#(let #ident = match #finalize_field {
@ -212,11 +245,14 @@ pub fn derive_from_form(input: proc_macro::TokenStream) -> TokenStream {
return #_Err(__c.__errors);
}
let #o = Self { #(#ident: #ident.unwrap()),* };
let #o = #builder;
#(
if let #_err(__e) = #validate {
__c.__errors.extend(__e.with_name(#name_view));
__c.__errors.extend(match #name_buf_opt {
Some(__name) => __e.with_name(__name),
None => __e
});
}
)*
@ -228,28 +264,32 @@ 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 (ident, ty) = (f.context_ident(), f.stripped_ty());
let validator = validators(f, &ident, true)?;
let name_buf_opt = f.name_buf_opt()?;
let default = default(f)?
.unwrap_or_else(|| quote_spanned!(ty.span() => {
<#ty as #_form::FromForm<'__f>>::default(__opts)
<#ty as #_form::FromForm<'r>>::default(__opts)
}));
let _err = _Err;
Ok(quote_spanned! { ty.span() => {
let __name = #name_view;
let __opts = __c.__opts;
let __name = #name_buf_opt;
__c.#ident
.map_or_else(
|| #default.ok_or_else(|| #_form::ErrorKind::Missing.into()),
<#ty as #_form::FromForm<'__f>>::finalize
<#ty as #_form::FromForm<'r>>::finalize
)
.and_then(|#ident| {
let mut __es = #_form::Errors::new();
#(if let #_err(__e) = #validator { __es.extend(__e); })*
__es.is_empty().then(|| #ident).ok_or(__es)
})
.map_err(|__e| __e.with_name(__name))
.map_err(|__e| match __name {
Some(__name) => __e.with_name(__name),
None => __e,
})
.map_err(|__e| __e.is_empty()
.then(|| #_form::ErrorKind::Unknown.into())
.unwrap_or(__e))

View File

@ -12,7 +12,6 @@ const ONLY_ONE_UNNAMED: &str = "tuple structs or variants must have exactly one
const EXACTLY_ONE_FIELD: &str = "struct must have exactly one field";
pub fn derive_uri_display_query(input: proc_macro::TokenStream) -> TokenStream {
const URI_DISPLAY: StaticTokens = quote_static!(#_fmt::UriDisplay<#_fmt::Query>);
const FORMATTER: StaticTokens = quote_static!(#_fmt::Formatter<#_fmt::Query>);
@ -65,8 +64,7 @@ pub fn derive_uri_display_query(input: proc_macro::TokenStream) -> TokenStream {
.try_field_map(|_, field| {
let span = field.span().into();
let accessor = field.accessor();
let tokens = if field.ident.is_some() {
let name = field.first_field_name()?;
let tokens = if let Some(name) = field.first_field_name()? {
quote_spanned!(span => f.write_named_value(#name, &#accessor)?;)
} else {
quote_spanned!(span => f.write_value(&#accessor)?;)

View File

@ -547,10 +547,12 @@ pub fn derive_from_form_field(input: TokenStream) -> TokenStream {
/// Derive for the [`FromForm`] trait.
///
/// The [`FromForm`] derive can be applied to structures with named fields:
/// The [`FromForm`] derive can be applied to structures with named or unnamed
/// fields:
///
/// ```rust
/// # #[macro_use] extern crate rocket;
/// use rocket::form::FromForm;
///
/// #[derive(FromForm)]
/// struct MyStruct<'r> {
/// field: usize,
@ -562,19 +564,36 @@ pub fn derive_from_form_field(input: TokenStream) -> TokenStream {
/// #[field(default = None)]
/// is_nice: bool,
/// }
///
/// #[derive(FromForm)]
/// #[field(validate = len(6..))]
/// #[field(validate = neq("password"))]
/// struct Password<'r>(&'r str);
/// ```
///
/// Each field's type is required to implement [`FromForm`].
/// Each field type is required to implement [`FromForm`].
///
/// The derive generates an implementation of the [`FromForm`] trait. 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
/// the [`FromForm`] implementation of the field's type. The `FromForm`
/// 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 generates an implementation of the [`FromForm`] trait.
///
/// The derive accepts one field attribute: `field`, with the following syntax:
/// **Named Fields**
///
/// If the structure has named fields, 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 the [`FromForm`] implementation
/// of the field's type. The `FromForm` 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.
///
/// **Unnamed Fields**
///
/// If the structure is a tuple struct, it must have exactly one field. The
/// implementation parses a form exactly when the internal field parses a form
/// _and_ any `#[field]` validations succeed.
///
/// ## Syntax
///
/// The derive accepts one field attribute: `field`, and one container
/// attribute, `form`, with the following syntax:
///
/// ```text
/// field := name? default? validate*
@ -592,8 +611,9 @@ pub fn derive_from_form_field(input: TokenStream) -> TokenStream {
/// EXPR := valid expression, as defined by Rust
/// ```
///
/// 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:
/// `#[field]` can be applied any number of times on a field. `default` and
/// `default_with` are mutually exclusive: at most _one_ of `default` or
/// `default_with` can be present per field.
///
/// ```rust
/// # #[macro_use] extern crate rocket;
@ -610,54 +630,118 @@ pub fn derive_from_form_field(input: TokenStream) -> TokenStream {
/// }
/// ```
///
/// **`name`**
///
/// A `name` attribute changes the name to match against when parsing the form
/// field. The value is either an exact string to match against (`"foo"`), or
/// `uncased("foo")`, which causes the match to be case-insensitive but
/// case-preserving. When more than one `name` attribute is applied, the field
/// will match against _any_ of the names.
///
/// **`validate = expr`**
///
/// 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.
/// For tuples structs, the `field` attribute can be applied to the structure
/// itself:
///
/// ```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,
/// }
/// #[field(default = 42, validate = eq(42))]
/// struct Meaning(usize);
/// ```
///
/// ## Field Attribute Parameters
///
/// * **`name`**
///
/// A `name` attribute changes the name to match against when parsing the
/// form field. The value is either an exact string to match against
/// (`"foo"`), or `uncased("foo")`, which causes the match to be
/// case-insensitive but case-preserving. When more than one `name`
/// attribute is applied, the field will match against _any_ of the names.
///
/// * **`validate = expr`**
///
/// 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
#[proc_macro_derive(FromForm, attributes(field))]
///
/// # Generics
///
/// The derive accepts any number of type generics and at most one lifetime
/// generic. If a type generic is present, the generated implementation will
/// require a bound of `FromForm<'r>` for the field type containing the generic.
/// For example, for a struct `struct Foo<T>(Json<T>)`, the bound `Json<T>:
/// FromForm<'r>` will be added to the generated implementation.
///
/// ```rust
/// use rocket::form::FromForm;
/// use rocket::serde::json::Json;
///
/// // The bounds `A: FromForm<'r>`, `B: FromForm<'r>` will be required.
/// #[derive(FromForm)]
/// struct FancyForm<A, B> {
/// first: A,
/// second: B,
/// };
///
/// // The bound `Json<T>: FromForm<'r>` will be required.
/// #[derive(FromForm)]
/// struct JsonToken<T> {
/// token: Json<T>,
/// id: usize,
/// }
/// ```
///
/// If a lifetime generic is present, it is replaced with `'r` in the
/// generated implementation `impl FromForm<'r>`:
///
/// ```rust
/// # #[macro_use] extern crate rocket;
/// // Generates `impl<'r> FromForm<'r> for MyWrapper<'r>`.
/// #[derive(FromForm)]
/// struct MyWrapper<'a>(&'a str);
/// ```
///
/// Both type generics and one lifetime generic may be used:
///
/// ```rust
/// use rocket::form::{self, FromForm};
///
/// // The bound `form::Result<'r, T>: FromForm<'r>` will be required.
/// #[derive(FromForm)]
/// struct SomeResult<'o, T>(form::Result<'o, T>);
/// ```
///
/// The special bounds on `Json` and `Result` are required due to incomplete and
/// incorrect support for lifetime generics in `async` blocks in Rust. See
/// [rust-lang/#64552](https://github.com/rust-lang/rust/issues/64552) for
/// further details.
#[proc_macro_derive(FromForm, attributes(form, field))]
pub fn derive_from_form(input: TokenStream) -> TokenStream {
emit!(derive::from_form::derive_from_form(input))
}

View File

@ -5,7 +5,7 @@ use proc_macro2::{Span, TokenStream};
/// A "name" read by codegen, which may or may not be an identifier. A `Name` is
/// typically constructed indirectly via FromMeta, or From<Ident> or directly
/// from a string via `Name::new()`.
/// from a string via `Name::new()`. A name is tokenized as a string.
///
/// Some "names" in Rocket include:
/// * Dynamic parameter: `name` in `<name>`

View File

@ -2,7 +2,11 @@ use std::net::{IpAddr, SocketAddr};
use std::collections::{BTreeMap, HashMap};
use pretty_assertions::assert_eq;
use rocket::UriDisplayQuery;
use rocket::http::uri::fmt::{UriDisplay, Query};
use rocket::form::{self, Form, Strict, FromForm, FromFormField, Errors};
use rocket::form::error::{ErrorKind, Entity};
use rocket::serde::json::Json;
fn strict<'f, T: FromForm<'f>>(string: &'f str) -> Result<T, Errors<'f>> {
Form::<Strict<T>>::parse(string).map(|s| s.into_inner())
@ -12,7 +16,7 @@ fn lenient<'f, T: FromForm<'f>>(string: &'f str) -> Result<T, Errors<'f>> {
Form::<T>::parse(string)
}
fn strict_encoded<T: 'static>(string: &'static str) -> Result<T, Errors<'static>>
fn strict_encoded<T: 'static>(string: &str) -> Result<T, Errors<'static>>
where for<'a> T: FromForm<'a>
{
Form::<Strict<T>>::parse_encoded(string.into()).map(|s| s.into_inner())
@ -331,8 +335,6 @@ fn generics() {
#[test]
fn form_errors() {
use rocket::form::error::{ErrorKind, Entity};
#[derive(Debug, PartialEq, FromForm)]
struct WhoopsForm {
complete: bool,
@ -671,16 +673,19 @@ fn test_defaults() {
fn test_hashmap() -> HashMap<&'static str, &'static str> {
let mut map = HashMap::new();
map.insert("key", "value");
map.insert("one-more", "good-value");
map
}
fn test_btreemap() -> BTreeMap<&'static str, &'static str> {
fn test_btreemap() -> BTreeMap<Vec<usize>, &'static str> {
let mut map = BTreeMap::new();
map.insert("key", "value");
map.insert(vec![], "empty");
map.insert(vec![1, 2], "one-and-two");
map.insert(vec![3, 7, 9], "prime");
map
}
#[derive(FromForm, PartialEq, Debug)]
#[derive(FromForm, UriDisplayQuery, PartialEq, Debug)]
struct FormWithDefaults<'a> {
field2: i128,
field5: bool,
@ -708,7 +713,7 @@ fn test_defaults() {
#[field(default = test_hashmap())]
hashmap: HashMap<&'a str, &'a str>,
#[field(default = test_btreemap())]
btreemap: BTreeMap<&'a str, &'a str>,
btreemap: BTreeMap<Vec<usize>, &'a str>,
#[field(default_with = Some(false))]
boolean: bool,
#[field(default_with = (|| Some(777))())]
@ -789,15 +794,39 @@ fn test_defaults() {
}));
// And that strict parsing still works.
let form4: Option<FormWithDefaults> = strict(&form_string).ok();
assert_eq!(form4, Some(FormWithDefaults {
field1: 101,
field2: 102,
field3: true,
field4: false,
field5: true,
..form3.unwrap()
let form = form3.unwrap();
let form_string = format!("{}", &form as &dyn UriDisplay<Query>);
let form4: form::Result<'_, FormWithDefaults> = strict(&form_string);
assert_eq!(form4, Ok(form));
#[derive(FromForm, UriDisplayQuery, PartialEq, Debug)]
struct OwnedFormWithDefaults {
#[field(default = {
let mut map = BTreeMap::new();
map.insert(vec![], "empty!/!? neat".into());
map.insert(vec![1, 2], "one/ and+two".into());
map.insert(vec![3, 7, 9], "prime numbers".into());
map
})]
btreemap: BTreeMap<Vec<usize>, String>,
}
// And that strict parsing still works even when encoded.
let form5: Option<OwnedFormWithDefaults> = lenient("").ok();
assert_eq!(form5, Some(OwnedFormWithDefaults {
btreemap: {
let mut map = BTreeMap::new();
map.insert(vec![3, 7, 9], "prime numbers".into());
map.insert(vec![1, 2], "one/ and+two".into());
map.insert(vec![], "empty!/!? neat".into());
map
}
}));
let form = form5.unwrap();
let form_string = format!("{}", &form as &dyn UriDisplay<Query>);
let form6: form::Result<'_, OwnedFormWithDefaults> = strict_encoded(&form_string);
assert_eq!(form6, Ok(form));
}
#[test]
@ -842,3 +871,65 @@ fn test_lazy_default() {
missing3: 42
}));
}
#[derive(Debug, PartialEq, FromForm, UriDisplayQuery)]
#[field(validate = len(3..), default = "some default hello")]
struct Token<'r>(&'r str);
#[derive(Debug, PartialEq, FromForm, UriDisplayQuery)]
#[field(validate = try_with(|s| s.parse::<usize>()), default = "123456")]
struct TokenOwned(String);
#[test]
fn wrapper_works() {
let form: Option<Token> = lenient("").ok();
assert_eq!(form, Some(Token("some default hello")));
let form: Option<TokenOwned> = lenient("").ok();
assert_eq!(form, Some(TokenOwned("123456".into())));
let errors = strict::<Token>("").unwrap_err();
assert!(errors.iter().any(|e| matches!(e.kind, ErrorKind::Missing)));
let form: Option<Token> = lenient("=hi there").ok();
assert_eq!(form, Some(Token("hi there")));
let form: Option<TokenOwned> = strict_encoded("=2318").ok();
assert_eq!(form, Some(TokenOwned("2318".into())));
let errors = lenient::<Token>("=hi").unwrap_err();
assert!(errors.iter().any(|e| matches!(e.kind, ErrorKind::InvalidLength { .. })));
let errors = lenient::<TokenOwned>("=hellothere").unwrap_err();
assert!(errors.iter().any(|e| matches!(e.kind, ErrorKind::Validation { .. })));
}
#[derive(Debug, PartialEq, FromForm, UriDisplayQuery)]
struct JsonToken<T>(Json<T>);
#[test]
fn json_wrapper_works() {
let form: JsonToken<String> = lenient("=\"hello\"").unwrap();
assert_eq!(form, JsonToken(Json("hello".into())));
let form: JsonToken<usize> = lenient("=10").unwrap();
assert_eq!(form, JsonToken(Json(10)));
let form: JsonToken<()> = lenient("=null").unwrap();
assert_eq!(form, JsonToken(Json(())));
let form: JsonToken<Vec<usize>> = lenient("=[1, 4, 3, 9]").unwrap();
assert_eq!(form, JsonToken(Json(vec![1, 4, 3, 9])));
let string = String::from("=\"foo bar\"");
let form: JsonToken<&str> = lenient(&string).unwrap();
assert_eq!(form, JsonToken(Json("foo bar")));
}
// FIXME: https://github.com/rust-lang/rust/issues/86706
#[allow(private_in_public)]
struct Q<T>(T);
// This is here to ensure we don't warn, which we can't test with trybuild.
#[derive(FromForm)]
pub struct JsonTokenBad<T>(Q<T>);

View File

@ -11,7 +11,7 @@ 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: tuple structs are not supported
error: at least one field is required
--> $DIR/from_form.rs:7:1
|
7 | struct Foo1;
@ -37,24 +37,11 @@ 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: tuple structs are not supported
--> $DIR/from_form.rs:13:1
error: tuple struct must have exactly one field
--> $DIR/from_form.rs:16:12
|
13 | struct Foo3(usize);
| ^^^^^^^^^^^^^^^^^^^
|
note: error occurred while deriving `FromForm`
--> $DIR/from_form.rs:12:10
|
12 | #[derive(FromForm)]
| ^^^^^^^^
= note: this error originates in the derive macro `FromForm` (in Nightly builds, run with -Z macro-backtrace for more info)
error: only one lifetime is supported
--> $DIR/from_form.rs:16:25
|
16 | struct NextTodoTask<'f, 'a> {
| ^^
16 | struct Foo4(usize, usize, usize);
| ^^^^^^^^^^^^^^^^^^^^^
|
note: error occurred while deriving `FromForm`
--> $DIR/from_form.rs:15:10
@ -63,381 +50,394 @@ 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: invalid form field name
--> $DIR/from_form.rs:25:20
error: only one lifetime is supported
--> $DIR/from_form.rs:19:25
|
25 | #[field(name = "isindex")]
19 | struct NextTodoTask<'f, 'a> {
| ^^
|
note: error occurred while deriving `FromForm`
--> $DIR/from_form.rs:18:10
|
18 | #[derive(FromForm)]
| ^^^^^^^^
= note: this error originates in the derive macro `FromForm` (in Nightly builds, run with -Z macro-backtrace for more info)
error: invalid form field name
--> $DIR/from_form.rs:28:20
|
28 | #[field(name = "isindex")]
| ^^^^^^^^^
|
= help: field name cannot be `isindex` or contain '&', '=', '?', '.', '[', ']'
note: error occurred while deriving `FromForm`
--> $DIR/from_form.rs:23:10
--> $DIR/from_form.rs:26:10
|
23 | #[derive(FromForm)]
26 | #[derive(FromForm)]
| ^^^^^^^^
= note: this error originates in the derive macro `FromForm` (in Nightly builds, run with -Z macro-backtrace for more info)
error: field name conflicts with previous name
--> $DIR/from_form.rs:33:5
--> $DIR/from_form.rs:36:5
|
33 | foo: usize,
36 | foo: usize,
| ^^^
|
help: declared in this field
--> $DIR/from_form.rs:33:5
--> $DIR/from_form.rs:36:5
|
33 | foo: usize,
36 | foo: usize,
| ^^^^^^^^^^
note: previous field with conflicting name
--> $DIR/from_form.rs:31:5
--> $DIR/from_form.rs:34:5
|
31 | / #[field(name = "foo")]
32 | | field: String,
34 | / #[field(name = "foo")]
35 | | field: String,
| |_________________^
note: error occurred while deriving `FromForm`
--> $DIR/from_form.rs:29:10
--> $DIR/from_form.rs:32:10
|
29 | #[derive(FromForm)]
32 | #[derive(FromForm)]
| ^^^^^^^^
= note: this error originates in the derive macro `FromForm` (in Nightly builds, run with -Z macro-backtrace for more info)
error: field name conflicts with previous name
--> $DIR/from_form.rs:40:20
--> $DIR/from_form.rs:43:20
|
40 | #[field(name = "hello")]
43 | #[field(name = "hello")]
| ^^^^^^^
|
help: declared in this field
--> $DIR/from_form.rs:40:5
--> $DIR/from_form.rs:43:5
|
40 | / #[field(name = "hello")]
41 | | other: String,
43 | / #[field(name = "hello")]
44 | | other: String,
| |_________________^
note: previous field with conflicting name
--> $DIR/from_form.rs:38:5
--> $DIR/from_form.rs:41:5
|
38 | / #[field(name = "hello")]
39 | | first: String,
41 | / #[field(name = "hello")]
42 | | first: String,
| |_________________^
note: error occurred while deriving `FromForm`
--> $DIR/from_form.rs:36:10
--> $DIR/from_form.rs:39:10
|
36 | #[derive(FromForm)]
39 | #[derive(FromForm)]
| ^^^^^^^^
= note: this error originates in the derive macro `FromForm` (in Nightly builds, run with -Z macro-backtrace for more info)
error: field name conflicts with previous name
--> $DIR/from_form.rs:47:20
--> $DIR/from_form.rs:50:20
|
47 | #[field(name = "first")]
50 | #[field(name = "first")]
| ^^^^^^^
|
help: declared in this field
--> $DIR/from_form.rs:47:5
--> $DIR/from_form.rs:50:5
|
47 | / #[field(name = "first")]
48 | | other: String,
50 | / #[field(name = "first")]
51 | | other: String,
| |_________________^
note: previous field with conflicting name
--> $DIR/from_form.rs:46:5
--> $DIR/from_form.rs:49:5
|
46 | first: String,
49 | first: String,
| ^^^^^^^^^^^^^
note: error occurred while deriving `FromForm`
--> $DIR/from_form.rs:44:10
--> $DIR/from_form.rs:47:10
|
44 | #[derive(FromForm)]
47 | #[derive(FromForm)]
| ^^^^^^^^
= note: this error originates in the derive macro `FromForm` (in Nightly builds, run with -Z macro-backtrace for more info)
error: unexpected attribute parameter: `field`
--> $DIR/from_form.rs:53:28
--> $DIR/from_form.rs:56:28
|
53 | #[field(name = "blah", field = "bloo")]
56 | #[field(name = "blah", field = "bloo")]
| ^^^^^^^^^^^^^^
|
note: error occurred while deriving `FromForm`
--> $DIR/from_form.rs:51:10
--> $DIR/from_form.rs:54:10
|
51 | #[derive(FromForm)]
54 | #[derive(FromForm)]
| ^^^^^^^^
= note: this error originates in the derive macro `FromForm` (in Nightly builds, run with -Z macro-backtrace for more info)
error: expected list `#[field(..)]`, found bare path "field"
--> $DIR/from_form.rs:59:7
--> $DIR/from_form.rs:62:7
|
59 | #[field]
62 | #[field]
| ^^^^^
|
note: error occurred while deriving `FromForm`
--> $DIR/from_form.rs:57:10
--> $DIR/from_form.rs:60:10
|
57 | #[derive(FromForm)]
60 | #[derive(FromForm)]
| ^^^^^^^^
= note: this error originates in the derive macro `FromForm` (in Nightly builds, run with -Z macro-backtrace for more info)
error: expected key/value `key = value`
--> $DIR/from_form.rs:65:13
--> $DIR/from_form.rs:68:13
|
65 | #[field("blah")]
68 | #[field("blah")]
| ^^^^^^
|
note: error occurred while deriving `FromForm`
--> $DIR/from_form.rs:63:10
--> $DIR/from_form.rs:66:10
|
63 | #[derive(FromForm)]
66 | #[derive(FromForm)]
| ^^^^^^^^
= note: this error originates in the derive macro `FromForm` (in Nightly builds, run with -Z macro-backtrace for more info)
error: expected key/value `key = value`
--> $DIR/from_form.rs:71:13
--> $DIR/from_form.rs:74:13
|
71 | #[field(123)]
74 | #[field(123)]
| ^^^
|
note: error occurred while deriving `FromForm`
--> $DIR/from_form.rs:69:10
--> $DIR/from_form.rs:72:10
|
69 | #[derive(FromForm)]
72 | #[derive(FromForm)]
| ^^^^^^^^
= note: this error originates in the derive macro `FromForm` (in Nightly builds, run with -Z macro-backtrace for more info)
error: unexpected attribute parameter: `beep`
--> $DIR/from_form.rs:77:13
--> $DIR/from_form.rs:80:13
|
77 | #[field(beep = "bop")]
80 | #[field(beep = "bop")]
| ^^^^^^^^^^^^
|
note: error occurred while deriving `FromForm`
--> $DIR/from_form.rs:75:10
--> $DIR/from_form.rs:78:10
|
75 | #[derive(FromForm)]
78 | #[derive(FromForm)]
| ^^^^^^^^
= note: this error originates in the derive macro `FromForm` (in Nightly builds, run with -Z macro-backtrace for more info)
error: field has conflicting names
--> $DIR/from_form.rs:83:5
--> $DIR/from_form.rs:86:5
|
83 | / #[field(name = "blah")]
84 | | #[field(name = "blah")]
85 | | my_field: String,
86 | / #[field(name = "blah")]
87 | | #[field(name = "blah")]
88 | | my_field: String,
| |____________________^
|
note: this field name...
--> $DIR/from_form.rs:83:20
--> $DIR/from_form.rs:86:20
|
83 | #[field(name = "blah")]
86 | #[field(name = "blah")]
| ^^^^^^
note: ...conflicts with this field name
--> $DIR/from_form.rs:84:20
--> $DIR/from_form.rs:87:20
|
84 | #[field(name = "blah")]
87 | #[field(name = "blah")]
| ^^^^^^
note: error occurred while deriving `FromForm`
--> $DIR/from_form.rs:81:10
--> $DIR/from_form.rs:84:10
|
81 | #[derive(FromForm)]
84 | #[derive(FromForm)]
| ^^^^^^^^
= note: this error originates in the derive macro `FromForm` (in Nightly builds, run with -Z macro-backtrace for more info)
error: expected list `#[attr(..)]`, found bare boolean literal
--> $DIR/from_form.rs:90:20
--> $DIR/from_form.rs:93:20
|
90 | #[field(name = true)]
93 | #[field(name = true)]
| ^^^^
|
note: error occurred while deriving `FromForm`
--> $DIR/from_form.rs:88:10
--> $DIR/from_form.rs:91:10
|
88 | #[derive(FromForm)]
91 | #[derive(FromForm)]
| ^^^^^^^^
= note: this error originates in the derive macro `FromForm` (in Nightly builds, run with -Z macro-backtrace for more info)
error: expected expression, found bare path "name"
--> $DIR/from_form.rs:96:13
--> $DIR/from_form.rs:99:13
|
96 | #[field(name)]
99 | #[field(name)]
| ^^^^
|
note: error occurred while deriving `FromForm`
--> $DIR/from_form.rs:94:10
--> $DIR/from_form.rs:97:10
|
94 | #[derive(FromForm)]
97 | #[derive(FromForm)]
| ^^^^^^^^
= note: this error originates in the derive macro `FromForm` (in Nightly builds, run with -Z macro-backtrace for more info)
error: expected list `#[attr(..)]`, found bare integer literal
--> $DIR/from_form.rs:102:20
--> $DIR/from_form.rs:105:20
|
102 | #[field(name = 123)]
105 | #[field(name = 123)]
| ^^^
|
note: error occurred while deriving `FromForm`
--> $DIR/from_form.rs:100:10
--> $DIR/from_form.rs:103:10
|
100 | #[derive(FromForm)]
103 | #[derive(FromForm)]
| ^^^^^^^^
= note: this error originates in the derive macro `FromForm` (in Nightly builds, run with -Z macro-backtrace for more info)
error: invalid form field name
--> $DIR/from_form.rs:108:20
--> $DIR/from_form.rs:111:20
|
108 | #[field(name = "hello&world")]
111 | #[field(name = "hello&world")]
| ^^^^^^^^^^^^^
|
= help: field name cannot be `isindex` or contain '&', '=', '?', '.', '[', ']'
note: error occurred while deriving `FromForm`
--> $DIR/from_form.rs:106:10
--> $DIR/from_form.rs:109:10
|
106 | #[derive(FromForm)]
109 | #[derive(FromForm)]
| ^^^^^^^^
= note: this error originates in the derive macro `FromForm` (in Nightly builds, run with -Z macro-backtrace for more info)
error: invalid form field name
--> $DIR/from_form.rs:114:20
--> $DIR/from_form.rs:117:20
|
114 | #[field(name = "!@#$%^&*()_")]
117 | #[field(name = "!@#$%^&*()_")]
| ^^^^^^^^^^^^^
|
= help: field name cannot be `isindex` or contain '&', '=', '?', '.', '[', ']'
note: error occurred while deriving `FromForm`
--> $DIR/from_form.rs:112:10
--> $DIR/from_form.rs:115:10
|
112 | #[derive(FromForm)]
115 | #[derive(FromForm)]
| ^^^^^^^^
= note: this error originates in the derive macro `FromForm` (in Nightly builds, run with -Z macro-backtrace for more info)
error: invalid form field name
--> $DIR/from_form.rs:120:20
--> $DIR/from_form.rs:123:20
|
120 | #[field(name = "?")]
123 | #[field(name = "?")]
| ^^^
|
= help: field name cannot be `isindex` or contain '&', '=', '?', '.', '[', ']'
note: error occurred while deriving `FromForm`
--> $DIR/from_form.rs:118:10
--> $DIR/from_form.rs:121:10
|
118 | #[derive(FromForm)]
121 | #[derive(FromForm)]
| ^^^^^^^^
= note: this error originates in the derive macro `FromForm` (in Nightly builds, run with -Z macro-backtrace for more info)
error: invalid form field name
--> $DIR/from_form.rs:126:20
--> $DIR/from_form.rs:129:20
|
126 | #[field(name = "")]
129 | #[field(name = "")]
| ^^
|
= help: field name cannot be `isindex` or contain '&', '=', '?', '.', '[', ']'
note: error occurred while deriving `FromForm`
--> $DIR/from_form.rs:124:10
--> $DIR/from_form.rs:127:10
|
124 | #[derive(FromForm)]
127 | #[derive(FromForm)]
| ^^^^^^^^
= note: this error originates in the derive macro `FromForm` (in Nightly builds, run with -Z macro-backtrace for more info)
error: invalid form field name
--> $DIR/from_form.rs:132:20
--> $DIR/from_form.rs:135:20
|
132 | #[field(name = "a&b")]
135 | #[field(name = "a&b")]
| ^^^^^
|
= help: field name cannot be `isindex` or contain '&', '=', '?', '.', '[', ']'
note: error occurred while deriving `FromForm`
--> $DIR/from_form.rs:130:10
--> $DIR/from_form.rs:133:10
|
130 | #[derive(FromForm)]
133 | #[derive(FromForm)]
| ^^^^^^^^
= note: this error originates in the derive macro `FromForm` (in Nightly builds, run with -Z macro-backtrace for more info)
error: invalid form field name
--> $DIR/from_form.rs:138:20
--> $DIR/from_form.rs:141:20
|
138 | #[field(name = "a=")]
141 | #[field(name = "a=")]
| ^^^^
|
= help: field name cannot be `isindex` or contain '&', '=', '?', '.', '[', ']'
note: error occurred while deriving `FromForm`
--> $DIR/from_form.rs:136:10
--> $DIR/from_form.rs:139:10
|
136 | #[derive(FromForm)]
139 | #[derive(FromForm)]
| ^^^^^^^^
= note: this error originates in the derive macro `FromForm` (in Nightly builds, run with -Z macro-backtrace for more info)
error: duplicate attribute parameter: default
--> $DIR/from_form.rs:174:26
--> $DIR/from_form.rs:177:26
|
174 | #[field(default = 1, default = 2)]
177 | #[field(default = 1, default = 2)]
| ^^^^^^^^^^^
|
note: error occurred while deriving `FromForm`
--> $DIR/from_form.rs:172:10
--> $DIR/from_form.rs:175:10
|
172 | #[derive(FromForm)]
175 | #[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 field expression
--> $DIR/from_form.rs:181:23
--> $DIR/from_form.rs:184:23
|
181 | #[field(default = 2)]
184 | #[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
--> $DIR/from_form.rs:181:10
|
178 | #[derive(FromForm)]
181 | #[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
--> $DIR/from_form.rs:190:23
|
187 | #[field(default = 1, default_with = None)]
190 | #[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
--> $DIR/from_form.rs:190:41
|
187 | #[field(default = 1, default_with = None)]
190 | #[field(default = 1, default_with = None)]
| ^^^^
note: error occurred while deriving `FromForm`
--> $DIR/from_form.rs:185:10
--> $DIR/from_form.rs:188:10
|
185 | #[derive(FromForm)]
188 | #[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
--> $DIR/from_form.rs:197:23
|
194 | #[field(default = 1)]
197 | #[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
--> $DIR/from_form.rs:196:28
|
193 | #[field(default_with = None)]
196 | #[field(default_with = None)]
| ^^^^
note: error occurred while deriving `FromForm`
--> $DIR/from_form.rs:191:10
--> $DIR/from_form.rs:194:10
|
191 | #[derive(FromForm)]
194 | #[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
--> $DIR/from_form.rs:153:24
|
150 | #[field(validate = unknown())]
153 | #[field(validate = unknown())]
| ^^^^^^^ not found in this scope
error[E0308]: mismatched types
--> $DIR/from_form.rs:144:24
--> $DIR/from_form.rs:147:24
|
144 | #[field(validate = 123)]
147 | #[field(validate = 123)]
| -------- ^^^ expected enum `Result`, found integer
| |
| expected due to this
@ -446,33 +446,33 @@ error[E0308]: mismatched types
found type `{integer}`
error[E0308]: mismatched types
--> $DIR/from_form.rs:157:5
--> $DIR/from_form.rs:160:12
|
157 | first: String,
| ^^^^^^^^^^^^^ expected enum `TempFile`, found struct `std::string::String`
160 | first: String,
| ^^^^^^ expected enum `TempFile`, found struct `std::string::String`
|
= note: expected reference `&TempFile<'_>`
found reference `&std::string::String`
error[E0308]: mismatched types
--> $DIR/from_form.rs:163:5
--> $DIR/from_form.rs:166:12
|
163 | first: String,
| ^^^^^^^^^^^^^ expected enum `TempFile`, found struct `std::string::String`
166 | first: String,
| ^^^^^^ expected enum `TempFile`, found struct `std::string::String`
|
= note: expected reference `&TempFile<'_>`
found reference `&std::string::String`
error[E0308]: mismatched types
--> $DIR/from_form.rs:162:28
--> $DIR/from_form.rs:165:28
|
162 | #[field(validate = ext("hello"))]
165 | #[field(validate = ext("hello"))]
| ^^^^^^^ expected struct `ContentType`, found `&str`
error[E0277]: the trait bound `i32: From<&str>` is not satisfied
--> $DIR/from_form.rs:168:23
--> $DIR/from_form.rs:171:23
|
168 | #[field(default = "no conversion")]
171 | #[field(default = "no conversion")]
| ^^^^^^^^^^^^^^^ the trait `From<&str>` is not implemented for `i32`
|
= help: the following implementations were found:
@ -484,14 +484,14 @@ error[E0277]: the trait bound `i32: From<&str>` is not satisfied
= note: required because of the requirements on the impl of `Into<i32>` for `&str`
error[E0308]: mismatched types
--> $DIR/from_form.rs:200:33
--> $DIR/from_form.rs:203:33
|
200 | #[field(default_with = Some("hi"))]
203 | #[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()))]
203 | #[field(default_with = Some("hi".to_string()))]
| ^^^^^^^^^^^^^^^^
200 | #[field(default_with = Some("hi".to_string()))]
203 | #[field(default_with = Some("hi".to_string()))]
| ^^^^^^^^^^^^^^^^

View File

@ -4,7 +4,7 @@ error[E0277]: the trait bound `Unknown: FromFormField<'_>` is not satisfied
7 | field: Unknown,
| ^^^^^^^ the trait `FromFormField<'_>` is not implemented for `Unknown`
|
= note: required because of the requirements on the impl of `FromForm<'__f>` for `Unknown`
= note: required because of the requirements on the impl of `FromForm<'r>` for `Unknown`
error[E0277]: the trait bound `Foo<usize>: FromFormField<'_>` is not satisfied
--> $DIR/from_form_type_errors.rs:14:12
@ -12,4 +12,4 @@ error[E0277]: the trait bound `Foo<usize>: FromFormField<'_>` is not satisfied
14 | field: Foo<usize>,
| ^^^^^^^^^^ the trait `FromFormField<'_>` is not implemented for `Foo<usize>`
|
= note: required because of the requirements on the impl of `FromForm<'__f>` for `Foo<usize>`
= note: required because of the requirements on the impl of `FromForm<'r>` for `Foo<usize>`

View File

@ -12,7 +12,7 @@ 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: tuple structs are not supported
error: at least one field is required
--> $DIR/from_form.rs:7:1
|
7 | struct Foo1;
@ -40,25 +40,11 @@ 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: tuple structs are not supported
--> $DIR/from_form.rs:13:1
error: tuple struct must have exactly one field
--> $DIR/from_form.rs:16:12
|
13 | struct Foo3(usize);
| ^^^^^^
error: [note] error occurred while deriving `FromForm`
--> $DIR/from_form.rs:12:10
|
12 | #[derive(FromForm)]
| ^^^^^^^^
|
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: only one lifetime is supported
--> $DIR/from_form.rs:16:25
|
16 | struct NextTodoTask<'f, 'a> {
| ^^
16 | struct Foo4(usize, usize, usize);
| ^^^^^^^^^^^^^^^^^^^^^
error: [note] error occurred while deriving `FromForm`
--> $DIR/from_form.rs:15:10
@ -68,408 +54,422 @@ 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: only one lifetime is supported
--> $DIR/from_form.rs:19:25
|
19 | struct NextTodoTask<'f, 'a> {
| ^^
error: [note] error occurred while deriving `FromForm`
--> $DIR/from_form.rs:18:10
|
18 | #[derive(FromForm)]
| ^^^^^^^^
|
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: invalid form field name
--- help: field name cannot be `isindex` or contain '&', '=', '?', '.', '[', ']'
--> $DIR/from_form.rs:25:20
--> $DIR/from_form.rs:28:20
|
25 | #[field(name = "isindex")]
28 | #[field(name = "isindex")]
| ^^^^^^^^^
error: [note] error occurred while deriving `FromForm`
--> $DIR/from_form.rs:23:10
--> $DIR/from_form.rs:26:10
|
23 | #[derive(FromForm)]
26 | #[derive(FromForm)]
| ^^^^^^^^
|
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: field name conflicts with previous name
--> $DIR/from_form.rs:33:5
--> $DIR/from_form.rs:36:5
|
33 | foo: usize,
36 | foo: usize,
| ^^^
error: [help] declared in this field
--> $DIR/from_form.rs:33:5
--> $DIR/from_form.rs:36:5
|
33 | foo: usize,
36 | foo: usize,
| ^^^
error: [note] previous field with conflicting name
--> $DIR/from_form.rs:31:5
--> $DIR/from_form.rs:34:5
|
31 | #[field(name = "foo")]
34 | #[field(name = "foo")]
| ^
error: [note] error occurred while deriving `FromForm`
--> $DIR/from_form.rs:29:10
--> $DIR/from_form.rs:32:10
|
29 | #[derive(FromForm)]
32 | #[derive(FromForm)]
| ^^^^^^^^
|
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: field name conflicts with previous name
--> $DIR/from_form.rs:40:20
--> $DIR/from_form.rs:43:20
|
40 | #[field(name = "hello")]
43 | #[field(name = "hello")]
| ^^^^^^^
error: [help] declared in this field
--> $DIR/from_form.rs:40:5
--> $DIR/from_form.rs:43:5
|
40 | #[field(name = "hello")]
43 | #[field(name = "hello")]
| ^
error: [note] previous field with conflicting name
--> $DIR/from_form.rs:38:5
--> $DIR/from_form.rs:41:5
|
38 | #[field(name = "hello")]
41 | #[field(name = "hello")]
| ^
error: [note] error occurred while deriving `FromForm`
--> $DIR/from_form.rs:36:10
--> $DIR/from_form.rs:39:10
|
36 | #[derive(FromForm)]
39 | #[derive(FromForm)]
| ^^^^^^^^
|
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: field name conflicts with previous name
--> $DIR/from_form.rs:47:20
--> $DIR/from_form.rs:50:20
|
47 | #[field(name = "first")]
50 | #[field(name = "first")]
| ^^^^^^^
error: [help] declared in this field
--> $DIR/from_form.rs:47:5
--> $DIR/from_form.rs:50:5
|
47 | #[field(name = "first")]
50 | #[field(name = "first")]
| ^
error: [note] previous field with conflicting name
--> $DIR/from_form.rs:46:5
--> $DIR/from_form.rs:49:5
|
46 | first: String,
49 | first: String,
| ^^^^^
error: [note] error occurred while deriving `FromForm`
--> $DIR/from_form.rs:44:10
--> $DIR/from_form.rs:47:10
|
44 | #[derive(FromForm)]
47 | #[derive(FromForm)]
| ^^^^^^^^
|
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: unexpected attribute parameter: `field`
--> $DIR/from_form.rs:53:28
--> $DIR/from_form.rs:56:28
|
53 | #[field(name = "blah", field = "bloo")]
56 | #[field(name = "blah", field = "bloo")]
| ^^^^^
error: [note] error occurred while deriving `FromForm`
--> $DIR/from_form.rs:51:10
--> $DIR/from_form.rs:54:10
|
51 | #[derive(FromForm)]
54 | #[derive(FromForm)]
| ^^^^^^^^
|
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: expected list `#[field(..)]`, found bare path "field"
--> $DIR/from_form.rs:59:7
--> $DIR/from_form.rs:62:7
|
59 | #[field]
62 | #[field]
| ^^^^^
error: [note] error occurred while deriving `FromForm`
--> $DIR/from_form.rs:57:10
--> $DIR/from_form.rs:60:10
|
57 | #[derive(FromForm)]
60 | #[derive(FromForm)]
| ^^^^^^^^
|
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: expected key/value `key = value`
--> $DIR/from_form.rs:65:13
--> $DIR/from_form.rs:68:13
|
65 | #[field("blah")]
68 | #[field("blah")]
| ^^^^^^
error: [note] error occurred while deriving `FromForm`
--> $DIR/from_form.rs:63:10
--> $DIR/from_form.rs:66:10
|
63 | #[derive(FromForm)]
66 | #[derive(FromForm)]
| ^^^^^^^^
|
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: expected key/value `key = value`
--> $DIR/from_form.rs:71:13
--> $DIR/from_form.rs:74:13
|
71 | #[field(123)]
74 | #[field(123)]
| ^^^
error: [note] error occurred while deriving `FromForm`
--> $DIR/from_form.rs:69:10
--> $DIR/from_form.rs:72:10
|
69 | #[derive(FromForm)]
72 | #[derive(FromForm)]
| ^^^^^^^^
|
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: unexpected attribute parameter: `beep`
--> $DIR/from_form.rs:77:13
--> $DIR/from_form.rs:80:13
|
77 | #[field(beep = "bop")]
80 | #[field(beep = "bop")]
| ^^^^
error: [note] error occurred while deriving `FromForm`
--> $DIR/from_form.rs:75:10
--> $DIR/from_form.rs:78:10
|
75 | #[derive(FromForm)]
78 | #[derive(FromForm)]
| ^^^^^^^^
|
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: field has conflicting names
--> $DIR/from_form.rs:83:5
--> $DIR/from_form.rs:86:5
|
83 | #[field(name = "blah")]
86 | #[field(name = "blah")]
| ^
error: [note] this field name...
--> $DIR/from_form.rs:83:20
--> $DIR/from_form.rs:86:20
|
83 | #[field(name = "blah")]
86 | #[field(name = "blah")]
| ^^^^^^
error: [note] ...conflicts with this field name
--> $DIR/from_form.rs:84:20
--> $DIR/from_form.rs:87:20
|
84 | #[field(name = "blah")]
87 | #[field(name = "blah")]
| ^^^^^^
error: [note] error occurred while deriving `FromForm`
--> $DIR/from_form.rs:81:10
--> $DIR/from_form.rs:84:10
|
81 | #[derive(FromForm)]
84 | #[derive(FromForm)]
| ^^^^^^^^
|
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: expected list `#[attr(..)]`, found bare boolean literal
--> $DIR/from_form.rs:90:20
--> $DIR/from_form.rs:93:20
|
90 | #[field(name = true)]
93 | #[field(name = true)]
| ^^^^
error: [note] error occurred while deriving `FromForm`
--> $DIR/from_form.rs:88:10
--> $DIR/from_form.rs:91:10
|
88 | #[derive(FromForm)]
91 | #[derive(FromForm)]
| ^^^^^^^^
|
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: expected expression, found bare path "name"
--> $DIR/from_form.rs:96:13
--> $DIR/from_form.rs:99:13
|
96 | #[field(name)]
99 | #[field(name)]
| ^^^^
error: [note] error occurred while deriving `FromForm`
--> $DIR/from_form.rs:94:10
--> $DIR/from_form.rs:97:10
|
94 | #[derive(FromForm)]
97 | #[derive(FromForm)]
| ^^^^^^^^
|
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: expected list `#[attr(..)]`, found bare integer literal
--> $DIR/from_form.rs:102:20
--> $DIR/from_form.rs:105:20
|
102 | #[field(name = 123)]
105 | #[field(name = 123)]
| ^^^
error: [note] error occurred while deriving `FromForm`
--> $DIR/from_form.rs:100:10
--> $DIR/from_form.rs:103:10
|
100 | #[derive(FromForm)]
103 | #[derive(FromForm)]
| ^^^^^^^^
|
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: invalid form field name
--- help: field name cannot be `isindex` or contain '&', '=', '?', '.', '[', ']'
--> $DIR/from_form.rs:108:20
--> $DIR/from_form.rs:111:20
|
108 | #[field(name = "hello&world")]
111 | #[field(name = "hello&world")]
| ^^^^^^^^^^^^^
error: [note] error occurred while deriving `FromForm`
--> $DIR/from_form.rs:106:10
--> $DIR/from_form.rs:109:10
|
106 | #[derive(FromForm)]
109 | #[derive(FromForm)]
| ^^^^^^^^
|
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: invalid form field name
--- help: field name cannot be `isindex` or contain '&', '=', '?', '.', '[', ']'
--> $DIR/from_form.rs:114:20
--> $DIR/from_form.rs:117:20
|
114 | #[field(name = "!@#$%^&*()_")]
117 | #[field(name = "!@#$%^&*()_")]
| ^^^^^^^^^^^^^
error: [note] error occurred while deriving `FromForm`
--> $DIR/from_form.rs:112:10
--> $DIR/from_form.rs:115:10
|
112 | #[derive(FromForm)]
115 | #[derive(FromForm)]
| ^^^^^^^^
|
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: invalid form field name
--- help: field name cannot be `isindex` or contain '&', '=', '?', '.', '[', ']'
--> $DIR/from_form.rs:120:20
--> $DIR/from_form.rs:123:20
|
120 | #[field(name = "?")]
123 | #[field(name = "?")]
| ^^^
error: [note] error occurred while deriving `FromForm`
--> $DIR/from_form.rs:118:10
--> $DIR/from_form.rs:121:10
|
118 | #[derive(FromForm)]
121 | #[derive(FromForm)]
| ^^^^^^^^
|
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: invalid form field name
--- help: field name cannot be `isindex` or contain '&', '=', '?', '.', '[', ']'
--> $DIR/from_form.rs:126:20
--> $DIR/from_form.rs:129:20
|
126 | #[field(name = "")]
129 | #[field(name = "")]
| ^^
error: [note] error occurred while deriving `FromForm`
--> $DIR/from_form.rs:124:10
--> $DIR/from_form.rs:127:10
|
124 | #[derive(FromForm)]
127 | #[derive(FromForm)]
| ^^^^^^^^
|
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: invalid form field name
--- help: field name cannot be `isindex` or contain '&', '=', '?', '.', '[', ']'
--> $DIR/from_form.rs:132:20
--> $DIR/from_form.rs:135:20
|
132 | #[field(name = "a&b")]
135 | #[field(name = "a&b")]
| ^^^^^
error: [note] error occurred while deriving `FromForm`
--> $DIR/from_form.rs:130:10
--> $DIR/from_form.rs:133:10
|
130 | #[derive(FromForm)]
133 | #[derive(FromForm)]
| ^^^^^^^^
|
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: invalid form field name
--- help: field name cannot be `isindex` or contain '&', '=', '?', '.', '[', ']'
--> $DIR/from_form.rs:138:20
--> $DIR/from_form.rs:141:20
|
138 | #[field(name = "a=")]
141 | #[field(name = "a=")]
| ^^^^
error: [note] error occurred while deriving `FromForm`
--> $DIR/from_form.rs:136:10
--> $DIR/from_form.rs:139:10
|
136 | #[derive(FromForm)]
139 | #[derive(FromForm)]
| ^^^^^^^^
|
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: duplicate attribute parameter: default
--> $DIR/from_form.rs:174:26
--> $DIR/from_form.rs:177:26
|
174 | #[field(default = 1, default = 2)]
177 | #[field(default = 1, default = 2)]
| ^^^^^^^
error: [note] error occurred while deriving `FromForm`
--> $DIR/from_form.rs:172:10
--> $DIR/from_form.rs:175:10
|
172 | #[derive(FromForm)]
175 | #[derive(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
--> $DIR/from_form.rs:184:23
|
181 | #[field(default = 2)]
184 | #[field(default = 2)]
| ^
error: [note] error occurred while deriving `FromForm`
--> $DIR/from_form.rs:178:10
--> $DIR/from_form.rs:181:10
|
178 | #[derive(FromForm)]
181 | #[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
--> $DIR/from_form.rs:190:23
|
187 | #[field(default = 1, default_with = None)]
190 | #[field(default = 1, default_with = None)]
| ^
error: [note] other default expression is here
--> $DIR/from_form.rs:187:41
--> $DIR/from_form.rs:190:41
|
187 | #[field(default = 1, default_with = None)]
190 | #[field(default = 1, default_with = None)]
| ^^^^
error: [note] error occurred while deriving `FromForm`
--> $DIR/from_form.rs:185:10
--> $DIR/from_form.rs:188:10
|
185 | #[derive(FromForm)]
188 | #[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
--> $DIR/from_form.rs:197:23
|
194 | #[field(default = 1)]
197 | #[field(default = 1)]
| ^
error: [note] other default expression is here
--> $DIR/from_form.rs:193:28
--> $DIR/from_form.rs:196:28
|
193 | #[field(default_with = None)]
196 | #[field(default_with = None)]
| ^^^^
error: [note] error occurred while deriving `FromForm`
--> $DIR/from_form.rs:191:10
--> $DIR/from_form.rs:194:10
|
191 | #[derive(FromForm)]
194 | #[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
--> $DIR/from_form.rs:153:24
|
150 | #[field(validate = unknown())]
153 | #[field(validate = unknown())]
| ^^^^^^^ not found in this scope
error[E0308]: mismatched types
--> $DIR/from_form.rs:144:24
--> $DIR/from_form.rs:147:24
|
144 | #[field(validate = 123)]
147 | #[field(validate = 123)]
| -------- ^^^ expected enum `Result`, found integer
| |
| expected due to this
@ -478,33 +478,33 @@ error[E0308]: mismatched types
found type `{integer}`
error[E0308]: mismatched types
--> $DIR/from_form.rs:157:12
--> $DIR/from_form.rs:160:12
|
157 | first: String,
160 | first: String,
| ^^^^^^ expected enum `TempFile`, found struct `std::string::String`
|
= note: expected reference `&TempFile<'_>`
found reference `&std::string::String`
error[E0308]: mismatched types
--> $DIR/from_form.rs:163:12
--> $DIR/from_form.rs:166:12
|
163 | first: String,
166 | first: String,
| ^^^^^^ expected enum `TempFile`, found struct `std::string::String`
|
= note: expected reference `&TempFile<'_>`
found reference `&std::string::String`
error[E0308]: mismatched types
--> $DIR/from_form.rs:162:28
--> $DIR/from_form.rs:165:28
|
162 | #[field(validate = ext("hello"))]
165 | #[field(validate = ext("hello"))]
| ^^^^^^^ expected struct `ContentType`, found `&str`
error[E0277]: the trait bound `i32: From<&str>` is not satisfied
--> $DIR/from_form.rs:168:23
--> $DIR/from_form.rs:171:23
|
168 | #[field(default = "no conversion")]
171 | #[field(default = "no conversion")]
| ^^^^^^^^^^^^^^^ the trait `From<&str>` is not implemented for `i32`
|
= help: the following implementations were found:
@ -516,14 +516,14 @@ error[E0277]: the trait bound `i32: From<&str>` is not satisfied
= note: required because of the requirements on the impl of `Into<i32>` for `&str`
error[E0308]: mismatched types
--> $DIR/from_form.rs:200:33
--> $DIR/from_form.rs:203:33
|
200 | #[field(default_with = Some("hi"))]
203 | #[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()))]
203 | #[field(default_with = Some("hi".to_string()))]
| ^^^^^^^^^^^^^^^^
200 | #[field(default_with = Some("hi".to_string()))]
203 | #[field(default_with = Some("hi".to_string()))]
| ^^^^^^^^^^^^^^^^

View File

@ -4,7 +4,7 @@ error[E0277]: the trait bound `Unknown: FromFormField<'_>` is not satisfied
7 | field: Unknown,
| ^^^^^^^ the trait `FromFormField<'_>` is not implemented for `Unknown`
|
= note: required because of the requirements on the impl of `FromForm<'__f>` for `Unknown`
= note: required because of the requirements on the impl of `FromForm<'r>` for `Unknown`
error[E0277]: the trait bound `Foo<usize>: FromFormField<'_>` is not satisfied
--> $DIR/from_form_type_errors.rs:14:12
@ -12,4 +12,4 @@ error[E0277]: the trait bound `Foo<usize>: FromFormField<'_>` is not satisfied
14 | field: Foo<usize>,
| ^^^ the trait `FromFormField<'_>` is not implemented for `Foo<usize>`
|
= note: required because of the requirements on the impl of `FromForm<'__f>` for `Foo<usize>`
= note: required because of the requirements on the impl of `FromForm<'r>` for `Foo<usize>`

View File

@ -5,6 +5,10 @@ fn ui() {
_ => "ui-fail-stable"
};
let glob = std::env::args().last()
.map(|arg| format!("*{}*.rs", arg))
.unwrap_or_else(|| "*.rs".into());
let t = trybuild::TestCases::new();
t.compile_fail(format!("tests/{}/*.rs", path));
t.compile_fail(format!("tests/{}/{}", path, glob));
}

View File

@ -1,4 +1,4 @@
#[macro_use] extern crate rocket;
use rocket::form::FromForm;
#[derive(FromForm)]
enum Thing { }
@ -12,6 +12,9 @@ struct Foo2 { }
#[derive(FromForm)]
struct Foo3(usize);
#[derive(FromForm)]
struct Foo4(usize, usize, usize);
#[derive(FromForm)]
struct NextTodoTask<'f, 'a> {
description: String,
@ -201,4 +204,16 @@ struct Default5 {
no_conversion_from_with: String,
}
#[derive(FromForm)] // NO ERROR
struct Another<T> {
_foo: T,
_bar: T,
}
#[derive(FromForm)] // NO ERROR
struct AnotherOne<T> { // NO ERROR
_foo: T,
_bar: T,
}
fn main() { }

View File

@ -841,3 +841,41 @@ pub fn with<'v, V, F, M>(value: V, f: F, msg: M) -> Result<'v, ()>
Ok(())
}
/// _Try_ With validator: succeeds when an arbitrary function or closure does.
///
/// Along with [`with`], this is the most generic validator. It succeeds
/// excactly when `f` returns `Ok` and fails otherwise.
///
/// On failure, returns a validation error with the message in the `Err`
/// variant converted into a string.
///
/// # Example
///
/// Assuming `Token` has a `from_str` method:
///
/// ```rust
/// # use rocket::form::FromForm;
/// # impl FromStr for Token<'_> {
/// # type Err = &'static str;
/// # fn from_str(s: &str) -> Result<Self, Self::Err> { todo!() }
/// # }
/// use std::str::FromStr;
///
/// #[derive(FromForm)]
/// #[field(validate = try_with(|s| Token::from_str(s)))]
/// struct Token<'r>(&'r str);
///
/// #[derive(FromForm)]
/// #[field(validate = try_with(|s| s.parse::<Token>()))]
/// struct Token2<'r>(&'r str);
/// ```
pub fn try_with<'v, V, F, T, E>(value: V, f: F) -> Result<'v, ()>
where F: FnOnce(V) -> std::result::Result<T, E>,
E: std::fmt::Display
{
match f(value) {
Ok(_) => Ok(()),
Err(e) => Err(Error::validation(e.to_string()).into())
}
}