mirror of https://github.com/rwf2/Rocket.git
Implemented FromForm derivation. Woo!
This commit is contained in:
parent
b76dc137d3
commit
d0dd49f98d
|
@ -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> {
|
||||
|
|
|
@ -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()))
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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.");
|
||||
|
|
Loading…
Reference in New Issue