Allow multiple and uncased field renamings.

This commit is contained in:
Sergio Benitez 2021-03-05 18:09:12 -08:00
parent f0a6b9a25a
commit 7784cc982a
16 changed files with 563 additions and 107 deletions

View File

@ -78,7 +78,7 @@ impl Parameter {
impl Dynamic {
// This isn't public since this `Dynamic` should always be an `Ignored`.
pub fn is_wild(&self) -> bool {
self.name == "_"
&self.name == "_"
}
}

View File

@ -200,7 +200,7 @@ impl Route {
.chain(query_params.iter().filter_map(|p| p.guard()))
.chain(data_guard.as_ref().into_iter());
all_other_guards.all(|g| g.name != name)
all_other_guards.all(|g| &g.name != *name)
})
.enumerate()
.map(|(index, (name, (ident, ty)))| Guard {

View File

@ -3,18 +3,19 @@ use devise::{*, ext::{TypeExt, SpanDiagnosticExt}};
use syn::visit_mut::VisitMut;
use syn::visit::Visit;
use crate::proc_macro2::{Span, TokenStream, TokenTree};
use crate::proc_macro2::{TokenStream, TokenTree};
use crate::syn_ext::IdentExt;
use crate::name::Name;
pub struct FormField {
pub span: Span,
pub name: Name,
#[derive(Debug)]
pub enum FieldName {
Cased(Name),
Uncased(Name),
}
#[derive(FromMeta)]
pub struct FieldAttr {
pub name: Option<FormField>,
pub name: Option<FieldName>,
pub validate: Option<SpanWrapped<syn::Expr>>,
}
@ -24,12 +25,13 @@ impl FieldAttr {
pub(crate) trait FieldExt {
fn ident(&self) -> &syn::Ident;
fn field_name(&self) -> Result<String>;
fn field_names(&self) -> Result<Vec<FieldName>>;
fn one_field_name(&self) -> Result<FieldName>;
fn stripped_ty(&self) -> syn::Type;
fn name_view(&self) -> Result<syn::Expr>;
}
impl FromMeta for FormField {
impl FromMeta for FieldName {
fn from_meta(meta: &MetaItem) -> Result<Self> {
// These are used during parsing.
const CONTROL_CHARS: &[char] = &['&', '=', '?', '.', '[', ']'];
@ -44,8 +46,23 @@ impl FromMeta for FormField {
s.chars().all(|c| c.is_ascii_graphic() && !CONTROL_CHARS.contains(&c))
}
let name = Name::from_meta(meta)?;
if !is_valid_field_name(name.as_str()) {
let field_name = match Name::from_meta(meta) {
Ok(name) => FieldName::Cased(name),
Err(_) => {
#[derive(FromMeta)]
struct Inner {
#[meta(naked)]
uncased: Name
}
let expr = meta.expr()?;
let item: MetaItem = syn::parse2(quote!(#expr))?;
let inner = Inner::from_meta(&item)?;
FieldName::Uncased(inner.uncased)
}
};
if !is_valid_field_name(field_name.as_str()) {
let chars = CONTROL_CHARS.iter()
.map(|c| format!("{:?}", c))
.collect::<Vec<_>>()
@ -56,7 +73,35 @@ impl FromMeta for FormField {
.help(format!("field name cannot be `isindex` or contain {}", chars)));
}
Ok(FormField { span: meta.value_span(), name })
Ok(field_name)
}
}
impl std::ops::Deref for FieldName {
type Target = Name;
fn deref(&self) -> &Self::Target {
match self {
FieldName::Cased(n) | FieldName::Uncased(n) => n,
}
}
}
impl quote::ToTokens for FieldName {
fn to_tokens(&self, tokens: &mut TokenStream) {
(self as &Name).to_tokens(tokens)
}
}
impl PartialEq for FieldName {
fn eq(&self, other: &Self) -> bool {
use FieldName::*;
match (self, other) {
(Cased(a), Cased(b)) => a == b,
(Cased(a), Uncased(u)) | (Uncased(u), Cased(a)) => a == u.as_uncased_str(),
(Uncased(u1), Uncased(u2)) => u1.as_uncased_str() == u2.as_uncased_str(),
}
}
}
@ -65,22 +110,31 @@ impl FieldExt for Field<'_> {
self.ident.as_ref().expect("named")
}
fn field_name(&self) -> Result<String> {
let mut fields = FieldAttr::from_attrs(FieldAttr::NAME, &self.attrs)?
fn field_names(&self) -> Result<Vec<FieldName>> {
let attr_names = FieldAttr::from_attrs(FieldAttr::NAME, &self.attrs)?
.into_iter()
.filter_map(|attr| attr.name);
.filter_map(|attr| attr.name)
.collect::<Vec<_>>();
let name = fields.next()
.map(|f| f.name)
.unwrap_or_else(|| Name::from(self.ident().clone()));
if let Some(field) = fields.next() {
return Err(field.span
.error("duplicate form field renaming")
.help("a field can only be renamed once"));
if attr_names.is_empty() {
let ident_name = Name::from(self.ident());
return Ok(vec![FieldName::Cased(ident_name)]);
}
Ok(name.to_string())
Ok(attr_names)
}
fn one_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)
}
fn stripped_ty(&self) -> syn::Type {
@ -88,7 +142,8 @@ impl FieldExt for Field<'_> {
}
fn name_view(&self) -> Result<syn::Expr> {
let field_name = self.field_name()?;
let field_names = self.field_names()?;
let field_name = field_names.first().expect("always have name");
define_spanned_export!(self.span() => _form);
let name_view = quote_spanned! { self.span() =>
#_form::NameBuf::from((__c.__parent, #field_name))

View File

@ -2,25 +2,30 @@ use devise::{*, ext::{TypeExt, SpanDiagnosticExt, GenericsExt, Split2, quote_res
use crate::exports::*;
use crate::proc_macro2::TokenStream;
use crate::derive::form_field::*;
use crate::derive::form_field::{*, FieldName::*};
// F: fn(field_ty: Ty, field_context: Expr)
fn fields_map<F>(fields: Fields<'_>, map_f: F) -> Result<TokenStream>
where F: Fn(&syn::Type, &syn::Expr) -> TokenStream
{
let matchers = fields.iter()
.map(|f| {
let (ident, field_name, ty) = (f.ident(), f.field_name()?, f.stripped_ty());
let mut matchers = vec![];
for field in fields.iter() {
let (ident, ty) = (field.ident(), field.stripped_ty());
let field_context = quote_spanned!(ty.span() => {
let _o = __c.__opts;
__c.#ident.get_or_insert_with(|| <#ty as #_form::FromForm<'__f>>::init(_o))
});
let field_names = field.field_names()?;
let field_context = syn::parse2(field_context).expect("valid expr");
let expr = map_f(&ty, &field_context);
Ok(quote!(#field_name => { #expr }))
})
.collect::<Result<Vec<TokenStream>>>()?;
let push = map_f(&ty, &field_context);
let field_matchers = field_names.iter().map(|f| match f {
Cased(name) => quote!(#name => { #push }),
Uncased(name) => quote!(__n if __n.as_uncased() == #name => { #push }),
});
matchers.extend(field_matchers);
}
Ok(quote! {
__c.__parent = __f.name.parent();
@ -65,15 +70,33 @@ pub fn derive_from_form(input: proc_macro::TokenStream) -> TokenStream {
return Err(fields.span().error("at least one field is required"));
}
let mut names = ::std::collections::HashMap::new();
let mut names = vec![];
let mut field_map = vec![];
for field in fields.iter() {
let name = field.field_name()?;
if let Some(span) = names.get(&name) {
return Err(field.span().error("duplicate form field")
.span_note(*span, "previously defined here"));
names.append(&mut field.field_names()?);
field_map.push((names.len(), field));
}
names.insert(name, field.span());
// 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"));
}
}
}
Ok(())
@ -105,6 +128,10 @@ pub fn derive_from_form(input: proc_macro::TokenStream) -> TokenStream {
quote_spanned!(ty.span() => #ident: #field_ty,)
})
)
.outer_mapper(quote! {
#[allow(unused_imports)]
use #_http::uncased::AsUncased;
})
.outer_mapper(quote!(#[rocket::async_trait]))
.inner_mapper(MapperBuild::new()
.try_input_map(|mapper, input| {

View File

@ -34,7 +34,7 @@ pub fn derive_from_form_field(input: proc_macro::TokenStream) -> TokenStream {
.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(|| v.ident.clone().into())
o.map(|f| f.value).unwrap_or_else(|| Name::from(&v.ident))
}))
.collect::<Result<Vec<Name>>>()?;

View File

@ -56,7 +56,7 @@ pub fn derive_uri_display_query(input: proc_macro::TokenStream) -> TokenStream {
let span = field.span().into();
let accessor = field.accessor();
let tokens = if field.ident.is_some() {
let name = field.field_name()?;
let name = field.one_field_name()?;
quote_spanned!(span => f.write_named_value(#name, &#accessor)?;)
} else {
quote_spanned!(span => f.write_value(&#accessor)?;)

View File

@ -1,17 +1,18 @@
use crate::syn::{self, Ident, ext::IdentExt};
use crate::http::uncased::UncasedStr;
use crate::proc_macro2::Span;
/// A "name" read by codegen, which may or may not be a valid identifier. A
/// `Name` is typically constructed indirectly via FromMeta, or From<Ident> or
/// directly from a string via `Name::new()`.
/// A "name" read by codegen, which may or may not be an identifier. A `Name` is
/// typically constructed indirectly via FromMeta, or From<Ident> or directly
/// from a string via `Name::new()`.
///
/// Some "names" in Rocket include:
/// * Dynamic parameter: `name` in `<name>`
/// * Renamed fields: `foo` in #[field(name = "foo")].
///
/// `Name` implements Hash, PartialEq, and Eq, and additionally PartialEq<S> for
/// all types `S: AsStr<str>`. These implementations all compare the value of
/// `name()` only.
/// all types `S: PartialEq<str>`. These implementations all compare the value
/// of `as_str()` only.
#[derive(Debug, Clone)]
pub struct Name {
value: String,
@ -31,6 +32,11 @@ impl Name {
&self.value
}
/// Like `as_str()` but into an `&UncasedStr`.
pub fn as_uncased_str(&self) -> &UncasedStr {
UncasedStr::new(self.as_str())
}
pub fn span(&self) -> Span {
self.span
}
@ -50,7 +56,7 @@ impl devise::FromMeta for Name {
impl quote::ToTokens for Name {
fn to_tokens(&self, tokens: &mut devise::proc_macro2::TokenStream) {
self.as_str().to_tokens(tokens)
syn::LitStr::new(self.as_str(), self.span()).to_tokens(tokens)
}
}
@ -60,12 +66,6 @@ impl From<&Ident> for Name {
}
}
impl From<Ident> for Name {
fn from(ident: Ident) -> Self {
Name::new(ident.unraw().to_string(), ident.span())
}
}
impl AsRef<str> for Name {
fn as_ref(&self) -> &str {
self.as_str()
@ -88,9 +88,9 @@ impl std::ops::Deref for Name {
impl Eq for Name { }
impl<S: AsRef<str> + ?Sized> PartialEq<S> for Name {
impl<S: PartialEq<str> + ?Sized> PartialEq<S> for Name {
fn eq(&self, other: &S) -> bool {
self.as_str() == other.as_ref()
other == self.as_str()
}
}

View File

@ -227,6 +227,60 @@ fn field_renaming() {
let form: Option<RenamedForm> = strict(&form_string).ok();
assert!(form.is_none());
#[derive(Debug, PartialEq, FromForm)]
struct MultiName<'r> {
single: usize,
#[field(name = "SomeCase")]
#[field(name = "some_case")]
some_case: &'r str,
}
let form_string = &["single=123", "some_case=hi_im_here"].join("&");
let form: Option<MultiName> = strict(&form_string).ok();
assert_eq!(form, Some(MultiName { single: 123, some_case: "hi_im_here", }));
let form_string = &["single=123", "SomeCase=HiImHere"].join("&");
let form: Option<MultiName> = strict(&form_string).ok();
assert_eq!(form, Some(MultiName { single: 123, some_case: "HiImHere", }));
let form_string = &["single=123", "some_case=hi_im_here", "SomeCase=HiImHere"].join("&");
let form: Option<MultiName> = strict(&form_string).ok();
assert!(form.is_none());
let form_string = &["single=123", "some_case=hi_im_here", "SomeCase=HiImHere"].join("&");
let form: Option<MultiName> = lenient(&form_string).ok();
assert_eq!(form, Some(MultiName { single: 123, some_case: "hi_im_here", }));
let form_string = &["single=123", "SomeCase=HiImHere", "some_case=hi_im_here"].join("&");
let form: Option<MultiName> = lenient(&form_string).ok();
assert_eq!(form, Some(MultiName { single: 123, some_case: "HiImHere", }));
#[derive(Debug, PartialEq, FromForm)]
struct CaseInsensitive<'r> {
#[field(name = uncased("SomeCase"))]
#[field(name = "some_case")]
some_case: &'r str,
#[field(name = uncased("hello"))]
hello: usize,
}
let form_string = &["HeLLO=123", "sOMECASe=hi_im_here"].join("&");
let form: Option<CaseInsensitive> = strict(&form_string).ok();
assert_eq!(form, Some(CaseInsensitive { hello: 123, some_case: "hi_im_here", }));
let form_string = &["hello=456", "SomeCase=HiImHere"].join("&");
let form: Option<CaseInsensitive> = strict(&form_string).ok();
assert_eq!(form, Some(CaseInsensitive { hello: 456, some_case: "HiImHere", }));
let form_string = &["helLO=789", "some_case=hi_there"].join("&");
let form: Option<CaseInsensitive> = strict(&form_string).ok();
assert_eq!(form, Some(CaseInsensitive { hello: 789, some_case: "hi_there", }));
let form_string = &["hello=123", "SOme_case=hi_im_here"].join("&");
let form: Option<CaseInsensitive> = strict(&form_string).ok();
assert!(form.is_none());
}
#[test]

View File

@ -77,18 +77,23 @@ note: error occurred while deriving `FromForm`
| ^^^^^^^^
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: duplicate form field
error: field name conflicts with previous name
--> $DIR/from_form.rs:33:5
|
33 | foo: usize,
| ^^^^^^^^^^
| ^^^
|
note: previously defined here
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
|
@ -96,19 +101,24 @@ note: error occurred while deriving `FromForm`
| ^^^^^^^^
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: duplicate form field
--> $DIR/from_form.rs:40:5
error: field name conflicts with previous name
--> $DIR/from_form.rs:40:20
|
40 | / #[field(name = "hello")]
41 | | other: String,
| |_________________^
40 | #[field(name = "hello")]
| ^^^^^^^
|
note: previously defined here
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
|
@ -116,18 +126,23 @@ note: error occurred while deriving `FromForm`
| ^^^^^^^^
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: duplicate form field
error: field name conflicts with previous name
--> $DIR/from_form.rs:47:20
|
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
--> $DIR/from_form.rs:47:5
|
47 | / #[field(name = "first")]
48 | | other: String,
| |_________________^
|
note: previously defined here
--> $DIR/from_form.rs:46:5
|
46 | first: String,
| ^^^^^^^^^^^^^
note: error occurred while deriving `FromForm`
--> $DIR/from_form.rs:44:10
|
@ -200,13 +215,24 @@ note: error occurred while deriving `FromForm`
| ^^^^^^^^
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: duplicate form field renaming
error: field has conflicting names
--> $DIR/from_form.rs:83:5
|
83 | / #[field(name = "blah")]
84 | | #[field(name = "blah")]
85 | | my_field: String,
| |____________________^
|
note: this field name...
--> $DIR/from_form.rs:83:20
|
83 | #[field(name = "blah")]
| ^^^^^^
note: ...conflicts with this name
--> $DIR/from_form.rs:84:20
|
84 | #[field(name = "blah")]
| ^^^^^^
|
= help: a field can only be renamed once
note: error occurred while deriving `FromForm`
--> $DIR/from_form.rs:81:10
|
@ -214,7 +240,7 @@ note: error occurred while deriving `FromForm`
| ^^^^^^^^
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: invalid value: expected string literal
error: expected list `#[attr(..)]`, found bare boolean literal
--> $DIR/from_form.rs:90:20
|
90 | #[field(name = true)]
@ -227,7 +253,7 @@ note: error occurred while deriving `FromForm`
| ^^^^^^^^
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: expected literal, found bare path "name"
error: expected expression, found bare path "name"
--> $DIR/from_form.rs:96:13
|
96 | #[field(name)]
@ -240,7 +266,7 @@ note: error occurred while deriving `FromForm`
| ^^^^^^^^
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: invalid value: expected string literal
error: expected list `#[attr(..)]`, found bare integer literal
--> $DIR/from_form.rs:102:20
|
102 | #[field(name = 123)]

View File

@ -103,3 +103,101 @@ note: error occurred while deriving `FromFormField`
33 | #[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:41:5
|
41 | / #[field(name = "foo")]
42 | | #[field(name = uncased("FOO"))]
43 | | single: usize,
| |_________________^
|
note: this field name...
--> $DIR/from_form_field.rs:41:20
|
41 | #[field(name = "foo")]
| ^^^^^
note: ...conflicts with this name
--> $DIR/from_form_field.rs:42:28
|
42 | #[field(name = uncased("FOO"))]
| ^^^^^
note: error occurred while deriving `FromForm`
--> $DIR/from_form_field.rs:39:10
|
39 | #[derive(FromForm)]
| ^^^^^^^^
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: field name conflicts with previous name
--> $DIR/from_form_field.rs:50:20
|
50 | #[field(name = "foo")]
| ^^^^^
|
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`
--> $DIR/from_form_field.rs:46:10
|
46 | #[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:58:5
|
58 | hello_there: usize,
| ^^^^^^^^^^^
|
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`
--> $DIR/from_form_field.rs:54:10
|
54 | #[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
|
65 | hello_there: usize,
| ^^^^^^^^^^^
|
note: previous field with conflicting name
--> $DIR/from_form_field.rs:63:5
|
63 | / #[field(name = "hello_there")]
64 | | 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
|
61 | #[derive(FromForm)]
| ^^^^^^^^
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)

View File

@ -63,7 +63,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: invalid value: expected string literal
error: expected list `#[attr(..)]`, found bare integer literal
--> $DIR/uri_display.rs:22:20
|
22 | #[field(name = 123)]

View File

@ -83,18 +83,24 @@ error: [note] error occurred while deriving `FromForm`
|
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: duplicate form field
error: field name conflicts with previous name
--> $DIR/from_form.rs:33:5
|
33 | foo: usize,
| ^^^
error: [note] previously defined here
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
|
@ -103,18 +109,24 @@ error: [note] error occurred while deriving `FromForm`
|
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: duplicate form field
--> $DIR/from_form.rs:40:5
error: field name conflicts with previous name
--> $DIR/from_form.rs:40:20
|
40 | #[field(name = "hello")]
| ^
| ^^^^^^^
error: [note] previously defined here
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
|
@ -123,18 +135,24 @@ error: [note] error occurred while deriving `FromForm`
|
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: duplicate form field
--> $DIR/from_form.rs:47:5
error: field name conflicts with previous name
--> $DIR/from_form.rs:47:20
|
47 | #[field(name = "first")]
| ^
| ^^^^^^^
error: [note] previously defined here
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
|
@ -213,8 +231,19 @@ error: [note] error occurred while deriving `FromForm`
|
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: duplicate form field renaming
--- help: a field can only be renamed once
error: field has conflicting names
--> $DIR/from_form.rs:83:5
|
83 | #[field(name = "blah")]
| ^
error: [note] this field name...
--> $DIR/from_form.rs:83:20
|
83 | #[field(name = "blah")]
| ^^^^^^
error: [note] ...conflicts with this name
--> $DIR/from_form.rs:84:20
|
84 | #[field(name = "blah")]
@ -228,7 +257,7 @@ error: [note] error occurred while deriving `FromForm`
|
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: invalid value: expected string literal
error: expected list `#[attr(..)]`, found bare boolean literal
--> $DIR/from_form.rs:90:20
|
90 | #[field(name = true)]
@ -242,7 +271,7 @@ error: [note] error occurred while deriving `FromForm`
|
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: expected literal, found bare path "name"
error: expected expression, found bare path "name"
--> $DIR/from_form.rs:96:13
|
96 | #[field(name)]
@ -256,7 +285,7 @@ error: [note] error occurred while deriving `FromForm`
|
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: invalid value: expected string literal
error: expected list `#[attr(..)]`, found bare integer literal
--> $DIR/from_form.rs:102:20
|
102 | #[field(name = 123)]

View File

@ -109,3 +109,107 @@ 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
--> $DIR/from_form_field.rs:41:5
|
41 | #[field(name = "foo")]
| ^
error: [note] this field name...
--> $DIR/from_form_field.rs:41:20
|
41 | #[field(name = "foo")]
| ^^^^^
error: [note] ...conflicts with this name
--> $DIR/from_form_field.rs:42:28
|
42 | #[field(name = uncased("FOO"))]
| ^^^^^
error: [note] error occurred while deriving `FromForm`
--> $DIR/from_form_field.rs:39:10
|
39 | #[derive(FromForm)]
| ^^^^^^^^
|
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: field name conflicts with previous name
--> $DIR/from_form_field.rs:50:20
|
50 | #[field(name = "foo")]
| ^^^^^
error: [note] previous field with conflicting name
--> $DIR/from_form_field.rs:48:5
|
48 | #[field(name = "foo")]
| ^
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`
--> $DIR/from_form_field.rs:46:10
|
46 | #[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:58:5
|
58 | hello_there: usize,
| ^^^^^^^^^^^
error: [note] previous field with conflicting name
--> $DIR/from_form_field.rs:56:5
|
56 | #[field(name = uncased("HELLO_THERE"))]
| ^
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`
--> $DIR/from_form_field.rs:54:10
|
54 | #[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
|
65 | hello_there: usize,
| ^^^^^^^^^^^
error: [note] previous field with conflicting name
--> $DIR/from_form_field.rs:63:5
|
63 | #[field(name = "hello_there")]
| ^
error: [help] field name is part of this field
--> $DIR/from_form_field.rs:65:5
|
65 | hello_there: usize,
| ^^^^^^^^^^^
error: [note] error occurred while deriving `FromForm`
--> $DIR/from_form_field.rs:61:10
|
61 | #[derive(FromForm)]
| ^^^^^^^^
|
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)

View File

@ -68,7 +68,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: invalid value: expected string literal
error: expected list `#[attr(..)]`, found bare integer literal
--> $DIR/uri_display.rs:22:20
|
22 | #[field(name = 123)]

View File

@ -36,4 +36,33 @@ enum Bar2 {
A,
}
#[derive(FromForm)]
struct Renamed0 {
#[field(name = "foo")]
#[field(name = uncased("FOO"))]
single: usize,
}
#[derive(FromForm)]
struct Renamed1 {
#[field(name = "foo")]
single: usize,
#[field(name = "foo")]
other: usize,
}
#[derive(FromForm)]
struct Renamed2 {
#[field(name = uncased("HELLO_THERE"))]
single: usize,
hello_there: usize,
}
#[derive(FromForm)]
struct Renamed3 {
#[field(name = "hello_there")]
single: usize,
hello_there: usize,
}
fn main() { }

View File

@ -764,16 +764,17 @@ By default, Rocket matches the name of an incoming form field to the name of a
structure field. While this behavior is typical, it may also be desired to use
different names for form fields and struct fields while still parsing as
expected. You can ask Rocket to look for a different form field for a given
structure field by using the `#[field(name = "name")]` field annotation.
structure field by using one or more `#[field(name = "name")]` or `#[field(name
= uncased("name")]` field annotation. The `uncased` variant case-insensitively
matches field names.
As an example, say that you're writing an application that receives data from an
external service. The external service `POST`s a form with a field named
`first-Name` which you'd like to write as `first_name` in Rust. Field renaming
helps:
`first-Name` which you'd like to write as `first_name` in Rust. Such a form
structure can be written as:
```rust
# #[macro_use] extern crate rocket;
# fn main() {}
# use rocket::form::FromForm;
#[derive(FromForm)]
struct External {
@ -782,8 +783,41 @@ struct External {
}
```
Rocket will then match the form field named `first-Name` to the structure field
named `first_name`.
If you want to accept both `firstName` case-insensitively as well as
`first_name` case-sensitively, you'll need to use two annotations:
```rust
# use rocket::form::FromForm;
#[derive(FromForm)]
struct External {
#[field(name = uncased("firstName"))]
#[field(name = "first_name")]
first_name: String
}
```
This will match any casing of `firstName` including `FirstName`, `firstname`,
`FIRSTname`, and so on, but only match exactly on `first_name`.
If instead you wanted to match any of `first-name`, `first_name` or `firstName`,
in each instance case-insensitively, you would write:
```rust
# use rocket::form::FromForm;
#[derive(FromForm)]
struct External {
#[field(name = uncased("first-name"))]
#[field(name = uncased("first_name"))]
#[field(name = uncased("firstname"))]
first_name: String
}
```
Cased and uncased renamings can be mixed and matched, and any number of
renamings is allowed. Rocket will emit an error at compile-time if field names
conflict, preventing ambiguous parsing at runtime.
### Ad-Hoc Validation