Allow several 'field' attributes in all derives.

In particular, 'FromFormField' and 'UriDisplayQuery' now allow any
number of form 'field' attributes. For the former, multiple 'value's are
allowed, all of which are used to match against incoming fields - any
match wins. For the latter, multiple 'name' and 'value's are allowed;
the first of each is used to render the query value.

Additionally, 'UriDisplayQuery' can now be derived for C-like enums.
This brings the derive to parity with 'FromFormValue' and allows their
unified application on C-like enums.

Resolves #843.
This commit is contained in:
Sergio Benitez 2021-04-07 22:49:34 -07:00
parent fd03417188
commit c45a83897f
16 changed files with 668 additions and 290 deletions

View File

@ -19,7 +19,7 @@ proc-macro = true
[dependencies]
quote = "1.0"
devise = { git = "https://github.com/SergioBenitez/Devise.git", rev = "3ebe83" }
devise = { git = "https://github.com/SergioBenitez/Devise.git", rev = "df00b5" }
[dev-dependencies]
rocket = { version = "0.5.0-dev", path = "../../core/lib" }

View File

@ -18,7 +18,7 @@ proc-macro = true
indexmap = "1.0"
quote = "1.0"
rocket_http = { version = "0.5.0-dev", path = "../http/" }
devise = { git = "https://github.com/SergioBenitez/Devise.git", rev = "3ebe83" }
devise = { git = "https://github.com/SergioBenitez/Devise.git", rev = "df00b5" }
unicode-xid = "0.2"
glob = "0.3"

View File

@ -3,7 +3,7 @@ use devise::{*, ext::{TypeExt, SpanDiagnosticExt}};
use syn::visit_mut::VisitMut;
use syn::visit::Visit;
use crate::proc_macro2::{TokenStream, TokenTree};
use crate::proc_macro2::{TokenStream, TokenTree, Span};
use crate::syn_ext::IdentExt;
use crate::name::Name;
@ -26,11 +26,50 @@ impl FieldAttr {
pub(crate) trait FieldExt {
fn ident(&self) -> &syn::Ident;
fn field_names(&self) -> Result<Vec<FieldName>>;
fn one_field_name(&self) -> Result<FieldName>;
fn first_field_name(&self) -> Result<FieldName>;
fn stripped_ty(&self) -> syn::Type;
fn name_view(&self) -> Result<syn::Expr>;
}
#[derive(FromMeta)]
pub struct VariantAttr {
pub value: Name,
}
impl VariantAttr {
const NAME: &'static str = "field";
}
pub(crate) trait VariantExt {
fn first_form_field_value(&self) -> Result<FieldName>;
fn form_field_values(&self) -> Result<Vec<FieldName>>;
}
impl VariantExt for Variant<'_> {
fn first_form_field_value(&self) -> Result<FieldName> {
let first = VariantAttr::from_attrs(VariantAttr::NAME, &self.attrs)?
.into_iter()
.next();
Ok(first.map_or_else(
|| FieldName::Uncased(Name::from(&self.ident)),
|attr| FieldName::Uncased(attr.value)))
}
fn form_field_values(&self) -> Result<Vec<FieldName>> {
let attr_values = VariantAttr::from_attrs(VariantAttr::NAME, &self.attrs)?
.into_iter()
.map(|attr| FieldName::Uncased(attr.value))
.collect::<Vec<_>>();
if attr_values.is_empty() {
return Ok(vec![FieldName::Uncased(Name::from(&self.ident))]);
}
Ok(attr_values)
}
}
impl FromMeta for FieldName {
fn from_meta(meta: &MetaItem) -> Result<Self> {
// These are used during parsing.
@ -124,17 +163,9 @@ impl FieldExt for Field<'_> {
Ok(attr_names)
}
fn one_field_name(&self) -> Result<FieldName> {
fn first_field_name(&self) -> Result<FieldName> {
let mut names = self.field_names()?.into_iter();
let first = names.next().expect("always have >= 1 name");
if let Some(name) = names.next() {
return Err(name.span()
.error("unexpected second field name")
.note("only one field rename is allowed in this context"));
}
Ok(first)
Ok(names.next().expect("always have >= 1 name"))
}
fn stripped_ty(&self) -> syn::Type {
@ -293,3 +324,31 @@ pub fn validators<'v>(
Ok(exprs)
}
pub fn first_duplicate<K: Spanned, V: PartialEq + Spanned>(
keys: impl Iterator<Item = K> + Clone,
values: impl Fn(&K) -> Result<Vec<V>>,
) -> Result<Option<((usize, Span, Span), (usize, Span, Span))>> {
let (mut all_values, mut key_map) = (vec![], vec![]);
for key in keys {
all_values.append(&mut values(&key)?);
key_map.push((all_values.len(), key));
}
// get the key corresponding to all_value index `k`.
let key = |k| key_map.iter().find(|(i, _)| k < *i).expect("k < *i");
for (i, a) in all_values.iter().enumerate() {
let rest = all_values.iter().enumerate().skip(i + 1);
if let Some((j, b)) = rest.filter(|(_, b)| *b == a).next() {
let (a_i, key_a) = key(i);
let (b_i, key_b) = key(j);
let a = (*a_i, key_a.span(), a.span());
let b = (*b_i, key_b.span(), b.span());
return Ok(Some((a, b)));
}
}
Ok(None)
}

View File

@ -70,33 +70,19 @@ pub fn derive_from_form(input: proc_macro::TokenStream) -> TokenStream {
return Err(fields.span().error("at least one field is required"));
}
let mut names = vec![];
let mut field_map = vec![];
for field in fields.iter() {
names.append(&mut field.field_names()?);
field_map.push((names.len(), field));
}
if let Some(d) = first_duplicate(fields.iter(), |f| f.field_names())? {
let (field_a_i, field_a, name_a) = d.0;
let (field_b_i, field_b, name_b) = d.1;
// get the field corresponding to name index `k`.
let field = |k| field_map.iter().find(|(i, _)| k < *i).expect("k < *i");
for (i, a) in names.iter().enumerate() {
let rest = names.iter().enumerate().skip(i + 1);
if let Some((j, b)) = rest.filter(|(_, b)| b == &a).next() {
let ((fa_i, field_a), (fb_i, field_b)) = (field(i), field(j));
if fa_i == fb_i {
return Err(field_a.span()
.error("field has conflicting names")
.span_note(a.span(), "this field name...")
.span_note(b.span(), "...conflicts with this name"));
} else {
return Err(b.span()
.error("field name conflicts with previous name")
.span_note(field_a.span(), "previous field with conflicting name")
.span_help(field_b.span(), "field name is part of this field"));
}
if field_a_i == field_b_i {
return Err(field_a.error("field has conflicting names")
.span_note(name_a, "this field name...")
.span_note(name_b, "...conflicts with this field name"));
}
return Err(name_b.error("field name conflicts with previous name")
.span_help(field_b, "declared in this field")
.span_note(field_a, "previous field with conflicting name"));
}
Ok(())

View File

@ -2,12 +2,7 @@ use devise::{*, ext::SpanDiagnosticExt};
use crate::exports::*;
use crate::proc_macro2::TokenStream;
use crate::name::Name;
#[derive(FromMeta)]
pub struct FieldAttr {
value: Name,
}
use crate::derive::form_field::{VariantExt, first_duplicate};
pub fn derive_from_form_field(input: proc_macro::TokenStream) -> TokenStream {
DeriveGenerator::build_for(input, quote!(impl<'__v> #_form::FromFormField<'__v>))
@ -26,47 +21,68 @@ pub fn derive_from_form_field(input: proc_macro::TokenStream) -> TokenStream {
return Err(data.span().error("enum must have at least one variant"));
}
if let Some(d) = first_duplicate(data.variants(), |v| v.form_field_values())? {
let (variant_a_i, variant_a, value_a) = d.0;
let (variant_b_i, variant_b, value_b) = d.1;
if variant_a_i == variant_b_i {
return Err(variant_a.error("variant has conflicting values")
.span_note(value_a, "this value...")
.span_note(value_b, "...conflicts with this value"));
}
return Err(value_b.error("field value conflicts with previous value")
.span_help(variant_b, "...declared in this variant")
.span_note(variant_a, "previous field with conflicting name"));
}
Ok(())
})
)
// TODO: Devise should have a try_variant_map.
.outer_mapper(quote! {
#[allow(unused_imports)]
use #_http::uncased::AsUncased;
})
.inner_mapper(MapperBuild::new()
.try_enum_map(|_, data| {
let variant_name_sources = data.variants()
.map(|v| FieldAttr::one_from_attrs("field", &v.attrs).map(|o| {
o.map(|f| f.value).unwrap_or_else(|| Name::from(&v.ident))
}))
.collect::<Result<Vec<Name>>>()?;
.with_output(|_, output| quote! {
fn from_value(
__f: #_form::ValueField<'__v>
) -> Result<Self, #_form::Errors<'__v>> {
let variant_name = variant_name_sources.iter()
.map(|n| n.as_str())
.collect::<Vec<_>>();
#output
}
})
.try_enum_map(|mapper, data| {
let mut variant_value = vec![];
for v in data.variants().map(|v| v.form_field_values()) {
variant_value.append(&mut v?);
}
let builder = data.variants()
.map(|v| v.builder(|_| unreachable!("fieldless")));
let variant_condition = data.variants()
.map(|v| mapper.map_variant(v))
.collect::<Result<Vec<_>>>()?;
let (_ok, _cow) = (std::iter::repeat(_Ok), std::iter::repeat(_Cow));
Ok(quote! {
fn from_value(
__f: #_form::ValueField<'__v>
) -> Result<Self, #_form::Errors<'__v>> {
#[allow(unused_imports)]
use #_http::uncased::AsUncased;
#(#variant_condition)*
#(
if __f.value.as_uncased() == #variant_name {
return #_ok(#builder);
}
)*
const OPTS: &'static [#_Cow<'static, str>] =
&[#(#_cow::Borrowed(#variant_value)),*];
const OPTS: &'static [#_Cow<'static, str>] =
&[#(#_cow::Borrowed(#variant_name)),*];
let _error = #_form::Error::from(OPTS)
.with_name(__f.name)
.with_value(__f.value);
let _error = #_form::Error::from(OPTS)
.with_name(__f.name)
.with_value(__f.value);
#_Err(_error)?
})
})
.try_variant_map(|_, variant| {
let builder = variant.builder(|_| unreachable!("fieldless"));
let value = variant.form_field_values()?;
#_Err(_error)?
Ok(quote_spanned! { variant.span() =>
if #(__f.value.as_uncased() == #value)||* {
return #_Ok(#builder);
}
})
})

View File

@ -3,35 +3,15 @@ use devise::{*, ext::SpanDiagnosticExt};
use rocket_http::uri;
use crate::exports::*;
use crate::derive::form_field::FieldExt;
use crate::derive::form_field::{FieldExt, VariantExt};
use crate::proc_macro2::TokenStream;
const NO_EMPTY_FIELDS: &str = "fieldless structs or variants are not supported";
const NO_EMPTY_FIELDS: &str = "fieldless structs are not supported";
const NO_NULLARY: &str = "nullary items are not supported";
const NO_EMPTY_ENUMS: &str = "empty enums are not supported";
const ONLY_ONE_UNNAMED: &str = "tuple structs or variants must have exactly one field";
const EXACTLY_ONE_FIELD: &str = "struct must have exactly one field";
fn validate_fields(fields: Fields<'_>) -> Result<()> {
if fields.count() == 0 {
return Err(fields.parent.span().error(NO_EMPTY_FIELDS))
} else if fields.are_unnamed() && fields.count() > 1 {
return Err(fields.span().error(ONLY_ONE_UNNAMED));
} else if fields.are_unit() {
return Err(fields.span().error(NO_NULLARY));
}
Ok(())
}
fn validate_enum(data: Enum<'_>) -> Result<()> {
if data.variants().count() == 0 {
return Err(data.brace_token.span.error(NO_EMPTY_ENUMS));
}
Ok(())
}
pub fn derive_uri_display_query(input: proc_macro::TokenStream) -> TokenStream {
use crate::http::uri::Query;
@ -41,8 +21,30 @@ pub fn derive_uri_display_query(input: proc_macro::TokenStream) -> TokenStream {
let uri_display = DeriveGenerator::build_for(input.clone(), quote!(impl #URI_DISPLAY))
.support(Support::Struct | Support::Enum | Support::Type | Support::Lifetime)
.validator(ValidatorBuild::new()
.enum_validate(|_, v| validate_enum(v))
.fields_validate(|_, v| validate_fields(v))
.enum_validate(|_, data| {
if data.variants().count() == 0 {
return Err(data.brace_token.span.error(NO_EMPTY_ENUMS));
} else {
Ok(())
}
})
.struct_validate(|_, data| {
let fields = data.fields();
if fields.is_empty() {
Err(data.span().error(NO_EMPTY_FIELDS))
} else if fields.are_unit() {
Err(data.span().error(NO_NULLARY))
} else {
Ok(())
}
})
.fields_validate(|_, fields| {
if fields.are_unnamed() && fields.count() > 1 {
Err(fields.span().error(ONLY_ONE_UNNAMED))
} else {
Ok(())
}
})
)
.type_bound(URI_DISPLAY)
.inner_mapper(MapperBuild::new()
@ -52,11 +54,21 @@ pub fn derive_uri_display_query(input: proc_macro::TokenStream) -> TokenStream {
Ok(())
}
})
.try_variant_map(|mapper, variant| {
if !variant.fields().is_empty() {
return mapper::variant_default(mapper, variant);
}
let value = variant.first_form_field_value()?;
Ok(quote_spanned! { variant.span() =>
f.write_value(#value)?;
})
})
.try_field_map(|_, field| {
let span = field.span().into();
let accessor = field.accessor();
let tokens = if field.ident.is_some() {
let name = field.one_field_name()?;
let name = field.first_field_name()?;
quote_spanned!(span => f.write_named_value(#name, &#accessor)?;)
} else {
quote_spanned!(span => f.write_value(&#accessor)?;)

View File

@ -394,8 +394,8 @@ pub fn launch(args: TokenStream, input: TokenStream) -> TokenStream {
///
/// As an example, for the `enum` above, the form values `"first"`, `"FIRST"`,
/// `"fiRSt"`, and so on would parse as `MyValue::First`, while `"second"` and
/// `"third"` would parse as `MyValue::Second` and `MyValue::Third`,
/// respectively.
/// `"third"` (in any casing) would parse as `MyValue::Second` and
/// `MyValue::Third`, respectively.
///
/// The `field` field attribute can be used to change the string value that is
/// compared against for a given variant:
@ -408,10 +408,16 @@ pub fn launch(args: TokenStream, input: TokenStream) -> TokenStream {
/// First,
/// Second,
/// #[field(value = "fourth")]
/// #[field(value = "fifth")]
/// Third,
/// }
/// ```
///
/// When more than one `value` is specified, matching _any_ value will result in
/// parsing the decorated variant. Declaring any two values that are
/// case-insensitively equal to any other value or variant name is a
/// compile-time error.
///
/// The `#[field]` attribute's grammar is:
///
/// ```text
@ -422,8 +428,8 @@ pub fn launch(args: TokenStream, input: TokenStream) -> TokenStream {
///
/// The attribute accepts a single string parameter of name `value`
/// corresponding to the string to use to match against for the decorated
/// variant. In the example above, the the strings `"fourth"`, `"FOUrth"` and so
/// on would parse as `MyValue::Third`.
/// variant. In the example above, the the strings `"fourth"`, `"FOUrth"`,
/// `"fiFTH"` and so on would parse as `MyValue::Third`.
///
/// [`FromFormField`]: rocket::form::FromFormField
/// [`FromFormField::Error`]: rocket::form::FromFormField::Error
@ -648,8 +654,8 @@ pub fn derive_responder(input: TokenStream) -> TokenStream {
/// Derive for the [`UriDisplay<Query>`] trait.
///
/// The [`UriDisplay<Query>`] derive can be applied to enums and structs. When
/// applied to enums, variants must have at least one field. When applied to
/// structs, the struct must have at least one field.
/// applied to an enum, the enum must have at least one variant. When applied to
/// a struct, the struct must have at least one field.
///
/// ```rust
/// # #[macro_use] extern crate rocket;
@ -678,12 +684,15 @@ pub fn derive_responder(input: TokenStream) -> TokenStream {
/// The derive accepts one field attribute: `field`, with the following syntax:
///
/// ```text
/// field := 'name' '=' '"' IDENT '"'
/// field := 'name' '=' '"' FIELD_NAME '"'
/// | 'value' '=' '"' FIELD_VALUE '"'
///
/// IDENT := valid identifier, as defined by Rust
/// FIELD_NAME := valid HTTP field name
/// FIELD_VALUE := valid HTTP field value
/// ```
///
/// When applied, the attribute looks as follows:
/// When applied to a struct, the attribute can only contain `name` and looks
/// as follows:
///
/// ```rust
/// # #[macro_use] extern crate rocket;
@ -694,15 +703,39 @@ pub fn derive_responder(input: TokenStream) -> TokenStream {
/// name: String,
/// id: usize,
/// #[field(name = "type")]
/// #[field(name = "kind")]
/// kind: Kind,
/// }
/// ```
///
/// The field attribute directs that a different field name be used when calling
/// [`Formatter::write_named_value()`] for the given field. The value of the
/// `name` attribute is used instead of the structure's actual field name. In
/// the example above, the field `MyStruct::kind` is rendered with a name of
/// `type`.
/// `name` attribute is used instead of the structure's actual field name. If
/// more than one `field` attribute is applied to a field, the _first_ name is
/// used. In the example above, the field `MyStruct::kind` is rendered with a
/// name of `type`.
///
/// The attribute can slso be applied to variants of C-like enums; it may only
/// contain `value` and looks as follows:
///
/// ```rust
/// # #[macro_use] extern crate rocket;
/// #[derive(UriDisplayQuery)]
/// enum Kind {
/// File,
/// #[field(value = "str")]
/// #[field(value = "string")]
/// String,
/// Other
/// }
/// ```
///
/// The field attribute directs that a different value be used when calling
/// [`Formatter::write_named_value()`] for the given variant. The value of the
/// `value` attribute is used instead of the variant's actual name. If more than
/// one `field` attribute is applied to a variant, the _first_ value is used. In
/// the example above, the variant `Kind::String` will render with a value of
/// `str`.
///
/// [`UriDisplay<Query>`]: ../rocket/http/uri/trait.UriDisplay.html
/// [`Formatter::write_named_value()`]: ../rocket/http/uri/struct.Formatter.html#method.write_named_value

View File

@ -62,12 +62,13 @@ fn from_form_value_renames() {
#[derive(Debug, FromFormField)]
enum Foo {
#[field(value = "foo")]
#[field(value = "bark")]
Bar,
#[field(value = ":book")]
Book
}
assert_parse!("foo", "FOO", "FoO" => Foo::Bar);
assert_parse!("foo", "FOO", "FoO", "bark", "BARK", "BaRk" => Foo::Bar);
assert_parse!(":book", ":BOOK", ":bOOk", ":booK" => Foo::Book);
assert_no_parse!("book", "bar" => Foo);
}

View File

@ -83,17 +83,17 @@ error: field name conflicts with previous name
33 | foo: usize,
| ^^^
|
help: declared in this field
--> $DIR/from_form.rs:33:5
|
33 | foo: usize,
| ^^^^^^^^^^
note: previous field with conflicting name
--> $DIR/from_form.rs:31:5
|
31 | / #[field(name = "foo")]
32 | | field: String,
| |_________________^
help: field name is part of this field
--> $DIR/from_form.rs:33:5
|
33 | foo: usize,
| ^^^^^^^^^^
note: error occurred while deriving `FromForm`
--> $DIR/from_form.rs:29:10
|
@ -107,18 +107,18 @@ error: field name conflicts with previous name
40 | #[field(name = "hello")]
| ^^^^^^^
|
help: declared in this field
--> $DIR/from_form.rs:40:5
|
40 | / #[field(name = "hello")]
41 | | other: String,
| |_________________^
note: previous field with conflicting name
--> $DIR/from_form.rs:38:5
|
38 | / #[field(name = "hello")]
39 | | first: String,
| |_________________^
help: field name is part of this field
--> $DIR/from_form.rs:40:5
|
40 | / #[field(name = "hello")]
41 | | other: String,
| |_________________^
note: error occurred while deriving `FromForm`
--> $DIR/from_form.rs:36:10
|
@ -132,17 +132,17 @@ error: field name conflicts with previous name
47 | #[field(name = "first")]
| ^^^^^^^
|
note: previous field with conflicting name
--> $DIR/from_form.rs:46:5
|
46 | first: String,
| ^^^^^^^^^^^^^
help: field name is part of this field
help: declared in this field
--> $DIR/from_form.rs:47:5
|
47 | / #[field(name = "first")]
48 | | other: String,
| |_________________^
note: previous field with conflicting name
--> $DIR/from_form.rs:46:5
|
46 | first: String,
| ^^^^^^^^^^^^^
note: error occurred while deriving `FromForm`
--> $DIR/from_form.rs:44:10
|
@ -228,7 +228,7 @@ note: this field name...
|
83 | #[field(name = "blah")]
| ^^^^^^
note: ...conflicts with this name
note: ...conflicts with this field name
--> $DIR/from_form.rs:84:20
|
84 | #[field(name = "blah")]

View File

@ -104,100 +104,199 @@ note: error occurred while deriving `FromFormField`
| ^^^^^^^^^^^^^
= 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: variant has conflicting values
--> $DIR/from_form_field.rs:41:5
|
41 | / #[field(name = "foo")]
42 | | #[field(name = uncased("FOO"))]
43 | | single: usize,
| |_________________^
41 | / #[field(value = "bar")]
42 | | #[field(value = "bar")]
43 | | A,
| |_____^
|
note: this field name...
--> $DIR/from_form_field.rs:41:20
note: this value...
--> $DIR/from_form_field.rs:41:21
|
41 | #[field(name = "foo")]
| ^^^^^
note: ...conflicts with this name
--> $DIR/from_form_field.rs:42:28
41 | #[field(value = "bar")]
| ^^^^^
note: ...conflicts with this value
--> $DIR/from_form_field.rs:42:21
|
42 | #[field(name = uncased("FOO"))]
| ^^^^^
note: error occurred while deriving `FromForm`
42 | #[field(value = "bar")]
| ^^^^^
note: error occurred while deriving `FromFormField`
--> $DIR/from_form_field.rs:39:10
|
39 | #[derive(FromForm)]
| ^^^^^^^^
39 | #[derive(FromFormField)]
| ^^^^^^^^^^^^^
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: field name conflicts with previous name
--> $DIR/from_form_field.rs:50:20
error: field value conflicts with previous value
--> $DIR/from_form_field.rs:50:21
|
50 | #[field(name = "foo")]
| ^^^^^
50 | #[field(value = "BAr")]
| ^^^^^
|
help: ...declared in this variant
--> $DIR/from_form_field.rs:50:5
|
50 | / #[field(value = "BAr")]
51 | | B,
| |_____^
note: previous field with conflicting name
--> $DIR/from_form_field.rs:48:5
|
48 | / #[field(name = "foo")]
49 | | single: usize,
| |_________________^
help: field name is part of this field
--> $DIR/from_form_field.rs:50:5
|
50 | / #[field(name = "foo")]
51 | | other: usize,
| |________________^
note: error occurred while deriving `FromForm`
48 | / #[field(value = "bar")]
49 | | A,
| |_____^
note: error occurred while deriving `FromFormField`
--> $DIR/from_form_field.rs:46:10
|
46 | #[derive(FromForm)]
| ^^^^^^^^
46 | #[derive(FromFormField)]
| ^^^^^^^^^^^^^
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: field name conflicts with previous name
--> $DIR/from_form_field.rs:58:5
error: field value conflicts with previous value
--> $DIR/from_form_field.rs:57:21
|
58 | hello_there: usize,
| ^^^^^^^^^^^
57 | #[field(value = "a")]
| ^^^
|
help: ...declared in this variant
--> $DIR/from_form_field.rs:57:5
|
57 | / #[field(value = "a")]
58 | | B,
| |_____^
note: previous field with conflicting name
--> $DIR/from_form_field.rs:56:5
|
56 | / #[field(name = uncased("HELLO_THERE"))]
57 | | single: usize,
| |_________________^
help: field name is part of this field
--> $DIR/from_form_field.rs:58:5
|
58 | hello_there: usize,
| ^^^^^^^^^^^^^^^^^^
note: error occurred while deriving `FromForm`
56 | A,
| ^
note: error occurred while deriving `FromFormField`
--> $DIR/from_form_field.rs:54:10
|
54 | #[derive(FromForm)]
54 | #[derive(FromFormField)]
| ^^^^^^^^^^^^^
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: variant has conflicting values
--> $DIR/from_form_field.rs:80:5
|
80 | / #[field(value = "FoO")]
81 | | #[field(value = "foo")]
82 | | A,
| |_____^
|
note: this value...
--> $DIR/from_form_field.rs:80:21
|
80 | #[field(value = "FoO")]
| ^^^^^
note: ...conflicts with this value
--> $DIR/from_form_field.rs:81:21
|
81 | #[field(value = "foo")]
| ^^^^^
note: error occurred while deriving `FromFormField`
--> $DIR/from_form_field.rs:78:10
|
78 | #[derive(FromFormField)]
| ^^^^^^^^^^^^^
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: field has conflicting names
--> $DIR/from_form_field.rs:87:5
|
87 | / #[field(name = "foo")]
88 | | #[field(name = uncased("FOO"))]
89 | | single: usize,
| |_________________^
|
note: this field name...
--> $DIR/from_form_field.rs:87:20
|
87 | #[field(name = "foo")]
| ^^^^^
note: ...conflicts with this field name
--> $DIR/from_form_field.rs:88:28
|
88 | #[field(name = uncased("FOO"))]
| ^^^^^
note: error occurred while deriving `FromForm`
--> $DIR/from_form_field.rs:85:10
|
85 | #[derive(FromForm)]
| ^^^^^^^^
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: field name conflicts with previous name
--> $DIR/from_form_field.rs:65:5
--> $DIR/from_form_field.rs:96:20
|
65 | hello_there: usize,
| ^^^^^^^^^^^
96 | #[field(name = "foo")]
| ^^^^^
|
help: declared in this field
--> $DIR/from_form_field.rs:96:5
|
96 | / #[field(name = "foo")]
97 | | other: usize,
| |________________^
note: previous field with conflicting name
--> $DIR/from_form_field.rs:63:5
--> $DIR/from_form_field.rs:94:5
|
63 | / #[field(name = "hello_there")]
64 | | single: usize,
94 | / #[field(name = "foo")]
95 | | single: usize,
| |_________________^
help: field name is part of this field
--> $DIR/from_form_field.rs:65:5
|
65 | hello_there: usize,
| ^^^^^^^^^^^^^^^^^^
note: error occurred while deriving `FromForm`
--> $DIR/from_form_field.rs:61:10
--> $DIR/from_form_field.rs:92:10
|
61 | #[derive(FromForm)]
92 | #[derive(FromForm)]
| ^^^^^^^^
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: field name conflicts with previous name
--> $DIR/from_form_field.rs:104:5
|
104 | hello_there: usize,
| ^^^^^^^^^^^
|
help: declared in this field
--> $DIR/from_form_field.rs:104:5
|
104 | hello_there: usize,
| ^^^^^^^^^^^^^^^^^^
note: previous field with conflicting name
--> $DIR/from_form_field.rs:102:5
|
102 | / #[field(name = uncased("HELLO_THERE"))]
103 | | single: usize,
| |_________________^
note: error occurred while deriving `FromForm`
--> $DIR/from_form_field.rs:100:10
|
100 | #[derive(FromForm)]
| ^^^^^^^^
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: field name conflicts with previous name
--> $DIR/from_form_field.rs:111:5
|
111 | hello_there: usize,
| ^^^^^^^^^^^
|
help: declared in this field
--> $DIR/from_form_field.rs:111:5
|
111 | hello_there: usize,
| ^^^^^^^^^^^^^^^^^^
note: previous field with conflicting name
--> $DIR/from_form_field.rs:109:5
|
109 | / #[field(name = "hello_there")]
110 | | single: usize,
| |_________________^
note: error occurred while deriving `FromForm`
--> $DIR/from_form_field.rs:107:10
|
107 | #[derive(FromForm)]
| ^^^^^^^^
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)

View File

@ -1,4 +1,4 @@
error: fieldless structs or variants are not supported
error: fieldless structs are not supported
--> $DIR/uri_display.rs:4:1
|
4 | struct Foo1;
@ -11,7 +11,7 @@ note: error occurred while deriving `UriDisplay`
| ^^^^^^^^^^^^^^^
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: fieldless structs or variants are not supported
error: fieldless structs are not supported
--> $DIR/uri_display.rs:7:1
|
7 | struct Foo2();
@ -37,19 +37,6 @@ note: error occurred while deriving `UriDisplay`
| ^^^^^^^^^^^^^^^
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: fieldless structs or variants are not supported
--> $DIR/uri_display.rs:14:5
|
14 | Variant,
| ^^^^^^^
|
note: error occurred while deriving `UriDisplay`
--> $DIR/uri_display.rs:12:10
|
12 | #[derive(UriDisplayQuery)]
| ^^^^^^^^^^^^^^^
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: tuple structs or variants must have exactly one field
--> $DIR/uri_display.rs:18:12
|

View File

@ -89,18 +89,18 @@ error: field name conflicts with previous name
33 | foo: usize,
| ^^^
error: [help] declared in this field
--> $DIR/from_form.rs:33:5
|
33 | foo: usize,
| ^^^
error: [note] previous field with conflicting name
--> $DIR/from_form.rs:31:5
|
31 | #[field(name = "foo")]
| ^
error: [help] field name is part of this field
--> $DIR/from_form.rs:33:5
|
33 | foo: usize,
| ^^^
error: [note] error occurred while deriving `FromForm`
--> $DIR/from_form.rs:29:10
|
@ -115,18 +115,18 @@ error: field name conflicts with previous name
40 | #[field(name = "hello")]
| ^^^^^^^
error: [help] declared in this field
--> $DIR/from_form.rs:40:5
|
40 | #[field(name = "hello")]
| ^
error: [note] previous field with conflicting name
--> $DIR/from_form.rs:38:5
|
38 | #[field(name = "hello")]
| ^
error: [help] field name is part of this field
--> $DIR/from_form.rs:40:5
|
40 | #[field(name = "hello")]
| ^
error: [note] error occurred while deriving `FromForm`
--> $DIR/from_form.rs:36:10
|
@ -141,18 +141,18 @@ error: field name conflicts with previous name
47 | #[field(name = "first")]
| ^^^^^^^
error: [help] declared in this field
--> $DIR/from_form.rs:47:5
|
47 | #[field(name = "first")]
| ^
error: [note] previous field with conflicting name
--> $DIR/from_form.rs:46:5
|
46 | first: String,
| ^^^^^
error: [help] field name is part of this field
--> $DIR/from_form.rs:47:5
|
47 | #[field(name = "first")]
| ^
error: [note] error occurred while deriving `FromForm`
--> $DIR/from_form.rs:44:10
|
@ -243,7 +243,7 @@ error: [note] this field name...
83 | #[field(name = "blah")]
| ^^^^^^
error: [note] ...conflicts with this name
error: [note] ...conflicts with this field name
--> $DIR/from_form.rs:84:20
|
84 | #[field(name = "blah")]

View File

@ -110,106 +110,210 @@ error: [note] error occurred while deriving `FromFormField`
|
= 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: variant has conflicting values
--> $DIR/from_form_field.rs:41:5
|
41 | #[field(name = "foo")]
41 | #[field(value = "bar")]
| ^
error: [note] this field name...
--> $DIR/from_form_field.rs:41:20
error: [note] this value...
--> $DIR/from_form_field.rs:41:21
|
41 | #[field(name = "foo")]
| ^^^^^
41 | #[field(value = "bar")]
| ^^^^^
error: [note] ...conflicts with this name
--> $DIR/from_form_field.rs:42:28
error: [note] ...conflicts with this value
--> $DIR/from_form_field.rs:42:21
|
42 | #[field(name = uncased("FOO"))]
| ^^^^^
42 | #[field(value = "bar")]
| ^^^^^
error: [note] error occurred while deriving `FromForm`
error: [note] error occurred while deriving `FromFormField`
--> $DIR/from_form_field.rs:39:10
|
39 | #[derive(FromForm)]
| ^^^^^^^^
39 | #[derive(FromFormField)]
| ^^^^^^^^^^^^^
|
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: field name conflicts with previous name
--> $DIR/from_form_field.rs:50:20
error: field value conflicts with previous value
--> $DIR/from_form_field.rs:50:21
|
50 | #[field(name = "foo")]
| ^^^^^
50 | #[field(value = "BAr")]
| ^^^^^
error: [help] ...declared in this variant
--> $DIR/from_form_field.rs:50:5
|
50 | #[field(value = "BAr")]
| ^
error: [note] previous field with conflicting name
--> $DIR/from_form_field.rs:48:5
|
48 | #[field(name = "foo")]
48 | #[field(value = "bar")]
| ^
error: [help] field name is part of this field
--> $DIR/from_form_field.rs:50:5
|
50 | #[field(name = "foo")]
| ^
error: [note] error occurred while deriving `FromForm`
error: [note] error occurred while deriving `FromFormField`
--> $DIR/from_form_field.rs:46:10
|
46 | #[derive(FromForm)]
| ^^^^^^^^
46 | #[derive(FromFormField)]
| ^^^^^^^^^^^^^
|
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: field name conflicts with previous name
--> $DIR/from_form_field.rs:58:5
error: field value conflicts with previous value
--> $DIR/from_form_field.rs:57:21
|
58 | hello_there: usize,
| ^^^^^^^^^^^
57 | #[field(value = "a")]
| ^^^
error: [help] ...declared in this variant
--> $DIR/from_form_field.rs:57:5
|
57 | #[field(value = "a")]
| ^
error: [note] previous field with conflicting name
--> $DIR/from_form_field.rs:56:5
|
56 | #[field(name = uncased("HELLO_THERE"))]
56 | A,
| ^
error: [help] field name is part of this field
--> $DIR/from_form_field.rs:58:5
|
58 | hello_there: usize,
| ^^^^^^^^^^^
error: [note] error occurred while deriving `FromForm`
error: [note] error occurred while deriving `FromFormField`
--> $DIR/from_form_field.rs:54:10
|
54 | #[derive(FromForm)]
54 | #[derive(FromFormField)]
| ^^^^^^^^^^^^^
|
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: variant has conflicting values
--> $DIR/from_form_field.rs:80:5
|
80 | #[field(value = "FoO")]
| ^
error: [note] this value...
--> $DIR/from_form_field.rs:80:21
|
80 | #[field(value = "FoO")]
| ^^^^^
error: [note] ...conflicts with this value
--> $DIR/from_form_field.rs:81:21
|
81 | #[field(value = "foo")]
| ^^^^^
error: [note] error occurred while deriving `FromFormField`
--> $DIR/from_form_field.rs:78:10
|
78 | #[derive(FromFormField)]
| ^^^^^^^^^^^^^
|
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: field has conflicting names
--> $DIR/from_form_field.rs:87:5
|
87 | #[field(name = "foo")]
| ^
error: [note] this field name...
--> $DIR/from_form_field.rs:87:20
|
87 | #[field(name = "foo")]
| ^^^^^
error: [note] ...conflicts with this field name
--> $DIR/from_form_field.rs:88:28
|
88 | #[field(name = uncased("FOO"))]
| ^^^^^
error: [note] error occurred while deriving `FromForm`
--> $DIR/from_form_field.rs:85:10
|
85 | #[derive(FromForm)]
| ^^^^^^^^
|
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: field name conflicts with previous name
--> $DIR/from_form_field.rs:65:5
--> $DIR/from_form_field.rs:96:20
|
65 | hello_there: usize,
| ^^^^^^^^^^^
96 | #[field(name = "foo")]
| ^^^^^
error: [note] previous field with conflicting name
--> $DIR/from_form_field.rs:63:5
error: [help] declared in this field
--> $DIR/from_form_field.rs:96:5
|
63 | #[field(name = "hello_there")]
96 | #[field(name = "foo")]
| ^
error: [help] field name is part of this field
--> $DIR/from_form_field.rs:65:5
error: [note] previous field with conflicting name
--> $DIR/from_form_field.rs:94:5
|
65 | hello_there: usize,
| ^^^^^^^^^^^
94 | #[field(name = "foo")]
| ^
error: [note] error occurred while deriving `FromForm`
--> $DIR/from_form_field.rs:61:10
--> $DIR/from_form_field.rs:92:10
|
61 | #[derive(FromForm)]
92 | #[derive(FromForm)]
| ^^^^^^^^
|
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: field name conflicts with previous name
--> $DIR/from_form_field.rs:104:5
|
104 | hello_there: usize,
| ^^^^^^^^^^^
error: [help] declared in this field
--> $DIR/from_form_field.rs:104:5
|
104 | hello_there: usize,
| ^^^^^^^^^^^
error: [note] previous field with conflicting name
--> $DIR/from_form_field.rs:102:5
|
102 | #[field(name = uncased("HELLO_THERE"))]
| ^
error: [note] error occurred while deriving `FromForm`
--> $DIR/from_form_field.rs:100:10
|
100 | #[derive(FromForm)]
| ^^^^^^^^
|
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: field name conflicts with previous name
--> $DIR/from_form_field.rs:111:5
|
111 | hello_there: usize,
| ^^^^^^^^^^^
error: [help] declared in this field
--> $DIR/from_form_field.rs:111:5
|
111 | hello_there: usize,
| ^^^^^^^^^^^
error: [note] previous field with conflicting name
--> $DIR/from_form_field.rs:109:5
|
109 | #[field(name = "hello_there")]
| ^
error: [note] error occurred while deriving `FromForm`
--> $DIR/from_form_field.rs:107:10
|
107 | #[derive(FromForm)]
| ^^^^^^^^
|
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)

View File

@ -1,4 +1,4 @@
error: fieldless structs or variants are not supported
error: fieldless structs are not supported
--> $DIR/uri_display.rs:4:1
|
4 | struct Foo1;
@ -12,7 +12,7 @@ error: [note] error occurred while deriving `UriDisplay`
|
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: fieldless structs or variants are not supported
error: fieldless structs are not supported
--> $DIR/uri_display.rs:7:1
|
7 | struct Foo2();
@ -40,20 +40,6 @@ error: [note] error occurred while deriving `UriDisplay`
|
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: fieldless structs or variants are not supported
--> $DIR/uri_display.rs:14:5
|
14 | Variant,
| ^^^^^^^
error: [note] error occurred while deriving `UriDisplay`
--> $DIR/uri_display.rs:12:10
|
12 | #[derive(UriDisplayQuery)]
| ^^^^^^^^^^^^^^^
|
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: tuple structs or variants must have exactly one field
--> $DIR/uri_display.rs:18:12
|

View File

@ -36,6 +36,52 @@ enum Bar2 {
A,
}
#[derive(FromFormField)]
enum Dup1 {
#[field(value = "bar")]
#[field(value = "bar")]
A,
}
#[derive(FromFormField)]
enum Dup2 {
#[field(value = "bar")]
A,
#[field(value = "BAr")]
B,
}
#[derive(FromFormField)]
enum Dup3 {
A,
#[field(value = "a")]
B,
}
#[derive(FromFormField)]
enum Dup4 {
A,
#[field(value = "c")] // this shouldn't error
B,
#[field(value = "b")] // this shouldn't error
C,
}
#[derive(FromFormField)]
enum Dup5 {
#[field(value = "a")] // this shouldn't error
A,
B,
C,
}
#[derive(FromFormField)]
enum Dup6 {
#[field(value = "FoO")]
#[field(value = "foo")]
A,
}
#[derive(FromForm)]
struct Renamed0 {
#[field(name = "foo")]

View File

@ -112,6 +112,55 @@ fn uri_display_bam() {
assert_uri_display_query!(bam, "foo=hi%20hi&baz=tony");
}
#[test]
fn uri_display_c_like() {
#[derive(UriDisplayQuery)]
enum CLike { A, B, C }
assert_uri_display_query!(CLike::A, "A");
assert_uri_display_query!(CLike::B, "B");
assert_uri_display_query!(CLike::C, "C");
#[derive(UriDisplayQuery)]
enum CLikeV {
#[field(value = "a")]
A,
#[field(value = "tomato")]
#[field(value = "juice")]
B,
#[field(value = "carrot")]
C
}
assert_uri_display_query!(CLikeV::A, "a");
assert_uri_display_query!(CLikeV::B, "tomato");
assert_uri_display_query!(CLikeV::C, "carrot");
#[derive(UriDisplayQuery)]
#[allow(non_camel_case_types)]
enum CLikeR { r#for, r#type, r#async, #[field(value = "stop")] r#yield }
assert_uri_display_query!(CLikeR::r#for, "for");
assert_uri_display_query!(CLikeR::r#type, "type");
assert_uri_display_query!(CLikeR::r#async, "async");
assert_uri_display_query!(CLikeR::r#yield, "stop");
#[derive(UriDisplayQuery)]
struct Nested {
foo: CLike,
bar: CLikeV,
last: CLikeR
}
let nested = Nested {
foo: CLike::B,
bar: CLikeV::B,
last: CLikeR::r#type,
};
assert_uri_display_query!(nested, "foo=B&bar=tomato&last=type");
}
macro_rules! assert_uri_display_path {
($v:expr, $s:expr) => (
let uri_string = format!("{}", &$v as &dyn UriDisplay<Path>);