diff --git a/examples/forms/src/main.rs b/examples/forms/src/main.rs index 72941dda..f8faa484 100644 --- a/examples/forms/src/main.rs +++ b/examples/forms/src/main.rs @@ -1,4 +1,4 @@ -#![feature(plugin)] +#![feature(plugin, custom_derive)] #![plugin(rocket_macros)] extern crate rocket; @@ -7,65 +7,19 @@ mod files; use rocket::Rocket; use rocket::response::Redirect; -use rocket::Error; -use rocket::form::{FromForm, FromFormValue, form_items}; #[route(GET, path = "/user/")] fn user_page(username: &str) -> String { format!("This is {}'s page.", username) } -// #[derive(FromForm)] // FIXME: Make that happen. -struct UserLogin<'a> { - username: &'a str, - password: &'a str, - age: Result, +#[derive(FromForm)] +struct UserLogin<'r> { + username: &'r str, + password: &'r str, + age: Result, } -// will help for validation. IE, can have a type Range(1, 10) that returns an -// enum with one of: TooLow(isize), TooHigh(isize), etc. -impl<'f> FromForm<'f> for UserLogin<'f> { - fn from_form_string(s: &'f str) -> Result { - let mut items = [("", ""); 3]; - let form_count = form_items(s, &mut items); - if form_count != items.len() { - return Err(Error::BadParse); - } - - let mut username: Option<&'f str> = None; - let mut password: Option<&'f str> = None; - let mut age: Option> = None; - for &(key, value) in &items { - match key { - "username" => username = match FromFormValue::parse(value) { - Ok(v) => Some(v), - Err(_) => return Err(Error::BadParse) - }, - "password" => password = match FromFormValue::parse(value) { - Ok(v) => Some(v), - Err(_) => return Err(Error::BadParse) - }, - "age" => age = match FromFormValue::parse(value) { - Ok(v) => Some(v), - Err(_) => return Err(Error::BadParse) - }, - _ => return Err(Error::BadParse) - } - } - - if username.is_none() || password.is_none() { - return Err(Error::BadParse); - } - - Ok(UserLogin { - username: username.unwrap(), - password: password.unwrap(), - age: age.unwrap(), - }) - } -} - -// TODO: Actually look at form parameters. // FIXME: fn login<'a>(user: UserLogin<'a>) #[route(POST, path = "/login", form = "")] fn login(user: UserLogin) -> Result { diff --git a/macros/src/derive_form.rs b/macros/src/derive_form.rs new file mode 100644 index 00000000..f22c9efa --- /dev/null +++ b/macros/src/derive_form.rs @@ -0,0 +1,207 @@ +#![allow(unused_imports)] // FIXME: Why is this coming from quote_tokens? + +use syntax::ext::base::{Annotatable, ExtCtxt}; +use syntax::ast::{ItemKind, Expr, MetaItem, Mutability, VariantData}; +use syntax::codemap::Span; +use syntax::ext::build::AstBuilder; +use syntax::ptr::P; +use std::mem::transmute; + +use syntax_ext::deriving::generic::MethodDef; +use syntax_ext::deriving::generic::{StaticStruct, Substructure, TraitDef, ty}; +use syntax_ext::deriving::generic::combine_substructure as c_s; + +const DEBUG: bool = false; + +static ONLY_STRUCTS_ERR: &'static str = "`FromForm` can only be derived for \ + structures with named fields."; + +fn get_struct_lifetime(ecx: &mut ExtCtxt, item: &Annotatable, span: Span) + -> Option<&'static str> { + match item { + &Annotatable::Item(ref item) => match item.node { + ItemKind::Struct(_, ref generics) => { + match generics.lifetimes.len() { + 0 => None, + 1 => { + let lifetime = generics.lifetimes[0].lifetime; + // According to the documentation, this is safe: + // Because the interner lives for the life of the + // thread, this can be safely treated as an immortal + // string, as long as it never crosses between threads. + let lifetime_name: &'static str = + unsafe { transmute(&*lifetime.name.as_str()) }; + Some(lifetime_name) + } + _ => { + ecx.span_err(item.span, "cannot have more than one \ + lifetime parameter when deriving `FromForm`."); + None + } + } + }, + _ => ecx.span_fatal(span, ONLY_STRUCTS_ERR) + }, + _ => ecx.span_fatal(span, ONLY_STRUCTS_ERR) + } +} + +pub fn from_form_derive(ecx: &mut ExtCtxt, span: Span, meta_item: &MetaItem, + annotated: &Annotatable, push: &mut FnMut(Annotatable)) { + let lifetime_var = get_struct_lifetime(ecx, annotated, span); + + let trait_def = TraitDef { + is_unsafe: false, + span: span, + attributes: Vec::new(), + path: ty::Path { + path: vec!["rocket", "form", "FromForm"], + lifetime: lifetime_var, + params: vec![], + global: true, + }, + additional_bounds: Vec::new(), + generics: ty::LifetimeBounds::empty(), + methods: vec![ + MethodDef { + name: "from_form_string", + generics: ty::LifetimeBounds::empty(), + explicit_self: None, + args: vec![ + ty::Ptr( + Box::new(ty::Literal(ty::Path::new_local("str"))), + ty::Borrowed(lifetime_var, Mutability::Immutable) + ) + ], + ret_ty: ty::Ty::Literal( + ty::Path { + path: vec!["std", "result", "Result"], + lifetime: None, + params: vec![ + Box::new(ty::Ty::Self_), + Box::new(ty::Ty::Literal( + ty::Path::new(vec!["rocket", "Error"]) + )), + ], + global: true, + } + ), + attributes: vec![], + is_unsafe: false, + combine_substructure: c_s(Box::new(from_form_substructure)), + } + ], + associated_types: vec![], + }; + + trait_def.expand(ecx, meta_item, annotated, push); +} + +// Mostly copied from syntax::ext::deriving::hash +/// Defines how the implementation for `trace()` is to be generated +fn from_form_substructure(cx: &mut ExtCtxt, trait_span: Span, substr: &Substructure) -> P { + let arg = if substr.nonself_args.len() == 1 { + &substr.nonself_args[0] + } else { + let msg = format!("incorrect number of arguments in `from_form_string`: \ + expected {}, found {}", 1, substr.nonself_args.len()); + cx.span_bug(trait_span, msg.as_str()); + }; + + debug!("argument is: {:?}", arg); + + let fields = match substr.fields { + &StaticStruct(var_data, _) => match var_data { + &VariantData::Struct(ref fields, _) => fields, + _ => cx.span_fatal(trait_span, ONLY_STRUCTS_ERR) + }, + _ => cx.span_bug(trait_span, "impossible substructure in `from_form`") + }; + + let mut fields_and_types = vec![]; + for field in fields { + let ident = match field.node.ident() { + Some(ident) => ident, + None => cx.span_fatal(trait_span, ONLY_STRUCTS_ERR) + }; + + fields_and_types.push((ident, &field.node.ty)); + } + + debug!("Fields and types: {:?}", fields_and_types); + let mut stmts = Vec::new(); + + let return_err_stmt = quote_tokens!(cx, + return Err(::rocket::Error::BadParse) + ); + + let num_fields = fields_and_types.len(); + let initial_block = quote_block!(cx, { + let mut items = [("", ""); $num_fields]; + let form_count = ::rocket::form::form_items($arg, &mut items); + if form_count != items.len() { + $return_err_stmt; + }; + }); + + stmts.extend(initial_block.unwrap().stmts); + + for &(ref ident, ref ty) in &fields_and_types { + stmts.push(quote_stmt!(cx, + let mut $ident: ::std::option::Option<$ty> = None; + ).unwrap()); + } + + let mut arms = vec![]; + for &(ref ident, _) in &fields_and_types { + let ident_string = ident.to_string(); + let id_str = ident_string.as_str(); + arms.push(quote_tokens!(cx, + $id_str => $ident = match ::rocket::form::FromFormValue::parse(v) { + Ok(v) => Some(v), + Err(_) => $return_err_stmt + }, + )); + } + + stmts.push(quote_stmt!(cx, + for &(k, v) in &items { + match k { + $arms + _ => $return_err_stmt + }; + } + ).unwrap()); + + let mut failure_conditions = vec![]; + for (i, &(ref ident, _)) in (&fields_and_types).iter().enumerate() { + if i > 0 { + failure_conditions.push(quote_tokens!(cx, || $ident.is_none())); + } else { + failure_conditions.push(quote_tokens!(cx, $ident.is_none())); + } + } + + let mut result_fields = vec![]; + for &(ref ident, _) in &fields_and_types { + result_fields.push(quote_tokens!(cx, + $ident: $ident.unwrap(), + )); + } + + let self_ident = substr.type_ident; + let final_block = quote_block!(cx, { + if $failure_conditions { + $return_err_stmt; + } + + return Ok($self_ident { + $result_fields + }); + }); + + stmts.extend(final_block.unwrap().stmts); + cx.expr_block(cx.block(trait_span, stmts, None)) + // cx.expr_block(P(initial_block.unwrap())) +} + diff --git a/macros/src/lib.rs b/macros/src/lib.rs index 106d340b..9cfd32af 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -2,6 +2,7 @@ #![feature(quote, concat_idents, plugin_registrar, rustc_private)] #[macro_use] extern crate syntax; +extern crate syntax_ext; extern crate rustc; extern crate rustc_plugin; extern crate rocket; @@ -9,6 +10,7 @@ extern crate rocket; #[macro_use] mod utils; mod routes_macro; mod route_decorator; +mod derive_form; use rustc_plugin::Registry; use syntax::ext::base::SyntaxExtension; @@ -16,6 +18,7 @@ use syntax::parse::token::intern; use routes_macro::routes_macro; use route_decorator::route_decorator; +use derive_form::from_form_derive; const STRUCT_PREFIX: &'static str = "static_rocket_route_info_for_"; const FN_PREFIX: &'static str = "rocket_route_fn_"; @@ -24,5 +27,7 @@ const FN_PREFIX: &'static str = "rocket_route_fn_"; pub fn plugin_registrar(reg: &mut Registry) { reg.register_syntax_extension(intern("route"), SyntaxExtension::MultiDecorator(Box::new(route_decorator))); + reg.register_syntax_extension(intern("derive_FromForm"), + SyntaxExtension::MultiDecorator(Box::new(from_form_derive))); reg.register_macro("routes", routes_macro); } diff --git a/macros/src/route_decorator.rs b/macros/src/route_decorator.rs index 12a9648d..19883b45 100644 --- a/macros/src/route_decorator.rs +++ b/macros/src/route_decorator.rs @@ -313,10 +313,10 @@ fn get_form_stmt(ecx: &ExtCtxt, fn_args: &mut Vec, let $param_ident: $param_ty = { let form_string = std::str::from_utf8(_req.data); if form_string.is_err() { - return rocket::Response::not_found() + return ::rocket::Response::not_found() }; - match FromForm::from_form_string(form_string.unwrap()) { + match ::rocket::form::FromForm::from_form_string(form_string.unwrap()) { Ok(v) => v, Err(_) => { println!("\t=> Form failed to parse.");