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

View File

@ -1,8 +1,13 @@
use devise::{*, ext::{TypeExt, SpanDiagnosticExt, GenericsExt, Split2, quote_respanned}};
use proc_macro2::TokenStream; use proc_macro2::TokenStream;
use devise::ext::{TypeExt, SpanDiagnosticExt, GenericsExt, quote_respanned};
use syn::parse::Parser;
use devise::*;
use crate::exports::*; use crate::exports::*;
use crate::derive::form_field::{*, FieldName::*}; 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) // F: fn(field_ty: Ty, field_context: Expr)
fn fields_map<F>(fields: Fields<'_>, map_f: F) -> Result<TokenStream> 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![]; let mut matchers = vec![];
for field in fields.iter() { for field in fields.iter() {
let (ident, ty) = (field.ident(), field.stripped_ty()); let (ident, ty) = (field.context_ident(), field.stripped_ty());
let field_context = quote_spanned!(ty.span() => { let field_context: syn::Expr = syn::parse2(quote_spanned!(ty.span() => {
let __o = __c.__opts; 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 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 }), Cased(name) => quote!(#name => { #push }),
Uncased(name) => quote!(__n if __n.as_uncased() == #name => { #push }), Uncased(name) => quote!(__n if __n.as_uncased() == #name => { #push }),
}); }));
matchers.extend(field_matchers);
} }
Ok(quote! { 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 mut gen = input.generics().clone();
let lifetime = syn::parse_quote!('__f); let lifetime = syn::parse_quote!('r);
if !gen.replace_lifetime(0, &lifetime) { if !gen.replace_lifetime(0, &lifetime) {
gen.insert_lifetime(syn::LifetimeDef::new(lifetime.clone())); gen.insert_lifetime(syn::LifetimeDef::new(lifetime.clone()));
} }
let span = input.ident().span(); gen.add_where_predicates(generic_bounds(input)?);
gen.add_type_bound(syn::parse_quote!(#_form::FromForm<#lifetime>)); let ty = quote_spanned!(input.ident().span() => FromFormGeneratedContext);
gen.add_type_bound(syn::TypeParamBound::from(lifetime)); Ok((ty, gen))
let (_, ty_gen, where_clause) = gen.split_for_impl();
(quote_spanned!(span => FromFormGeneratedContext #ty_gen), where_clause.cloned())
} }
pub fn derive_from_form(input: proc_macro::TokenStream) -> TokenStream { pub fn derive_from_form(input: proc_macro::TokenStream) -> TokenStream {
DeriveGenerator::build_for(input, quote!(impl<'__f> #_form::FromForm<'__f>)) DeriveGenerator::build_for(input, quote!(impl<'r> #_form::FromForm<'r>))
// NOTE: If support is widened, fix `FieldExt::ident()` `expect()`. .support(Support::Struct | Support::Lifetime | Support::Type)
.support(Support::NamedStruct | Support::Lifetime | Support::Type)
.replace_generic(0, 0) .replace_generic(0, 0)
.type_bound(quote!(#_form::FromForm<'__f> + '__f)) .type_bound_mapper(MapperBuild::new().try_input_map(|_, i| generic_bounds_tokens(i)))
.validator(ValidatorBuild::new() .validator(ValidatorBuild::new()
.input_validate(|_, i| match i.generics().lifetimes().enumerate().last() { .input_validate(|_, i| match i.generics().lifetimes().enumerate().last() {
Some((i, lt)) if i >= 1 => Err(lt.span().error("only one lifetime is supported")), 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| { .fields_validate(|_, fields| {
if fields.is_empty() { if fields.is_empty() {
return Err(fields.span().error("at least one field is required")); return Err(fields.span().error("at least one field is required"));
} } else if fields.are_unnamed() && fields.count() != 1 {
return Err(fields.span().error("tuple struct must have exactly one field"));
if let Some(d) = first_duplicate(fields.iter(), |f| f.field_names())? { } 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_a_i, field_a, name_a) = d.0;
let (field_b_i, field_b, name_b) = d.1; 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() .outer_mapper(MapperBuild::new()
.try_input_map(|mapper, input| { .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)?; let output = mapper::input_default(mapper, input)?;
Ok(quote! { Ok(quote_spanned! { input.span() =>
/// Rocket generated FormForm context. /// Rocket generated FormForm context.
#[doc(hidden)] #[doc(hidden)]
pub struct #ctxt_ty #where_clause { #[allow(private_in_public)]
#vis struct #ctxt_ty #impl_gen #where_clause {
__opts: #_form::Options, __opts: #_form::Options,
__errors: #_form::Errors<'__f>, __errors: #_form::Errors<'r>,
__parent: #_Option<&'__f #_form::Name>, __parent: #_Option<&'r #_form::Name>,
#output #output
} }
}) })
}) })
.try_fields_map(|m, f| mapper::fields_null(m, f)) .try_fields_map(|m, f| mapper::fields_null(m, f))
.field_map(|_, field| { .field_map(|_, field| {
let (ident, mut ty) = (field.ident(), field.stripped_ty()); let ident = field.context_ident();
ty.replace_lifetimes(syn::parse_quote!('__f)); let mut ty = field.stripped_ty();
ty.replace_lifetimes(syn::parse_quote!('r));
let field_ty = quote_respanned!(ty.span() => 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,) quote_spanned!(ty.span() => #ident: #field_ty,)
@ -118,13 +148,15 @@ pub fn derive_from_form(input: proc_macro::TokenStream) -> TokenStream {
#[allow(unused_imports)] #[allow(unused_imports)]
use #_http::uncased::AsUncased; use #_http::uncased::AsUncased;
}) })
.outer_mapper(quote!(#[allow(private_in_public)]))
.outer_mapper(quote!(#[rocket::async_trait])) .outer_mapper(quote!(#[rocket::async_trait]))
.inner_mapper(MapperBuild::new() .inner_mapper(MapperBuild::new()
.try_input_map(|mapper, input| { .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)?; let output = mapper::input_default(mapper, input)?;
Ok(quote! { Ok(quote! {
type Context = #ctxt_ty; type Context = #ctxt_ty #ty_gen;
fn init(__opts: #_form::Options) -> Self::Context { fn init(__opts: #_form::Options) -> Self::Context {
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)) .try_fields_map(|m, f| mapper::fields_null(m, f))
.field_map(|_, field| { .field_map(|_, field| {
let ident = field.ident.as_ref().expect("named"); let ident = field.context_ident();
let ty = field.ty.with_stripped_lifetimes(); let ty = field.ty.with_stripped_lifetimes();
quote_spanned!(ty.span() => quote_spanned!(ty.span() => #ident: #_None,)
#ident: #_None,
// #ident: <#ty as #_form::FromForm<'__f>>::init(__opts),
)
}) })
) )
.inner_mapper(MapperBuild::new() .inner_mapper(MapperBuild::new()
.with_output(|_, output| quote! { .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 #output
} }
}) })
.try_fields_map(|_, f| fields_map(f, |ty, ctxt| quote_spanned!(ty.span() => { .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() .inner_mapper(MapperBuild::new()
.try_input_map(|mapper, input| { .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)?; let output = mapper::input_default(mapper, input)?;
Ok(quote! { Ok(quote! {
async fn push_data( async fn push_data(
__c: &mut #ctxt_ty, __c: &mut #ctxt_ty #ty_gen,
__f: #_form::DataField<'__f, '_> __f: #_form::DataField<'r, '_>
) { ) {
#output #output
} }
}) })
}) })
// Without the `let _fut`, we get a wild lifetime error. It don't // 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() => { .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; _fut.await;
}))) })))
) )
.inner_mapper(MapperBuild::new() .inner_mapper(MapperBuild::new()
.with_output(|_, output| quote! { .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)] #[allow(unused_imports)]
use #_form::validate::*; use #_form::validate::*;
@ -186,21 +216,24 @@ pub fn derive_from_form(input: proc_macro::TokenStream) -> TokenStream {
} }
}) })
.try_fields_map(|mapper, fields| { .try_fields_map(|mapper, fields| {
// This validates the attributes so we can `unwrap()` later.
let finalize_field = fields.iter() let finalize_field = fields.iter()
.map(|f| mapper.map_field(f)) .map(|f| mapper.map_field(f))
.collect::<Result<Vec<TokenStream>>>()?; .collect::<Result<Vec<TokenStream>>>()?;
let ident: Vec<_> = fields.iter()
.map(|f| f.ident().clone())
.collect();
let o = syn::Ident::new("__o", fields.span()); let o = syn::Ident::new("__o", fields.span());
let (_ok, _some, _err, _none) = (_Ok, _Some, _Err, _None); let (_ok, _some, _err, _none) = (_Ok, _Some, _Err, _None);
let (name_view, validate) = fields.iter() let validate = fields.iter().flat_map(|f| validators(f, &o, false).unwrap());
.map(|f| (f.name_view().unwrap(), validators(f, &o, false).unwrap())) let name_buf_opt = fields.iter().map(|f| f.name_buf_opt().unwrap());
.map(|(nv, vs)| vs.map(move |v| (nv.clone(), v)))
.flatten() let ident: Vec<_> = fields.iter()
.split2(); .map(|f| f.context_ident())
.collect();
let builder = fields.builder(|f| {
let ident = f.context_ident();
quote!(#ident.unwrap())
});
Ok(quote_spanned! { fields.span() => Ok(quote_spanned! { fields.span() =>
#(let #ident = match #finalize_field { #(let #ident = match #finalize_field {
@ -212,11 +245,14 @@ pub fn derive_from_form(input: proc_macro::TokenStream) -> TokenStream {
return #_Err(__c.__errors); return #_Err(__c.__errors);
} }
let #o = Self { #(#ident: #ident.unwrap()),* }; let #o = #builder;
#( #(
if let #_err(__e) = #validate { 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| { .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 validator = validators(f, &ident, true)?;
let name_buf_opt = f.name_buf_opt()?;
let default = default(f)? let default = default(f)?
.unwrap_or_else(|| quote_spanned!(ty.span() => { .unwrap_or_else(|| quote_spanned!(ty.span() => {
<#ty as #_form::FromForm<'__f>>::default(__opts) <#ty as #_form::FromForm<'r>>::default(__opts)
})); }));
let _err = _Err; let _err = _Err;
Ok(quote_spanned! { ty.span() => { Ok(quote_spanned! { ty.span() => {
let __name = #name_view;
let __opts = __c.__opts; let __opts = __c.__opts;
let __name = #name_buf_opt;
__c.#ident __c.#ident
.map_or_else( .map_or_else(
|| #default.ok_or_else(|| #_form::ErrorKind::Missing.into()), || #default.ok_or_else(|| #_form::ErrorKind::Missing.into()),
<#ty as #_form::FromForm<'__f>>::finalize <#ty as #_form::FromForm<'r>>::finalize
) )
.and_then(|#ident| { .and_then(|#ident| {
let mut __es = #_form::Errors::new(); let mut __es = #_form::Errors::new();
#(if let #_err(__e) = #validator { __es.extend(__e); })* #(if let #_err(__e) = #validator { __es.extend(__e); })*
__es.is_empty().then(|| #ident).ok_or(__es) __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() .map_err(|__e| __e.is_empty()
.then(|| #_form::ErrorKind::Unknown.into()) .then(|| #_form::ErrorKind::Unknown.into())
.unwrap_or(__e)) .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"; const EXACTLY_ONE_FIELD: &str = "struct must have exactly one field";
pub fn derive_uri_display_query(input: proc_macro::TokenStream) -> TokenStream { pub fn derive_uri_display_query(input: proc_macro::TokenStream) -> TokenStream {
const URI_DISPLAY: StaticTokens = quote_static!(#_fmt::UriDisplay<#_fmt::Query>); const URI_DISPLAY: StaticTokens = quote_static!(#_fmt::UriDisplay<#_fmt::Query>);
const FORMATTER: StaticTokens = quote_static!(#_fmt::Formatter<#_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| { .try_field_map(|_, field| {
let span = field.span().into(); let span = field.span().into();
let accessor = field.accessor(); let accessor = field.accessor();
let tokens = if field.ident.is_some() { let tokens = if let Some(name) = field.first_field_name()? {
let name = field.first_field_name()?;
quote_spanned!(span => f.write_named_value(#name, &#accessor)?;) quote_spanned!(span => f.write_named_value(#name, &#accessor)?;)
} else { } else {
quote_spanned!(span => f.write_value(&#accessor)?;) 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. /// 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 /// ```rust
/// # #[macro_use] extern crate rocket; /// use rocket::form::FromForm;
///
/// #[derive(FromForm)] /// #[derive(FromForm)]
/// struct MyStruct<'r> { /// struct MyStruct<'r> {
/// field: usize, /// field: usize,
@ -562,19 +564,36 @@ pub fn derive_from_form_field(input: TokenStream) -> TokenStream {
/// #[field(default = None)] /// #[field(default = None)]
/// is_nice: bool, /// 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 /// The derive generates an implementation of the [`FromForm`] trait.
/// 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 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 /// ```text
/// field := name? default? validate* /// field := name? default? validate*
@ -592,8 +611,9 @@ pub fn derive_from_form_field(input: TokenStream) -> TokenStream {
/// EXPR := valid expression, as defined by Rust /// EXPR := valid expression, as defined by Rust
/// ``` /// ```
/// ///
/// The attribute can be applied any number of times on a field as long as at /// `#[field]` can be applied any number of times on a field. `default` and
/// most _one_ of `default` or `default_with` is present per field: /// `default_with` are mutually exclusive: at most _one_ of `default` or
/// `default_with` can be present per field.
/// ///
/// ```rust /// ```rust
/// # #[macro_use] extern crate rocket; /// # #[macro_use] extern crate rocket;
@ -610,54 +630,118 @@ pub fn derive_from_form_field(input: TokenStream) -> TokenStream {
/// } /// }
/// ``` /// ```
/// ///
/// **`name`** /// For tuples structs, the `field` attribute can be applied to the structure
/// /// itself:
/// 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 /// ```rust
/// # #[macro_use] extern crate rocket; /// # #[macro_use] extern crate rocket;
/// use std::num::NonZeroUsize;
///
/// #[derive(FromForm)] /// #[derive(FromForm)]
/// struct MyForm { /// #[field(default = 42, validate = eq(42))]
/// // `NonZeroUsize::new()` return an `Option<NonZeroUsize>`. /// struct Meaning(usize);
/// #[field(default_with = NonZeroUsize::new(42))]
/// num: NonZeroUsize,
/// }
/// ``` /// ```
/// ///
/// ## 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 /// [`FromForm`]: rocket::form::FromForm
/// [`form::Errors`]: rocket::form::Errors /// [`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 { pub fn derive_from_form(input: TokenStream) -> TokenStream {
emit!(derive::from_form::derive_from_form(input)) 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 /// 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 /// 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: /// Some "names" in Rocket include:
/// * Dynamic parameter: `name` in `<name>` /// * Dynamic parameter: `name` in `<name>`

View File

@ -2,7 +2,11 @@ use std::net::{IpAddr, SocketAddr};
use std::collections::{BTreeMap, HashMap}; use std::collections::{BTreeMap, HashMap};
use pretty_assertions::assert_eq; 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::{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>> { fn strict<'f, T: FromForm<'f>>(string: &'f str) -> Result<T, Errors<'f>> {
Form::<Strict<T>>::parse(string).map(|s| s.into_inner()) 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) 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> where for<'a> T: FromForm<'a>
{ {
Form::<Strict<T>>::parse_encoded(string.into()).map(|s| s.into_inner()) Form::<Strict<T>>::parse_encoded(string.into()).map(|s| s.into_inner())
@ -331,8 +335,6 @@ fn generics() {
#[test] #[test]
fn form_errors() { fn form_errors() {
use rocket::form::error::{ErrorKind, Entity};
#[derive(Debug, PartialEq, FromForm)] #[derive(Debug, PartialEq, FromForm)]
struct WhoopsForm { struct WhoopsForm {
complete: bool, complete: bool,
@ -671,16 +673,19 @@ fn test_defaults() {
fn test_hashmap() -> HashMap<&'static str, &'static str> { fn test_hashmap() -> HashMap<&'static str, &'static str> {
let mut map = HashMap::new(); let mut map = HashMap::new();
map.insert("key", "value"); map.insert("key", "value");
map.insert("one-more", "good-value");
map map
} }
fn test_btreemap() -> BTreeMap<&'static str, &'static str> { fn test_btreemap() -> BTreeMap<Vec<usize>, &'static str> {
let mut map = BTreeMap::new(); 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 map
} }
#[derive(FromForm, PartialEq, Debug)] #[derive(FromForm, UriDisplayQuery, PartialEq, Debug)]
struct FormWithDefaults<'a> { struct FormWithDefaults<'a> {
field2: i128, field2: i128,
field5: bool, field5: bool,
@ -708,7 +713,7 @@ fn test_defaults() {
#[field(default = test_hashmap())] #[field(default = test_hashmap())]
hashmap: HashMap<&'a str, &'a str>, hashmap: HashMap<&'a str, &'a str>,
#[field(default = test_btreemap())] #[field(default = test_btreemap())]
btreemap: BTreeMap<&'a str, &'a str>, btreemap: BTreeMap<Vec<usize>, &'a str>,
#[field(default_with = Some(false))] #[field(default_with = Some(false))]
boolean: bool, boolean: bool,
#[field(default_with = (|| Some(777))())] #[field(default_with = (|| Some(777))())]
@ -789,15 +794,39 @@ fn test_defaults() {
})); }));
// And that strict parsing still works. // And that strict parsing still works.
let form4: Option<FormWithDefaults> = strict(&form_string).ok(); let form = form3.unwrap();
assert_eq!(form4, Some(FormWithDefaults { let form_string = format!("{}", &form as &dyn UriDisplay<Query>);
field1: 101, let form4: form::Result<'_, FormWithDefaults> = strict(&form_string);
field2: 102, assert_eq!(form4, Ok(form));
field3: true,
field4: false, #[derive(FromForm, UriDisplayQuery, PartialEq, Debug)]
field5: true, struct OwnedFormWithDefaults {
..form3.unwrap() #[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] #[test]
@ -842,3 +871,65 @@ fn test_lazy_default() {
missing3: 42 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) = 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 --> $DIR/from_form.rs:7:1
| |
7 | struct Foo1; 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) = 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: tuple struct must have exactly one field
--> $DIR/from_form.rs:13:1 --> $DIR/from_form.rs:16:12
| |
13 | struct Foo3(usize); 16 | struct Foo4(usize, usize, 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> {
| ^^
| |
note: error occurred while deriving `FromForm` note: error occurred while deriving `FromForm`
--> $DIR/from_form.rs:15:10 --> $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) = 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 error: only one lifetime is supported
--> $DIR/from_form.rs:25:20 --> $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 '&', '=', '?', '.', '[', ']' = help: field name cannot be `isindex` or contain '&', '=', '?', '.', '[', ']'
note: error occurred while deriving `FromForm` 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) = 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 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 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 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")]
32 | | field: String, 35 | | field: String,
| |_________________^ | |_________________^
note: error occurred while deriving `FromForm` 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) = 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 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 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")]
41 | | other: String, 44 | | other: String,
| |_________________^ | |_________________^
note: previous field with conflicting name 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")]
39 | | first: String, 42 | | first: String,
| |_________________^ | |_________________^
note: error occurred while deriving `FromForm` 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) = 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 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 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")]
48 | | other: String, 51 | | other: String,
| |_________________^ | |_________________^
note: previous field with conflicting name 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` 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) = 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` 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` 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) = 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" 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` 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) = 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` 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` 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) = 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` 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` 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) = 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` 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` 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) = 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 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")]
84 | | #[field(name = "blah")] 87 | | #[field(name = "blah")]
85 | | my_field: String, 88 | | my_field: String,
| |____________________^ | |____________________^
| |
note: this field name... 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 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` 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) = 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 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` 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) = 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" 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` 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) = 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 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` 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) = 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 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 '&', '=', '?', '.', '[', ']' = help: field name cannot be `isindex` or contain '&', '=', '?', '.', '[', ']'
note: error occurred while deriving `FromForm` 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) = 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 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 '&', '=', '?', '.', '[', ']' = help: field name cannot be `isindex` or contain '&', '=', '?', '.', '[', ']'
note: error occurred while deriving `FromForm` 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) = 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 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 '&', '=', '?', '.', '[', ']' = help: field name cannot be `isindex` or contain '&', '=', '?', '.', '[', ']'
note: error occurred while deriving `FromForm` 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) = 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 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 '&', '=', '?', '.', '[', ']' = help: field name cannot be `isindex` or contain '&', '=', '?', '.', '[', ']'
note: error occurred while deriving `FromForm` 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) = 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 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 '&', '=', '?', '.', '[', ']' = help: field name cannot be `isindex` or contain '&', '=', '?', '.', '[', ']'
note: error occurred while deriving `FromForm` 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) = 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 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 '&', '=', '?', '.', '[', ']' = help: field name cannot be `isindex` or contain '&', '=', '?', '.', '[', ']'
note: error occurred while deriving `FromForm` 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) = 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 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` 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) = 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 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 = help: at most one `default` or `default_with` is allowed
note: error occurred while deriving `FromForm` 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) = note: this error originates in the derive macro `FromForm` (in Nightly builds, run with -Z macro-backtrace for more info)
error: duplicate default expressions 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 = help: only one of `default` or `default_with` must be used
note: other default expression is here 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` 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) = note: this error originates in the derive macro `FromForm` (in Nightly builds, run with -Z macro-backtrace for more info)
error: duplicate default expressions 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 = help: only one of `default` or `default_with` must be used
note: other default expression is here 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` 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) = note: this error originates in the derive macro `FromForm` (in Nightly builds, run with -Z macro-backtrace for more info)
error[E0425]: cannot find function `unknown` in this scope error[E0425]: cannot find function `unknown` in this scope
--> $DIR/from_form.rs:150:24 --> $DIR/from_form.rs:153:24
| |
150 | #[field(validate = unknown())] 153 | #[field(validate = unknown())]
| ^^^^^^^ not found in this scope | ^^^^^^^ not found in this scope
error[E0308]: mismatched types 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 enum `Result`, found integer
| | | |
| expected due to this | expected due to this
@ -446,33 +446,33 @@ error[E0308]: mismatched types
found type `{integer}` found type `{integer}`
error[E0308]: mismatched types error[E0308]: mismatched types
--> $DIR/from_form.rs:157:5 --> $DIR/from_form.rs:160:12
| |
157 | first: String, 160 | first: String,
| ^^^^^^^^^^^^^ expected enum `TempFile`, found struct `std::string::String` | ^^^^^^ expected enum `TempFile`, found struct `std::string::String`
| |
= note: expected reference `&TempFile<'_>` = note: expected reference `&TempFile<'_>`
found reference `&std::string::String` found reference `&std::string::String`
error[E0308]: mismatched types error[E0308]: mismatched types
--> $DIR/from_form.rs:163:5 --> $DIR/from_form.rs:166:12
| |
163 | first: String, 166 | first: String,
| ^^^^^^^^^^^^^ expected enum `TempFile`, found struct `std::string::String` | ^^^^^^ expected enum `TempFile`, found struct `std::string::String`
| |
= note: expected reference `&TempFile<'_>` = note: expected reference `&TempFile<'_>`
found reference `&std::string::String` found reference `&std::string::String`
error[E0308]: mismatched types 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` | ^^^^^^^ expected struct `ContentType`, found `&str`
error[E0277]: the trait bound `i32: From<&str>` is not satisfied 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` | ^^^^^^^^^^^^^^^ the trait `From<&str>` is not implemented for `i32`
| |
= help: the following implementations were found: = 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` = note: required because of the requirements on the impl of `Into<i32>` for `&str`
error[E0308]: mismatched types 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` | ^^^^ expected struct `std::string::String`, found `&str`
| |
help: try using a conversion method 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, 7 | field: Unknown,
| ^^^^^^^ the trait `FromFormField<'_>` is not implemented for `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 error[E0277]: the trait bound `Foo<usize>: FromFormField<'_>` is not satisfied
--> $DIR/from_form_type_errors.rs:14:12 --> $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>, 14 | field: Foo<usize>,
| ^^^^^^^^^^ the trait `FromFormField<'_>` is not implemented for `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) = 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 --> $DIR/from_form.rs:7:1
| |
7 | struct Foo1; 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) = 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: tuple struct must have exactly one field
--> $DIR/from_form.rs:13:1 --> $DIR/from_form.rs:16:12
| |
13 | struct Foo3(usize); 16 | struct Foo4(usize, usize, 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> {
| ^^
error: [note] error occurred while deriving `FromForm` error: [note] error occurred while deriving `FromForm`
--> $DIR/from_form.rs:15:10 --> $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) = 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 error: invalid form field name
--- help: field name cannot be `isindex` or contain '&', '=', '?', '.', '[', ']' --- 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` 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) = 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 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 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 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` 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) = 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 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 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 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` 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) = 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 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 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 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` 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) = note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: unexpected attribute parameter: `field` 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` 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) = 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" 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` 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) = 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` 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` 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) = 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` 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` 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) = note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: unexpected attribute parameter: `beep` 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` 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) = note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: field has conflicting names 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... 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 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` 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) = 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 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` 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) = 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" 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` 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) = 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 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` 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) = note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: invalid form field name error: invalid form field name
--- help: field name cannot be `isindex` or contain '&', '=', '?', '.', '[', ']' --- 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` 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) = note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: invalid form field name error: invalid form field name
--- help: field name cannot be `isindex` or contain '&', '=', '?', '.', '[', ']' --- 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` 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) = note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: invalid form field name error: invalid form field name
--- help: field name cannot be `isindex` or contain '&', '=', '?', '.', '[', ']' --- 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` 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) = note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: invalid form field name error: invalid form field name
--- help: field name cannot be `isindex` or contain '&', '=', '?', '.', '[', ']' --- 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` 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) = note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: invalid form field name error: invalid form field name
--- help: field name cannot be `isindex` or contain '&', '=', '?', '.', '[', ']' --- 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` 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) = note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: invalid form field name error: invalid form field name
--- help: field name cannot be `isindex` or contain '&', '=', '?', '.', '[', ']' --- 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` 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) = note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: duplicate attribute parameter: default 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` 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) = note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: duplicate default field expression error: duplicate default field expression
--- help: at most one `default` or `default_with` is allowed --- 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` 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) = note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: duplicate default expressions error: duplicate default expressions
--- help: only one of `default` or `default_with` must be used --- 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 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` 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) = note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: duplicate default expressions error: duplicate default expressions
--- help: only one of `default` or `default_with` must be used --- 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 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` 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) = note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error[E0425]: cannot find function `unknown` in this scope error[E0425]: cannot find function `unknown` in this scope
--> $DIR/from_form.rs:150:24 --> $DIR/from_form.rs:153:24
| |
150 | #[field(validate = unknown())] 153 | #[field(validate = unknown())]
| ^^^^^^^ not found in this scope | ^^^^^^^ not found in this scope
error[E0308]: mismatched types 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 enum `Result`, found integer
| | | |
| expected due to this | expected due to this
@ -478,33 +478,33 @@ error[E0308]: mismatched types
found type `{integer}` found type `{integer}`
error[E0308]: mismatched types 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` | ^^^^^^ expected enum `TempFile`, found struct `std::string::String`
| |
= note: expected reference `&TempFile<'_>` = note: expected reference `&TempFile<'_>`
found reference `&std::string::String` found reference `&std::string::String`
error[E0308]: mismatched types 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` | ^^^^^^ expected enum `TempFile`, found struct `std::string::String`
| |
= note: expected reference `&TempFile<'_>` = note: expected reference `&TempFile<'_>`
found reference `&std::string::String` found reference `&std::string::String`
error[E0308]: mismatched types 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` | ^^^^^^^ expected struct `ContentType`, found `&str`
error[E0277]: the trait bound `i32: From<&str>` is not satisfied 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` | ^^^^^^^^^^^^^^^ the trait `From<&str>` is not implemented for `i32`
| |
= help: the following implementations were found: = 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` = note: required because of the requirements on the impl of `Into<i32>` for `&str`
error[E0308]: mismatched types 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` | ^^^^ expected struct `std::string::String`, found `&str`
| |
help: try using a conversion method 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, 7 | field: Unknown,
| ^^^^^^^ the trait `FromFormField<'_>` is not implemented for `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 error[E0277]: the trait bound `Foo<usize>: FromFormField<'_>` is not satisfied
--> $DIR/from_form_type_errors.rs:14:12 --> $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>, 14 | field: Foo<usize>,
| ^^^ the trait `FromFormField<'_>` is not implemented for `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" _ => "ui-fail-stable"
}; };
let glob = std::env::args().last()
.map(|arg| format!("*{}*.rs", arg))
.unwrap_or_else(|| "*.rs".into());
let t = trybuild::TestCases::new(); 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)] #[derive(FromForm)]
enum Thing { } enum Thing { }
@ -12,6 +12,9 @@ struct Foo2 { }
#[derive(FromForm)] #[derive(FromForm)]
struct Foo3(usize); struct Foo3(usize);
#[derive(FromForm)]
struct Foo4(usize, usize, usize);
#[derive(FromForm)] #[derive(FromForm)]
struct NextTodoTask<'f, 'a> { struct NextTodoTask<'f, 'a> {
description: String, description: String,
@ -201,4 +204,16 @@ struct Default5 {
no_conversion_from_with: String, 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() { } fn main() { }

View File

@ -841,3 +841,41 @@ pub fn with<'v, V, F, M>(value: V, f: F, msg: M) -> Result<'v, ()>
Ok(()) 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())
}
}