mirror of https://github.com/rwf2/Rocket.git
Allow multiple and uncased field renamings.
This commit is contained in:
parent
f0a6b9a25a
commit
7784cc982a
|
@ -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 == "_"
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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| {
|
||||
|
|
|
@ -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>>>()?;
|
||||
|
||||
|
|
|
@ -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)?;)
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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() { }
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
Loading…
Reference in New Issue