mirror of https://github.com/rwf2/Rocket.git
Allow form field renaming via #[form(field = "name")] attribute.
This commit is contained in:
parent
351658801e
commit
7c19bf784d
|
@ -1,10 +1,12 @@
|
||||||
#![allow(unused_imports)] // FIXME: Why is this coming from quote_tokens?
|
#![allow(unused_imports)] // FIXME: Why is this coming from quote_tokens?
|
||||||
|
|
||||||
use std::mem::transmute;
|
use std::mem::transmute;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use syntax::ext::base::{Annotatable, ExtCtxt};
|
use syntax::ext::base::{Annotatable, ExtCtxt};
|
||||||
use syntax::print::pprust::{stmt_to_string};
|
use syntax::print::pprust::{stmt_to_string};
|
||||||
use syntax::ast::{ItemKind, Expr, MetaItem, Mutability, VariantData, Ident};
|
use syntax::ast::{ItemKind, Expr, MetaItem, Mutability, VariantData, Ident};
|
||||||
|
use syntax::ast::StructField;
|
||||||
use syntax::codemap::Span;
|
use syntax::codemap::Span;
|
||||||
use syntax::ext::build::AstBuilder;
|
use syntax::ext::build::AstBuilder;
|
||||||
use syntax::ptr::P;
|
use syntax::ptr::P;
|
||||||
|
@ -13,7 +15,7 @@ use syntax_ext::deriving::generic::MethodDef;
|
||||||
use syntax_ext::deriving::generic::{StaticStruct, Substructure, TraitDef, ty};
|
use syntax_ext::deriving::generic::{StaticStruct, Substructure, TraitDef, ty};
|
||||||
use syntax_ext::deriving::generic::combine_substructure as c_s;
|
use syntax_ext::deriving::generic::combine_substructure as c_s;
|
||||||
|
|
||||||
use utils::strip_ty_lifetimes;
|
use utils::{strip_ty_lifetimes, SpanExt};
|
||||||
|
|
||||||
static ONLY_STRUCTS_ERR: &'static str = "`FromForm` can only be derived for \
|
static ONLY_STRUCTS_ERR: &'static str = "`FromForm` can only be derived for \
|
||||||
structures with named fields.";
|
structures with named fields.";
|
||||||
|
@ -49,6 +51,55 @@ fn get_struct_lifetime(ecx: &mut ExtCtxt, item: &Annotatable, span: Span)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn extract_field_ident_name(ecx: &ExtCtxt, struct_field: &StructField)
|
||||||
|
-> (Ident, String, Span) {
|
||||||
|
let ident = match struct_field.ident {
|
||||||
|
Some(ident) => ident,
|
||||||
|
None => ecx.span_fatal(struct_field.span, ONLY_STRUCTS_ERR)
|
||||||
|
};
|
||||||
|
|
||||||
|
let field_attrs: Vec<_> = struct_field.attrs.iter()
|
||||||
|
.filter(|attr| attr.check_name("form"))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let default = |ident: Ident| (ident, ident.to_string(), struct_field.span);
|
||||||
|
if field_attrs.len() == 0 {
|
||||||
|
return default(ident);
|
||||||
|
} else if field_attrs.len() > 1 {
|
||||||
|
ecx.span_err(struct_field.span, "only a single #[form(..)] \
|
||||||
|
attribute can be applied to a given struct field at a time");
|
||||||
|
return default(ident);
|
||||||
|
}
|
||||||
|
|
||||||
|
let field_attr = field_attrs[0];
|
||||||
|
::syntax::attr::mark_known(&field_attr);
|
||||||
|
if !field_attr.meta_item_list().map_or(false, |l| l.len() == 1) {
|
||||||
|
ecx.struct_span_err(field_attr.span, "incorrect use of attribute")
|
||||||
|
.help(r#"the `form` attribute must have the form: #[form(field = "..")]"#)
|
||||||
|
.emit();
|
||||||
|
return default(ident);
|
||||||
|
}
|
||||||
|
|
||||||
|
let inner_item = &field_attr.meta_item_list().unwrap()[0];
|
||||||
|
if !inner_item.check_name("field") {
|
||||||
|
ecx.struct_span_err(inner_item.span, "invalid `form` attribute contents")
|
||||||
|
.help(r#"only the 'field' key is supported: #[form(field = "..")]"#)
|
||||||
|
.emit();
|
||||||
|
return default(ident);
|
||||||
|
}
|
||||||
|
|
||||||
|
if !inner_item.is_value_str() {
|
||||||
|
ecx.struct_span_err(inner_item.span, "invalid `field` in attribute")
|
||||||
|
.help(r#"the `form` attribute must have the form: #[form(field = "..")]"#)
|
||||||
|
.emit();
|
||||||
|
return default(ident);
|
||||||
|
}
|
||||||
|
|
||||||
|
let name = inner_item.value_str().unwrap().as_str().to_string();
|
||||||
|
let sp = inner_item.span.shorten_upto(name.len() + 2);
|
||||||
|
(ident, name, sp)
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: Use proper logging to emit the error messages.
|
// TODO: Use proper logging to emit the error messages.
|
||||||
pub fn from_form_derive(ecx: &mut ExtCtxt, span: Span, meta_item: &MetaItem,
|
pub fn from_form_derive(ecx: &mut ExtCtxt, span: Span, meta_item: &MetaItem,
|
||||||
annotated: &Annotatable, push: &mut FnMut(Annotatable)) {
|
annotated: &Annotatable, push: &mut FnMut(Annotatable)) {
|
||||||
|
@ -144,19 +195,25 @@ fn from_form_substructure(cx: &mut ExtCtxt, trait_span: Span, substr: &Substruct
|
||||||
_ => cx.span_bug(trait_span, "impossible substructure in `from_form`")
|
_ => cx.span_bug(trait_span, "impossible substructure in `from_form`")
|
||||||
};
|
};
|
||||||
|
|
||||||
// Create a vector of (ident, type) pairs, one for each field in struct.
|
// Vec of (ident: Ident, type: Ty, name: String), one for each field.
|
||||||
let mut fields_and_types = vec![];
|
let mut names = HashMap::new();
|
||||||
|
let mut fields_info = vec![];
|
||||||
for field in fields {
|
for field in fields {
|
||||||
let ident = match field.ident {
|
let (ident, name, span) = extract_field_ident_name(cx, field);
|
||||||
Some(ident) => ident,
|
|
||||||
None => cx.span_fatal(trait_span, ONLY_STRUCTS_ERR)
|
|
||||||
};
|
|
||||||
|
|
||||||
let stripped_ty = strip_ty_lifetimes(field.ty.clone());
|
let stripped_ty = strip_ty_lifetimes(field.ty.clone());
|
||||||
fields_and_types.push((ident, stripped_ty));
|
|
||||||
|
if let Some(sp) = names.get(&name).map(|sp| *sp) {
|
||||||
|
cx.struct_span_err(span, "field with duplicate name")
|
||||||
|
.span_note(sp, "original was declared here")
|
||||||
|
.emit();
|
||||||
|
} else {
|
||||||
|
names.insert(name.clone(), span);
|
||||||
|
}
|
||||||
|
|
||||||
|
fields_info.push((ident, stripped_ty, name));
|
||||||
}
|
}
|
||||||
|
|
||||||
debug!("Fields and types: {:?}", fields_and_types);
|
debug!("Fields, types, attrs: {:?}", fields_info);
|
||||||
let mut stmts = Vec::new();
|
let mut stmts = Vec::new();
|
||||||
|
|
||||||
// The thing to do when we wish to exit with an error.
|
// The thing to do when we wish to exit with an error.
|
||||||
|
@ -168,7 +225,7 @@ fn from_form_substructure(cx: &mut ExtCtxt, trait_span: Span, substr: &Substruct
|
||||||
// placed into the final struct. They start out as `None` and are changed
|
// placed into the final struct. They start out as `None` and are changed
|
||||||
// to Some when a parse completes, or some default value if the parse was
|
// to Some when a parse completes, or some default value if the parse was
|
||||||
// unsuccessful and default() returns Some.
|
// unsuccessful and default() returns Some.
|
||||||
for &(ref ident, ref ty) in &fields_and_types {
|
for &(ref ident, ref ty, _) in &fields_info {
|
||||||
stmts.push(quote_stmt!(cx,
|
stmts.push(quote_stmt!(cx,
|
||||||
let mut $ident: ::std::option::Option<$ty> = None;
|
let mut $ident: ::std::option::Option<$ty> = None;
|
||||||
).unwrap());
|
).unwrap());
|
||||||
|
@ -177,17 +234,15 @@ fn from_form_substructure(cx: &mut ExtCtxt, trait_span: Span, substr: &Substruct
|
||||||
// Generating an arm for each struct field. This matches against the key and
|
// Generating an arm for each struct field. This matches against the key and
|
||||||
// tries to parse the value according to the type.
|
// tries to parse the value according to the type.
|
||||||
let mut arms = vec![];
|
let mut arms = vec![];
|
||||||
for &(ref ident, _) in &fields_and_types {
|
for &(ref ident, _, ref name) in &fields_info {
|
||||||
let ident_string = ident.to_string();
|
|
||||||
let id_str = ident_string.as_str();
|
|
||||||
arms.push(quote_tokens!(cx,
|
arms.push(quote_tokens!(cx,
|
||||||
$id_str => {
|
$name => {
|
||||||
let r = ::rocket::http::RawStr::from_str(v);
|
let r = ::rocket::http::RawStr::from_str(v);
|
||||||
$ident = match ::rocket::request::FromFormValue::from_form_value(r) {
|
$ident = match ::rocket::request::FromFormValue::from_form_value(r) {
|
||||||
Ok(v) => Some(v),
|
Ok(v) => Some(v),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
println!(" => Error parsing form val '{}': {:?}",
|
println!(" => Error parsing form val '{}': {:?}",
|
||||||
$id_str, e);
|
$name, e);
|
||||||
$return_err_stmt
|
$return_err_stmt
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -219,7 +274,7 @@ fn from_form_substructure(cx: &mut ExtCtxt, trait_span: Span, substr: &Substruct
|
||||||
// that each parameter actually is Some() or has a default value.
|
// that each parameter actually is Some() or has a default value.
|
||||||
let mut failure_conditions = vec![];
|
let mut failure_conditions = vec![];
|
||||||
|
|
||||||
for &(ref ident, ref ty) in (&fields_and_types).iter() {
|
for &(ref ident, ref ty, _) in (&fields_info).iter() {
|
||||||
failure_conditions.push(quote_tokens!(cx,
|
failure_conditions.push(quote_tokens!(cx,
|
||||||
if $ident.is_none() &&
|
if $ident.is_none() &&
|
||||||
<$ty as ::rocket::request::FromFormValue>::default().is_none() {
|
<$ty as ::rocket::request::FromFormValue>::default().is_none() {
|
||||||
|
@ -232,7 +287,7 @@ fn from_form_substructure(cx: &mut ExtCtxt, trait_span: Span, substr: &Substruct
|
||||||
// The fields of the struct, which are just the let bindings declared above
|
// The fields of the struct, which are just the let bindings declared above
|
||||||
// or the default value.
|
// or the default value.
|
||||||
let mut result_fields = vec![];
|
let mut result_fields = vec![];
|
||||||
for &(ref ident, ref ty) in &fields_and_types {
|
for &(ref ident, ref ty, _) in &fields_info {
|
||||||
result_fields.push(quote_tokens!(cx,
|
result_fields.push(quote_tokens!(cx,
|
||||||
$ident: $ident.unwrap_or_else(||
|
$ident: $ident.unwrap_or_else(||
|
||||||
<$ty as ::rocket::request::FromFormValue>::default().unwrap()
|
<$ty as ::rocket::request::FromFormValue>::default().unwrap()
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
use syntax::codemap::{Span, BytePos};
|
use syntax::codemap::{Span, BytePos};
|
||||||
|
|
||||||
pub trait SpanExt {
|
pub trait SpanExt {
|
||||||
fn shorten_to(self, to_length: usize) -> Span;
|
|
||||||
|
|
||||||
/// Trim the span on the left and right by `length`.
|
/// Trim the span on the left and right by `length`.
|
||||||
fn trim(self, length: u32) -> Span;
|
fn trim(self, length: u32) -> Span;
|
||||||
|
|
||||||
|
@ -11,6 +9,12 @@ pub trait SpanExt {
|
||||||
|
|
||||||
/// Trim the span on the right by `length`.
|
/// Trim the span on the right by `length`.
|
||||||
fn trim_right(self, length: usize) -> Span;
|
fn trim_right(self, length: usize) -> Span;
|
||||||
|
|
||||||
|
// Trim from the right so that the span is `length` in size.
|
||||||
|
fn shorten_to(self, to_length: usize) -> Span;
|
||||||
|
|
||||||
|
// Trim from the left so that the span is `length` in size.
|
||||||
|
fn shorten_upto(self, length: usize) -> Span;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SpanExt for Span {
|
impl SpanExt for Span {
|
||||||
|
@ -29,6 +33,11 @@ impl SpanExt for Span {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn shorten_upto(mut self, length: usize) -> Span {
|
||||||
|
self.lo = self.hi - BytePos(length as u32);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
fn trim(mut self, length: u32) -> Span {
|
fn trim(mut self, length: u32) -> Span {
|
||||||
self.lo = self.lo + BytePos(length);
|
self.lo = self.lo + BytePos(length);
|
||||||
self.hi = self.hi - BytePos(length);
|
self.hi = self.hi - BytePos(length);
|
||||||
|
|
|
@ -0,0 +1,83 @@
|
||||||
|
#![feature(plugin, custom_derive)]
|
||||||
|
#![plugin(rocket_codegen)]
|
||||||
|
|
||||||
|
#[derive(FromForm)]
|
||||||
|
struct MyForm {
|
||||||
|
#[form(field = "blah", field = "bloo")]
|
||||||
|
//~^ ERROR: incorrect use of attribute
|
||||||
|
my_field: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(FromForm)]
|
||||||
|
struct MyForm1 {
|
||||||
|
#[form]
|
||||||
|
//~^ ERROR: incorrect use of attribute
|
||||||
|
my_field: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(FromForm)]
|
||||||
|
struct MyForm2 {
|
||||||
|
#[form("blah")]
|
||||||
|
//~^ ERROR: invalid `form` attribute
|
||||||
|
my_field: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(FromForm)]
|
||||||
|
struct MyForm3 {
|
||||||
|
#[form(123)]
|
||||||
|
//~^ ERROR: invalid `form` attribute
|
||||||
|
my_field: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(FromForm)]
|
||||||
|
struct MyForm4 {
|
||||||
|
#[form(beep = "bop")]
|
||||||
|
//~^ ERROR: invalid `form` attribute
|
||||||
|
my_field: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(FromForm)]
|
||||||
|
struct MyForm5 {
|
||||||
|
#[form(field = "blah")]
|
||||||
|
#[form(field = "blah")]
|
||||||
|
my_field: String,
|
||||||
|
//~^ ERROR: only a single
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(FromForm)]
|
||||||
|
struct MyForm6 {
|
||||||
|
#[form(field = true)]
|
||||||
|
//~^ ERROR: invalid `field` in attribute
|
||||||
|
my_field: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(FromForm)]
|
||||||
|
struct MyForm7 {
|
||||||
|
#[form(field)]
|
||||||
|
//~^ ERROR: invalid `field` in attribute
|
||||||
|
my_field: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(FromForm)]
|
||||||
|
struct MyForm8 {
|
||||||
|
#[form(field = 123)]
|
||||||
|
//~^ ERROR: invalid `field` in attribute
|
||||||
|
my_field: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(FromForm)]
|
||||||
|
struct MyForm9 {
|
||||||
|
#[form(field = "hello")]
|
||||||
|
first: String,
|
||||||
|
#[form(field = "hello")]
|
||||||
|
//~^ ERROR: field with duplicate name
|
||||||
|
other: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(FromForm)]
|
||||||
|
struct MyForm10 {
|
||||||
|
first: String,
|
||||||
|
#[form(field = "first")]
|
||||||
|
//~^ ERROR: field with duplicate name
|
||||||
|
other: String,
|
||||||
|
}
|
|
@ -0,0 +1,54 @@
|
||||||
|
#![feature(plugin, custom_derive)]
|
||||||
|
#![plugin(rocket_codegen)]
|
||||||
|
|
||||||
|
extern crate rocket;
|
||||||
|
|
||||||
|
use rocket::request::{FromForm, FromFormValue, FormItems};
|
||||||
|
use rocket::http::RawStr;
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, FromForm)]
|
||||||
|
struct Form {
|
||||||
|
single: usize,
|
||||||
|
#[form(field = "camelCase")]
|
||||||
|
camel_case: String,
|
||||||
|
#[form(field = "TitleCase")]
|
||||||
|
title_case: String,
|
||||||
|
#[form(field = "type")]
|
||||||
|
field_type: isize,
|
||||||
|
#[form(field = "DOUBLE")]
|
||||||
|
double: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse<'f, T: FromForm<'f>>(string: &'f str) -> Option<T> {
|
||||||
|
let mut items = FormItems::from(string);
|
||||||
|
let result = T::from_form_items(items.by_ref());
|
||||||
|
if !items.exhaust() {
|
||||||
|
panic!("Invalid form input.");
|
||||||
|
}
|
||||||
|
|
||||||
|
result.ok()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let form_string = &[
|
||||||
|
"single=100", "camelCase=helloThere", "TitleCase=HiHi", "type=-2",
|
||||||
|
"DOUBLE=bing_bong"
|
||||||
|
].join("&");
|
||||||
|
|
||||||
|
let form: Option<Form> = parse(&form_string);
|
||||||
|
assert_eq!(form, Some(Form {
|
||||||
|
single: 100,
|
||||||
|
camel_case: "helloThere".into(),
|
||||||
|
title_case: "HiHi".into(),
|
||||||
|
field_type: -2,
|
||||||
|
double: "bing_bong".into()
|
||||||
|
}));
|
||||||
|
|
||||||
|
let form_string = &[
|
||||||
|
"single=100", "camel_case=helloThere", "TitleCase=HiHi", "type=-2",
|
||||||
|
"DOUBLE=bing_bong"
|
||||||
|
].join("&");
|
||||||
|
|
||||||
|
let form: Option<Form> = parse(&form_string);
|
||||||
|
assert!(form.is_none());
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
#![feature(plugin, custom_derive, custom_attribute)]
|
#![feature(plugin, custom_derive)]
|
||||||
#![plugin(rocket_codegen)]
|
#![plugin(rocket_codegen)]
|
||||||
|
|
||||||
extern crate rocket_contrib;
|
extern crate rocket_contrib;
|
||||||
|
|
|
@ -33,9 +33,11 @@ impl<'v> FromFormValue<'v> for FormOption {
|
||||||
struct FormInput {
|
struct FormInput {
|
||||||
checkbox: bool,
|
checkbox: bool,
|
||||||
number: usize,
|
number: usize,
|
||||||
|
#[form(field = "type")]
|
||||||
radio: FormOption,
|
radio: FormOption,
|
||||||
password: String,
|
password: String,
|
||||||
textarea: String,
|
#[form(field = "textarea")]
|
||||||
|
text_area: String,
|
||||||
select: FormOption,
|
select: FormOption,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,15 +14,15 @@
|
||||||
</label>
|
</label>
|
||||||
<br /><br />
|
<br /><br />
|
||||||
|
|
||||||
<label for="radio">Radios:
|
<label for="type">Type:
|
||||||
<label for="a">A
|
<label for="a">A
|
||||||
<input type="radio" name="radio" id="a" value="a" />
|
<input type="radio" name="type" id="a" value="a" />
|
||||||
</label>
|
</label>
|
||||||
<label for="b">B
|
<label for="b">B
|
||||||
<input type="radio" name="radio" id="b" value="b" checked />
|
<input type="radio" name="type" id="b" value="b" checked />
|
||||||
</label>
|
</label>
|
||||||
<label for="c">C
|
<label for="c">C
|
||||||
<input type="radio" name="radio" id="c" value="c" />
|
<input type="radio" name="type" id="c" value="c" />
|
||||||
</label>
|
</label>
|
||||||
</label>
|
</label>
|
||||||
<br /><br />
|
<br /><br />
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
#![feature(plugin, custom_derive, custom_attribute)]
|
#![feature(plugin, custom_derive)]
|
||||||
#![plugin(rocket_codegen)]
|
#![plugin(rocket_codegen)]
|
||||||
|
|
||||||
extern crate rocket_contrib;
|
extern crate rocket_contrib;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
#![feature(plugin, custom_derive, custom_attribute)]
|
#![feature(plugin, custom_derive)]
|
||||||
#![plugin(rocket_codegen)]
|
#![plugin(rocket_codegen)]
|
||||||
|
|
||||||
extern crate rocket;
|
extern crate rocket;
|
||||||
|
|
Loading…
Reference in New Issue