Implemented FromForm derivation. Woo!

This commit is contained in:
Sergio Benitez 2016-04-04 04:14:18 -07:00
parent b76dc137d3
commit d0dd49f98d
4 changed files with 220 additions and 54 deletions

View File

@ -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/<username>")]
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<isize, &'a str>,
#[derive(FromForm)]
struct UserLogin<'r> {
username: &'r str,
password: &'r str,
age: Result<isize, &'r str>,
}
// 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<Self, Error> {
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<Result<isize, &'f str>> = 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 = "<user>")]
fn login(user: UserLogin) -> Result<Redirect, String> {

207
macros/src/derive_form.rs Normal file
View File

@ -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<Expr> {
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()))
}

View File

@ -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);
}

View File

@ -313,10 +313,10 @@ fn get_form_stmt(ecx: &ExtCtxt, fn_args: &mut Vec<SimpleArg>,
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.");