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 { impl Dynamic {
// This isn't public since this `Dynamic` should always be an `Ignored`. // This isn't public since this `Dynamic` should always be an `Ignored`.
pub fn is_wild(&self) -> bool { 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(query_params.iter().filter_map(|p| p.guard()))
.chain(data_guard.as_ref().into_iter()); .chain(data_guard.as_ref().into_iter());
all_other_guards.all(|g| g.name != name) all_other_guards.all(|g| &g.name != *name)
}) })
.enumerate() .enumerate()
.map(|(index, (name, (ident, ty)))| Guard { .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_mut::VisitMut;
use syn::visit::Visit; use syn::visit::Visit;
use crate::proc_macro2::{Span, TokenStream, TokenTree}; use crate::proc_macro2::{TokenStream, TokenTree};
use crate::syn_ext::IdentExt; use crate::syn_ext::IdentExt;
use crate::name::Name; use crate::name::Name;
pub struct FormField { #[derive(Debug)]
pub span: Span, pub enum FieldName {
pub name: Name, Cased(Name),
Uncased(Name),
} }
#[derive(FromMeta)] #[derive(FromMeta)]
pub struct FieldAttr { pub struct FieldAttr {
pub name: Option<FormField>, pub name: Option<FieldName>,
pub validate: Option<SpanWrapped<syn::Expr>>, pub validate: Option<SpanWrapped<syn::Expr>>,
} }
@ -24,12 +25,13 @@ impl FieldAttr {
pub(crate) trait FieldExt { pub(crate) trait FieldExt {
fn ident(&self) -> &syn::Ident; 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 stripped_ty(&self) -> syn::Type;
fn name_view(&self) -> Result<syn::Expr>; fn name_view(&self) -> Result<syn::Expr>;
} }
impl FromMeta for FormField { impl FromMeta for FieldName {
fn from_meta(meta: &MetaItem) -> Result<Self> { fn from_meta(meta: &MetaItem) -> Result<Self> {
// These are used during parsing. // These are used during parsing.
const CONTROL_CHARS: &[char] = &['&', '=', '?', '.', '[', ']']; const CONTROL_CHARS: &[char] = &['&', '=', '?', '.', '[', ']'];
@ -44,8 +46,23 @@ impl FromMeta for FormField {
s.chars().all(|c| c.is_ascii_graphic() && !CONTROL_CHARS.contains(&c)) s.chars().all(|c| c.is_ascii_graphic() && !CONTROL_CHARS.contains(&c))
} }
let name = Name::from_meta(meta)?; let field_name = match Name::from_meta(meta) {
if !is_valid_field_name(name.as_str()) { 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() let chars = CONTROL_CHARS.iter()
.map(|c| format!("{:?}", c)) .map(|c| format!("{:?}", c))
.collect::<Vec<_>>() .collect::<Vec<_>>()
@ -56,7 +73,35 @@ impl FromMeta for FormField {
.help(format!("field name cannot be `isindex` or contain {}", chars))); .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") self.ident.as_ref().expect("named")
} }
fn field_name(&self) -> Result<String> { fn field_names(&self) -> Result<Vec<FieldName>> {
let mut fields = FieldAttr::from_attrs(FieldAttr::NAME, &self.attrs)? let attr_names = FieldAttr::from_attrs(FieldAttr::NAME, &self.attrs)?
.into_iter() .into_iter()
.filter_map(|attr| attr.name); .filter_map(|attr| attr.name)
.collect::<Vec<_>>();
let name = fields.next() if attr_names.is_empty() {
.map(|f| f.name) let ident_name = Name::from(self.ident());
.unwrap_or_else(|| Name::from(self.ident().clone())); return Ok(vec![FieldName::Cased(ident_name)]);
if let Some(field) = fields.next() {
return Err(field.span
.error("duplicate form field renaming")
.help("a field can only be renamed once"));
} }
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 { fn stripped_ty(&self) -> syn::Type {
@ -88,7 +142,8 @@ impl FieldExt for Field<'_> {
} }
fn name_view(&self) -> Result<syn::Expr> { 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); define_spanned_export!(self.span() => _form);
let name_view = quote_spanned! { self.span() => let name_view = quote_spanned! { self.span() =>
#_form::NameBuf::from((__c.__parent, #field_name)) #_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::exports::*;
use crate::proc_macro2::TokenStream; use crate::proc_macro2::TokenStream;
use crate::derive::form_field::*; use crate::derive::form_field::{*, FieldName::*};
// 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>
where F: Fn(&syn::Type, &syn::Expr) -> TokenStream where F: Fn(&syn::Type, &syn::Expr) -> TokenStream
{ {
let matchers = fields.iter() let mut matchers = vec![];
.map(|f| { for field in fields.iter() {
let (ident, field_name, ty) = (f.ident(), f.field_name()?, f.stripped_ty()); let (ident, ty) = (field.ident(), field.stripped_ty());
let field_context = quote_spanned!(ty.span() => { let field_context = 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<'__f>>::init(_o))
}); });
let field_context = syn::parse2(field_context).expect("valid expr"); let field_names = field.field_names()?;
let expr = map_f(&ty, &field_context); let field_context = syn::parse2(field_context).expect("valid expr");
Ok(quote!(#field_name => { #expr })) let push = map_f(&ty, &field_context);
}) let field_matchers = field_names.iter().map(|f| match f {
.collect::<Result<Vec<TokenStream>>>()?; Cased(name) => quote!(#name => { #push }),
Uncased(name) => quote!(__n if __n.as_uncased() == #name => { #push }),
});
matchers.extend(field_matchers);
}
Ok(quote! { Ok(quote! {
__c.__parent = __f.name.parent(); __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")); 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() { for field in fields.iter() {
let name = field.field_name()?; names.append(&mut field.field_names()?);
if let Some(span) = names.get(&name) { field_map.push((names.len(), field));
return Err(field.span().error("duplicate form field") }
.span_note(*span, "previously defined here"));
}
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(()) Ok(())
@ -105,6 +128,10 @@ pub fn derive_from_form(input: proc_macro::TokenStream) -> TokenStream {
quote_spanned!(ty.span() => #ident: #field_ty,) quote_spanned!(ty.span() => #ident: #field_ty,)
}) })
) )
.outer_mapper(quote! {
#[allow(unused_imports)]
use #_http::uncased::AsUncased;
})
.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| {

View File

@ -34,7 +34,7 @@ pub fn derive_from_form_field(input: proc_macro::TokenStream) -> TokenStream {
.try_enum_map(|_, data| { .try_enum_map(|_, data| {
let variant_name_sources = data.variants() let variant_name_sources = data.variants()
.map(|v| FieldAttr::one_from_attrs("field", &v.attrs).map(|o| { .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>>>()?; .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 span = field.span().into();
let accessor = field.accessor(); let accessor = field.accessor();
let tokens = if field.ident.is_some() { 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)?;) 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

@ -1,17 +1,18 @@
use crate::syn::{self, Ident, ext::IdentExt}; use crate::syn::{self, Ident, ext::IdentExt};
use crate::http::uncased::UncasedStr;
use crate::proc_macro2::Span; use crate::proc_macro2::Span;
/// A "name" read by codegen, which may or may not be a valid identifier. A /// A "name" read by codegen, which may or may not be an identifier. A `Name` is
/// `Name` is typically constructed indirectly via FromMeta, or From<Ident> or /// typically constructed indirectly via FromMeta, or From<Ident> or directly
/// directly from a string via `Name::new()`. /// from a string via `Name::new()`.
/// ///
/// Some "names" in Rocket include: /// Some "names" in Rocket include:
/// * Dynamic parameter: `name` in `<name>` /// * Dynamic parameter: `name` in `<name>`
/// * Renamed fields: `foo` in #[field(name = "foo")]. /// * Renamed fields: `foo` in #[field(name = "foo")].
/// ///
/// `Name` implements Hash, PartialEq, and Eq, and additionally PartialEq<S> for /// `Name` implements Hash, PartialEq, and Eq, and additionally PartialEq<S> for
/// all types `S: AsStr<str>`. These implementations all compare the value of /// all types `S: PartialEq<str>`. These implementations all compare the value
/// `name()` only. /// of `as_str()` only.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Name { pub struct Name {
value: String, value: String,
@ -31,6 +32,11 @@ impl Name {
&self.value &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 { pub fn span(&self) -> Span {
self.span self.span
} }
@ -50,7 +56,7 @@ impl devise::FromMeta for Name {
impl quote::ToTokens for Name { impl quote::ToTokens for Name {
fn to_tokens(&self, tokens: &mut devise::proc_macro2::TokenStream) { 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 { impl AsRef<str> for Name {
fn as_ref(&self) -> &str { fn as_ref(&self) -> &str {
self.as_str() self.as_str()
@ -88,9 +88,9 @@ impl std::ops::Deref for Name {
impl Eq 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 { 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(); let form: Option<RenamedForm> = strict(&form_string).ok();
assert!(form.is_none()); 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] #[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) = 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 --> $DIR/from_form.rs:33:5
| |
33 | foo: usize, 33 | foo: usize,
| ^^^^^^^^^^ | ^^^
| |
note: previously defined here note: previous field with conflicting name
--> $DIR/from_form.rs:31:5 --> $DIR/from_form.rs:31:5
| |
31 | / #[field(name = "foo")] 31 | / #[field(name = "foo")]
32 | | field: String, 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` note: error occurred while deriving `FromForm`
--> $DIR/from_form.rs:29:10 --> $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) = 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:40:5 --> $DIR/from_form.rs:40:20
| |
40 | / #[field(name = "hello")] 40 | #[field(name = "hello")]
41 | | other: String, | ^^^^^^^
| |_________________^
| |
note: previously defined here note: previous field with conflicting name
--> $DIR/from_form.rs:38:5 --> $DIR/from_form.rs:38:5
| |
38 | / #[field(name = "hello")] 38 | / #[field(name = "hello")]
39 | | first: String, 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` note: error occurred while deriving `FromForm`
--> $DIR/from_form.rs:36:10 --> $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) = 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 --> $DIR/from_form.rs:47:5
| |
47 | / #[field(name = "first")] 47 | / #[field(name = "first")]
48 | | other: String, 48 | | other: String,
| |_________________^ | |_________________^
|
note: previously defined here
--> $DIR/from_form.rs:46:5
|
46 | first: String,
| ^^^^^^^^^^^^^
note: error occurred while deriving `FromForm` note: error occurred while deriving `FromForm`
--> $DIR/from_form.rs:44:10 --> $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) = 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 --> $DIR/from_form.rs:84:20
| |
84 | #[field(name = "blah")] 84 | #[field(name = "blah")]
| ^^^^^^ | ^^^^^^
|
= help: a field can only be renamed once
note: error occurred while deriving `FromForm` note: error occurred while deriving `FromForm`
--> $DIR/from_form.rs:81:10 --> $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) = 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 --> $DIR/from_form.rs:90:20
| |
90 | #[field(name = true)] 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) = 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 --> $DIR/from_form.rs:96:13
| |
96 | #[field(name)] 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) = 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 --> $DIR/from_form.rs:102:20
| |
102 | #[field(name = 123)] 102 | #[field(name = 123)]

View File

@ -103,3 +103,101 @@ note: error occurred while deriving `FromFormField`
33 | #[derive(FromFormField)] 33 | #[derive(FromFormField)]
| ^^^^^^^^^^^^^ | ^^^^^^^^^^^^^
= 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
--> $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) = 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 --> $DIR/uri_display.rs:22:20
| |
22 | #[field(name = 123)] 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) = 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 --> $DIR/from_form.rs:33:5
| |
33 | foo: usize, 33 | foo: usize,
| ^^^ | ^^^
error: [note] previously defined here error: [note] previous field with conflicting name
--> $DIR/from_form.rs:31:5 --> $DIR/from_form.rs:31:5
| |
31 | #[field(name = "foo")] 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` error: [note] error occurred while deriving `FromForm`
--> $DIR/from_form.rs:29:10 --> $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) = 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:40:5 --> $DIR/from_form.rs:40:20
| |
40 | #[field(name = "hello")] 40 | #[field(name = "hello")]
| ^ | ^^^^^^^
error: [note] previously defined here error: [note] previous field with conflicting name
--> $DIR/from_form.rs:38:5 --> $DIR/from_form.rs:38:5
| |
38 | #[field(name = "hello")] 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` error: [note] error occurred while deriving `FromForm`
--> $DIR/from_form.rs:36:10 --> $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) = 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:5 --> $DIR/from_form.rs:47:20
| |
47 | #[field(name = "first")] 47 | #[field(name = "first")]
| ^ | ^^^^^^^
error: [note] previously defined here error: [note] previous field with conflicting name
--> $DIR/from_form.rs:46:5 --> $DIR/from_form.rs:46:5
| |
46 | first: String, 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` error: [note] error occurred while deriving `FromForm`
--> $DIR/from_form.rs:44:10 --> $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) = 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
--- help: a field can only be renamed once --> $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 --> $DIR/from_form.rs:84:20
| |
84 | #[field(name = "blah")] 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) = 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 --> $DIR/from_form.rs:90:20
| |
90 | #[field(name = true)] 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) = 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 --> $DIR/from_form.rs:96:13
| |
96 | #[field(name)] 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) = 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 --> $DIR/from_form.rs:102:20
| |
102 | #[field(name = 123)] 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) = 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) = 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 --> $DIR/uri_display.rs:22:20
| |
22 | #[field(name = 123)] 22 | #[field(name = 123)]

View File

@ -36,4 +36,33 @@ enum Bar2 {
A, 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() { } 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 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 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 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 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 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 `first-Name` which you'd like to write as `first_name` in Rust. Such a form
helps: structure can be written as:
```rust ```rust
# #[macro_use] extern crate rocket; # use rocket::form::FromForm;
# fn main() {}
#[derive(FromForm)] #[derive(FromForm)]
struct External { struct External {
@ -782,8 +783,41 @@ struct External {
} }
``` ```
Rocket will then match the form field named `first-Name` to the structure field If you want to accept both `firstName` case-insensitively as well as
named `first_name`. `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 ### Ad-Hoc Validation