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)]
|
#![plugin(rocket_macros)]
|
||||||
|
|
||||||
extern crate rocket;
|
extern crate rocket;
|
||||||
|
@ -7,65 +7,19 @@ mod files;
|
||||||
|
|
||||||
use rocket::Rocket;
|
use rocket::Rocket;
|
||||||
use rocket::response::Redirect;
|
use rocket::response::Redirect;
|
||||||
use rocket::Error;
|
|
||||||
use rocket::form::{FromForm, FromFormValue, form_items};
|
|
||||||
|
|
||||||
#[route(GET, path = "/user/<username>")]
|
#[route(GET, path = "/user/<username>")]
|
||||||
fn user_page(username: &str) -> String {
|
fn user_page(username: &str) -> String {
|
||||||
format!("This is {}'s page.", username)
|
format!("This is {}'s page.", username)
|
||||||
}
|
}
|
||||||
|
|
||||||
// #[derive(FromForm)] // FIXME: Make that happen.
|
#[derive(FromForm)]
|
||||||
struct UserLogin<'a> {
|
struct UserLogin<'r> {
|
||||||
username: &'a str,
|
username: &'r str,
|
||||||
password: &'a str,
|
password: &'r str,
|
||||||
age: Result<isize, &'a 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>)
|
// FIXME: fn login<'a>(user: UserLogin<'a>)
|
||||||
#[route(POST, path = "/login", form = "<user>")]
|
#[route(POST, path = "/login", form = "<user>")]
|
||||||
fn login(user: UserLogin) -> Result<Redirect, String> {
|
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)]
|
#![feature(quote, concat_idents, plugin_registrar, rustc_private)]
|
||||||
|
|
||||||
#[macro_use] extern crate syntax;
|
#[macro_use] extern crate syntax;
|
||||||
|
extern crate syntax_ext;
|
||||||
extern crate rustc;
|
extern crate rustc;
|
||||||
extern crate rustc_plugin;
|
extern crate rustc_plugin;
|
||||||
extern crate rocket;
|
extern crate rocket;
|
||||||
|
@ -9,6 +10,7 @@ extern crate rocket;
|
||||||
#[macro_use] mod utils;
|
#[macro_use] mod utils;
|
||||||
mod routes_macro;
|
mod routes_macro;
|
||||||
mod route_decorator;
|
mod route_decorator;
|
||||||
|
mod derive_form;
|
||||||
|
|
||||||
use rustc_plugin::Registry;
|
use rustc_plugin::Registry;
|
||||||
use syntax::ext::base::SyntaxExtension;
|
use syntax::ext::base::SyntaxExtension;
|
||||||
|
@ -16,6 +18,7 @@ use syntax::parse::token::intern;
|
||||||
|
|
||||||
use routes_macro::routes_macro;
|
use routes_macro::routes_macro;
|
||||||
use route_decorator::route_decorator;
|
use route_decorator::route_decorator;
|
||||||
|
use derive_form::from_form_derive;
|
||||||
|
|
||||||
const STRUCT_PREFIX: &'static str = "static_rocket_route_info_for_";
|
const STRUCT_PREFIX: &'static str = "static_rocket_route_info_for_";
|
||||||
const FN_PREFIX: &'static str = "rocket_route_fn_";
|
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) {
|
pub fn plugin_registrar(reg: &mut Registry) {
|
||||||
reg.register_syntax_extension(intern("route"),
|
reg.register_syntax_extension(intern("route"),
|
||||||
SyntaxExtension::MultiDecorator(Box::new(route_decorator)));
|
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);
|
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 $param_ident: $param_ty = {
|
||||||
let form_string = std::str::from_utf8(_req.data);
|
let form_string = std::str::from_utf8(_req.data);
|
||||||
if form_string.is_err() {
|
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,
|
Ok(v) => v,
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
println!("\t=> Form failed to parse.");
|
println!("\t=> Form failed to parse.");
|
||||||
|
|
Loading…
Reference in New Issue