mirror of https://github.com/rwf2/Rocket.git
Major changes. FN params are now being used! Woo!
Subset of list of changes: * Split up decorator and macro into their own files. * Fully parsing the path parameter and verifying against the function's args. * Actually calling methods to fetch and convert the request parameters. * Actually calling methods to convert the handler's return type. * Sketched out more of the Request/Response structures. Pretty close to having a fully working MVP.
This commit is contained in:
parent
d5db70afc4
commit
ad08fe1d04
|
@ -2,24 +2,19 @@
|
||||||
#![plugin(rocket_macros)]
|
#![plugin(rocket_macros)]
|
||||||
|
|
||||||
extern crate rocket;
|
extern crate rocket;
|
||||||
use rocket::{Rocket, Request, Response, Method, Route};
|
use rocket::Rocket;
|
||||||
|
|
||||||
#[route(GET, path = "/hello")]
|
#[route(GET, path = "/<name>")]
|
||||||
fn hello() -> &'static str {
|
fn hello(name: String) -> String {
|
||||||
"Hello, world!"
|
format!("Hello, {}!", name)
|
||||||
}
|
}
|
||||||
|
|
||||||
mod test {
|
#[route(PUT, path = "/<x>/<y>")]
|
||||||
use rocket::{Request, Response, Method, Route};
|
fn bye(x: usize, y: usize) -> String {
|
||||||
|
format!("{} + {} = {}", x, y, x + y)
|
||||||
#[route(GET, path = "")]
|
|
||||||
pub fn hello() -> &'static str {
|
|
||||||
"Hello, world!"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let mut rocket = Rocket::new("localhost", 8000);
|
let rocket = Rocket::new("localhost", 8000);
|
||||||
rocket.mount("/test", routes![test::hello]);
|
rocket.mount_and_launch("/", routes![hello, bye]);
|
||||||
rocket.mount_and_launch("/", routes![hello]);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
BadMethod
|
BadMethod,
|
||||||
|
BadParse,
|
||||||
|
NoKey
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,9 +2,13 @@ extern crate hyper;
|
||||||
|
|
||||||
mod method;
|
mod method;
|
||||||
mod error;
|
mod error;
|
||||||
|
mod response;
|
||||||
|
mod request;
|
||||||
|
|
||||||
pub use method::Method;
|
pub use method::Method;
|
||||||
pub use error::Error;
|
pub use error::Error;
|
||||||
|
pub use response::Response;
|
||||||
|
pub use request::Request;
|
||||||
|
|
||||||
use hyper::server::Handler as HypHandler;
|
use hyper::server::Handler as HypHandler;
|
||||||
use hyper::server::Request as HypRequest;
|
use hyper::server::Request as HypRequest;
|
||||||
|
@ -14,9 +18,6 @@ use hyper::Server;
|
||||||
|
|
||||||
pub type Handler = fn(Request) -> Response;
|
pub type Handler = fn(Request) -> Response;
|
||||||
|
|
||||||
pub struct Request;
|
|
||||||
pub struct Response;
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub struct Route<'a> {
|
pub struct Route<'a> {
|
||||||
pub method: Method,
|
pub method: Method,
|
||||||
|
@ -48,10 +49,9 @@ impl Rocket {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn mount(&mut self, base: &str, routes: &[&Route]) -> &mut Self {
|
pub fn mount(&mut self, base: &str, routes: &[&Route]) -> &mut Self {
|
||||||
println!("Mounting at {}", base);
|
println!("🛰 Mounting '{}':", base);
|
||||||
for route in routes {
|
for route in routes {
|
||||||
println!(" - Found {} route to {}", route.method, route.path);
|
println!("\t* {} '{}'", route.method, route.path);
|
||||||
(route.handler)(Request);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self
|
self
|
||||||
|
@ -64,7 +64,7 @@ impl Rocket {
|
||||||
|
|
||||||
pub fn launch(self) {
|
pub fn launch(self) {
|
||||||
let full_addr = format!("{}:{}", self.address, self.port);
|
let full_addr = format!("{}:{}", self.address, self.port);
|
||||||
println!("🚀 Rocket is launching ({})...", full_addr);
|
println!("🚀 Rocket has launched from {}...", full_addr);
|
||||||
let _ = Server::http(full_addr.as_str()).unwrap().handle(self);
|
let _ = Server::http(full_addr.as_str()).unwrap().handle(self);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use self::Method::{Options, Get, Post, Put, Delete, Head, Trace, Connect, Patch};
|
use self::Method::*;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::fmt::{self, Display};
|
use std::fmt::{self, Display};
|
||||||
use error::Error;
|
use error::Error;
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
use std::str::FromStr;
|
||||||
|
use error::Error;
|
||||||
|
|
||||||
|
pub struct Request;
|
||||||
|
|
||||||
|
impl Request {
|
||||||
|
pub fn get_param_str(&self, name: &str) -> Result<&str, Error> {
|
||||||
|
Err(Error::NoKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_param<T: FromStr>(&self, name: &str) -> Result<T, Error> {
|
||||||
|
self.get_param_str(name).and_then(|s| {
|
||||||
|
T::from_str(s).map_err(|_| Error::BadParse)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
pub struct Response;
|
||||||
|
|
||||||
|
impl<'a> From<&'a str> for Response {
|
||||||
|
fn from(_s: &'a str) -> Self {
|
||||||
|
Response
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<String> for Response {
|
||||||
|
fn from(_s: String) -> Self {
|
||||||
|
Response
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Response {
|
||||||
|
pub fn error(number: usize) -> Response {
|
||||||
|
println!("ERROR {}!", number);
|
||||||
|
Response
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,178 +6,20 @@ extern crate rustc;
|
||||||
extern crate rustc_plugin;
|
extern crate rustc_plugin;
|
||||||
extern crate rocket;
|
extern crate rocket;
|
||||||
|
|
||||||
#[macro_use] mod macro_utils;
|
#[macro_use] mod utils;
|
||||||
|
mod routes_macro;
|
||||||
|
mod route_decorator;
|
||||||
|
|
||||||
use macro_utils::{prepend_ident, get_key_values};
|
|
||||||
|
|
||||||
use std::str::FromStr;
|
|
||||||
use rustc_plugin::Registry;
|
use rustc_plugin::Registry;
|
||||||
|
|
||||||
use syntax::parse::token::{intern};
|
|
||||||
use syntax::ext::base::SyntaxExtension;
|
use syntax::ext::base::SyntaxExtension;
|
||||||
use syntax::ext::quote::rt::ToTokens;
|
use syntax::parse::token::intern;
|
||||||
use syntax::codemap::Span;
|
|
||||||
use syntax::ast::{Item, ItemKind, MetaItem, MetaItemKind, FnDecl, LitKind};
|
|
||||||
use syntax::ext::base::{Annotatable, ExtCtxt};
|
|
||||||
use syntax::ptr::P;
|
|
||||||
|
|
||||||
use syntax::ast::{Path, PathSegment, Expr, ExprKind, TokenTree};
|
use routes_macro::routes_macro;
|
||||||
use syntax::ext::base::{DummyResult, MacResult, MacEager};
|
use route_decorator::route_decorator;
|
||||||
use syntax::ext::build::AstBuilder; // trait for expr_usize
|
|
||||||
use syntax::parse::parser::{Parser, PathParsingMode};
|
|
||||||
use syntax::parse::PResult;
|
|
||||||
use syntax::parse::token::Token;
|
|
||||||
|
|
||||||
const DEBUG: bool = true;
|
|
||||||
|
|
||||||
const STRUCT_PREFIX: &'static str = "ROCKET_ROUTE_STRUCT_";
|
const STRUCT_PREFIX: &'static str = "ROCKET_ROUTE_STRUCT_";
|
||||||
const FN_PREFIX: &'static str = "rocket_route_fn_";
|
const FN_PREFIX: &'static str = "rocket_route_fn_";
|
||||||
|
|
||||||
use rocket::Method;
|
|
||||||
|
|
||||||
struct Params {
|
|
||||||
method: Method,
|
|
||||||
path: String
|
|
||||||
}
|
|
||||||
|
|
||||||
fn bad_item_fatal(ecx: &mut ExtCtxt, dec_sp: Span, i_sp: Span) -> ! {
|
|
||||||
ecx.span_err(dec_sp, "This decorator cannot be used on non-functions...");
|
|
||||||
ecx.span_fatal(i_sp, "...but an attempt to use it on the item below was made.")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn bad_method_err(ecx: &mut ExtCtxt, dec_sp: Span, message: &str) -> Method {
|
|
||||||
let message = format!("{} Valid methods are: [GET, PUT, POST, DELETE, \
|
|
||||||
OPTIONS, HEAD, TRACE, CONNECT, PATCH]", message);
|
|
||||||
ecx.span_err(dec_sp, message.as_str());
|
|
||||||
Method::Get
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_fn_decl<'a>(ecx: &mut ExtCtxt, sp: Span, annotated: &'a Annotatable)
|
|
||||||
-> (&'a P<Item>, &'a P<FnDecl>) {
|
|
||||||
// `annotated` is the AST object for the annotated item.
|
|
||||||
let item: &P<Item> = match annotated {
|
|
||||||
&Annotatable::Item(ref item) => item,
|
|
||||||
&Annotatable::TraitItem(ref item) => bad_item_fatal(ecx, sp, item.span),
|
|
||||||
&Annotatable::ImplItem(ref item) => bad_item_fatal(ecx, sp, item.span)
|
|
||||||
};
|
|
||||||
|
|
||||||
let fn_decl: &P<FnDecl> = match item.node {
|
|
||||||
ItemKind::Fn(ref decl, _, _, _, _, _) => decl,
|
|
||||||
_ => bad_item_fatal(ecx, sp, item.span)
|
|
||||||
};
|
|
||||||
|
|
||||||
(item, fn_decl)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_route_params(ecx: &mut ExtCtxt, meta_item: &MetaItem) -> Params {
|
|
||||||
// First, check that the macro was used in the #[route(a, b, ..)] form.
|
|
||||||
let params: &Vec<P<MetaItem>> = match meta_item.node {
|
|
||||||
MetaItemKind::List(_, ref params) => params,
|
|
||||||
_ => ecx.span_fatal(meta_item.span,
|
|
||||||
"incorrect use of macro. correct form is: #[demo(...)]"),
|
|
||||||
};
|
|
||||||
|
|
||||||
// Ensure we can unwrap the k = v params.
|
|
||||||
if params.len() < 1 {
|
|
||||||
bad_method_err(ecx, meta_item.span, "HTTP method parameter is missing.");
|
|
||||||
ecx.span_fatal(meta_item.span, "At least 2 arguments are required.");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the method and the rest of the k = v params. Ensure method parameter
|
|
||||||
// is valid. If it's not, issue an error but use "GET" to continue parsing.
|
|
||||||
let (method_param, kv_params) = params.split_first().unwrap();
|
|
||||||
let method = if let MetaItemKind::Word(ref word) = method_param.node {
|
|
||||||
Method::from_str(word).unwrap_or_else(|_| {
|
|
||||||
let message = format!("{} is not a valid method.", word);
|
|
||||||
bad_method_err(ecx, method_param.span, message.as_str())
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
bad_method_err(ecx, method_param.span, "Invalid parameter. Expected a
|
|
||||||
valid HTTP method at this position.")
|
|
||||||
};
|
|
||||||
|
|
||||||
// Now grab all of the required and optional parameters.
|
|
||||||
let req: [&'static str; 1] = ["path"];
|
|
||||||
let opt: [&'static str; 0] = [];
|
|
||||||
let kv_pairs = get_key_values(ecx, meta_item.span, &req, &opt, kv_params);
|
|
||||||
|
|
||||||
// Ensure we have a path, just to keep parsing and generating errors.
|
|
||||||
let path = kv_pairs.get("path").map_or(String::from("/"), |s| {
|
|
||||||
String::from(*s)
|
|
||||||
});
|
|
||||||
|
|
||||||
Params {
|
|
||||||
method: method,
|
|
||||||
path: path
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn route_decorator(ecx: &mut ExtCtxt, sp: Span, meta_item: &MetaItem,
|
|
||||||
annotated: &Annotatable, push: &mut FnMut(Annotatable)) {
|
|
||||||
let (item, fn_decl) = get_fn_decl(ecx, sp, annotated);
|
|
||||||
let route_params = get_route_params(ecx, meta_item);
|
|
||||||
|
|
||||||
let route_fn_name = prepend_ident(FN_PREFIX, &item.ident);
|
|
||||||
let fn_name = item.ident;
|
|
||||||
push(Annotatable::Item(quote_item!(ecx,
|
|
||||||
fn $route_fn_name(_req: Request) -> Response {
|
|
||||||
let result = $fn_name();
|
|
||||||
println!("Routing function. Result: {}", result);
|
|
||||||
Response
|
|
||||||
}
|
|
||||||
).unwrap()));
|
|
||||||
|
|
||||||
let struct_name = prepend_ident(STRUCT_PREFIX, &item.ident);
|
|
||||||
let path = route_params.path;
|
|
||||||
let struct_item = quote_item!(ecx,
|
|
||||||
#[allow(non_upper_case_globals)]
|
|
||||||
pub static $struct_name: Route<'static> = Route {
|
|
||||||
method: Method::Get, // FIXME
|
|
||||||
path: $path,
|
|
||||||
handler: $route_fn_name
|
|
||||||
};
|
|
||||||
).unwrap();
|
|
||||||
push(Annotatable::Item(struct_item));
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_paths<'a>(parser: &mut Parser<'a>) -> PResult<'a, Vec<Path>> {
|
|
||||||
if parser.eat(&Token::Eof) {
|
|
||||||
return Ok(vec![]);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut results = Vec::new();
|
|
||||||
loop {
|
|
||||||
results.push(try!(parser.parse_path(PathParsingMode::NoTypesAllowed)));
|
|
||||||
if !parser.eat(&Token::Comma) {
|
|
||||||
try!(parser.expect(&Token::Eof));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(results)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn routes_macro(ecx: &mut ExtCtxt, sp: Span, args: &[TokenTree])
|
|
||||||
-> Box<MacResult + 'static> {
|
|
||||||
let mut parser = ecx.new_parser_from_tts(args);
|
|
||||||
let mut paths = get_paths(&mut parser).unwrap_or_else(|mut e| {
|
|
||||||
e.emit();
|
|
||||||
vec![]
|
|
||||||
});
|
|
||||||
|
|
||||||
// Prefix each path terminator with STRUCT_PREFIX.
|
|
||||||
for p in &mut paths {
|
|
||||||
let last = p.segments.len() - 1;
|
|
||||||
let last_seg = &mut p.segments[last];
|
|
||||||
let new_ident = prepend_ident(STRUCT_PREFIX, &last_seg.identifier);
|
|
||||||
last_seg.identifier = new_ident;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build up the P<Expr> for each &path.
|
|
||||||
let path_exprs = paths.iter().map(|p| { quote_expr!(ecx, &$p) }).collect();
|
|
||||||
MacEager::expr(ecx.expr_vec_slice(sp, path_exprs))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[plugin_registrar]
|
#[plugin_registrar]
|
||||||
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"),
|
||||||
|
|
|
@ -0,0 +1,233 @@
|
||||||
|
use super::{STRUCT_PREFIX, FN_PREFIX};
|
||||||
|
use utils::{prepend_ident, get_key_values};
|
||||||
|
|
||||||
|
use std::str::FromStr;
|
||||||
|
use std::collections::HashSet;
|
||||||
|
|
||||||
|
use syntax::ext::quote::rt::ToTokens;
|
||||||
|
use syntax::codemap::{Span, DUMMY_SP};
|
||||||
|
use syntax::ast::{Ident, TokenTree, PatKind};
|
||||||
|
use syntax::ast::{Item, Expr, ItemKind, MetaItem, MetaItemKind, FnDecl};
|
||||||
|
use syntax::ext::base::{Annotatable, ExtCtxt};
|
||||||
|
use syntax::ptr::P;
|
||||||
|
use syntax::ext::build::AstBuilder;
|
||||||
|
use syntax::print::pprust::item_to_string;
|
||||||
|
use syntax::parse::token::{self, str_to_ident};
|
||||||
|
|
||||||
|
use rocket::Method;
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
const DEBUG: bool = true;
|
||||||
|
|
||||||
|
struct Params {
|
||||||
|
method: Method,
|
||||||
|
path: String
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bad_item_fatal(ecx: &mut ExtCtxt, dec_sp: Span, i_sp: Span) -> ! {
|
||||||
|
ecx.span_err(dec_sp, "This decorator cannot be used on non-functions...");
|
||||||
|
ecx.span_fatal(i_sp, "...but it was used on the item below.")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bad_method_err(ecx: &mut ExtCtxt, dec_sp: Span, message: &str) -> Method {
|
||||||
|
let message = format!("{} Valid methods are: [GET, PUT, POST, DELETE, \
|
||||||
|
OPTIONS, HEAD, TRACE, CONNECT, PATCH]", message);
|
||||||
|
ecx.span_err(dec_sp, message.as_str());
|
||||||
|
Method::Get
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_fn_decl<'a>(ecx: &mut ExtCtxt, sp: Span, annotated: &'a Annotatable)
|
||||||
|
-> (&'a P<Item>, &'a P<FnDecl>) {
|
||||||
|
// `annotated` is the AST object for the annotated item.
|
||||||
|
let item: &P<Item> = match annotated {
|
||||||
|
&Annotatable::Item(ref item) => item,
|
||||||
|
&Annotatable::TraitItem(ref item) => bad_item_fatal(ecx, sp, item.span),
|
||||||
|
&Annotatable::ImplItem(ref item) => bad_item_fatal(ecx, sp, item.span)
|
||||||
|
};
|
||||||
|
|
||||||
|
let fn_decl: &P<FnDecl> = match item.node {
|
||||||
|
ItemKind::Fn(ref decl, _, _, _, _, _) => decl,
|
||||||
|
_ => bad_item_fatal(ecx, sp, item.span)
|
||||||
|
};
|
||||||
|
|
||||||
|
(item, fn_decl)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_route_params(ecx: &mut ExtCtxt, meta_item: &MetaItem) -> Params {
|
||||||
|
// First, check that the macro was used in the #[route(a, b, ..)] form.
|
||||||
|
let params: &Vec<P<MetaItem>> = match meta_item.node {
|
||||||
|
MetaItemKind::List(_, ref params) => params,
|
||||||
|
_ => ecx.span_fatal(meta_item.span,
|
||||||
|
"incorrect use of macro. correct form is: #[demo(...)]"),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Ensure we can unwrap the k = v params.
|
||||||
|
if params.len() < 1 {
|
||||||
|
bad_method_err(ecx, meta_item.span, "HTTP method parameter is missing.");
|
||||||
|
ecx.span_fatal(meta_item.span, "At least 2 arguments are required.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the method and the rest of the k = v params. Ensure method parameter
|
||||||
|
// is valid. If it's not, issue an error but use "GET" to continue parsing.
|
||||||
|
let (method_param, kv_params) = params.split_first().unwrap();
|
||||||
|
let method = if let MetaItemKind::Word(ref word) = method_param.node {
|
||||||
|
Method::from_str(word).unwrap_or_else(|_| {
|
||||||
|
let message = format!("{} is not a valid method.", word);
|
||||||
|
bad_method_err(ecx, method_param.span, message.as_str())
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
bad_method_err(ecx, method_param.span, "Invalid parameter. Expected a
|
||||||
|
valid HTTP method at this position.")
|
||||||
|
};
|
||||||
|
|
||||||
|
// Now grab all of the required and optional parameters.
|
||||||
|
let req: [&'static str; 1] = ["path"];
|
||||||
|
let opt: [&'static str; 0] = [];
|
||||||
|
let kv_pairs = get_key_values(ecx, meta_item.span, &req, &opt, kv_params);
|
||||||
|
|
||||||
|
// Ensure we have a path, just to keep parsing and generating errors.
|
||||||
|
let path = kv_pairs.get("path").map_or(String::from("/"), |s| {
|
||||||
|
String::from(*s)
|
||||||
|
});
|
||||||
|
|
||||||
|
Params {
|
||||||
|
method: method,
|
||||||
|
path: path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Is there a better way to do this? I need something with ToTokens for the
|
||||||
|
// quote_expr macro that builds the route struct. I tried using
|
||||||
|
// str_to_ident("rocket::Method::Options"), but this seems to miss the context,
|
||||||
|
// and you get an 'ident not found' on compile. I also tried using the path expr
|
||||||
|
// builder from ASTBuilder: same thing.
|
||||||
|
fn method_variant_to_expr(ecx: &ExtCtxt, method: Method) -> P<Expr> {
|
||||||
|
match method {
|
||||||
|
Method::Options => quote_expr!(ecx, rocket::Method::Options),
|
||||||
|
Method::Get => quote_expr!(ecx, rocket::Method::Get),
|
||||||
|
Method::Post => quote_expr!(ecx, rocket::Method::Post),
|
||||||
|
Method::Put => quote_expr!(ecx, rocket::Method::Put),
|
||||||
|
Method::Delete => quote_expr!(ecx, rocket::Method::Delete),
|
||||||
|
Method::Head => quote_expr!(ecx, rocket::Method::Head),
|
||||||
|
Method::Trace => quote_expr!(ecx, rocket::Method::Trace),
|
||||||
|
Method::Connect => quote_expr!(ecx, rocket::Method::Connect),
|
||||||
|
Method::Patch => quote_expr!(ecx, rocket::Method::Patch),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_fn_params(ecx: &ExtCtxt, sp: Span, path: &str,
|
||||||
|
fn_decl: &FnDecl) -> Vec<String> {
|
||||||
|
let mut seen = HashSet::new();
|
||||||
|
let bad_match_err = "Path string is malformed.";
|
||||||
|
let mut matching = false;
|
||||||
|
|
||||||
|
// Collect all of the params in the path and insert into HashSet.
|
||||||
|
let mut start = 0;
|
||||||
|
for (i, c) in path.char_indices() {
|
||||||
|
match c {
|
||||||
|
'<' if !matching => {
|
||||||
|
matching = true;
|
||||||
|
start = i;
|
||||||
|
},
|
||||||
|
'>' if matching => {
|
||||||
|
matching = false;
|
||||||
|
if start + 1 < i {
|
||||||
|
let param_name = &path[(start + 1)..i];
|
||||||
|
seen.insert(param_name);
|
||||||
|
} else {
|
||||||
|
ecx.span_err(sp, "Parameter cannot be empty.");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'<' if matching => ecx.span_err(sp, bad_match_err),
|
||||||
|
'>' if !matching => ecx.span_err(sp, bad_match_err),
|
||||||
|
_ => { /* ... */ }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure every param in the function declaration is in `path`. Also add
|
||||||
|
// each param name in the declaration to the result vector.
|
||||||
|
let mut result = vec![];
|
||||||
|
for arg in &fn_decl.inputs {
|
||||||
|
let ident: &Ident = match arg.pat.node {
|
||||||
|
PatKind::Ident(_, ref ident, _) => &ident.node,
|
||||||
|
_ => {
|
||||||
|
ecx.span_err(sp, "Expected an identifier."); // FIXME: fn span.
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let name = ident.to_string();
|
||||||
|
if !seen.remove(name.as_str()) {
|
||||||
|
let msg = format!("'{}' appears in the function declaration but \
|
||||||
|
not in the path string.", name);
|
||||||
|
ecx.span_err(sp, msg.as_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
result.push(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure every param in `path` is in the function declaration.
|
||||||
|
for item in seen {
|
||||||
|
let msg = format!("'{}' appears in the path string but not in the \
|
||||||
|
function declaration.", item);
|
||||||
|
ecx.span_err(sp, msg.as_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn route_decorator(ecx: &mut ExtCtxt, sp: Span, meta_item: &MetaItem,
|
||||||
|
annotated: &Annotatable, push: &mut FnMut(Annotatable)) {
|
||||||
|
let (item, fn_decl) = get_fn_decl(ecx, sp, annotated);
|
||||||
|
let route_params = get_route_params(ecx, meta_item);
|
||||||
|
let fn_params = get_fn_params(ecx, sp, &route_params.path, &fn_decl);
|
||||||
|
|
||||||
|
debug!("Path: {:?}", route_params.path);
|
||||||
|
debug!("Function Declaration: {:?}", fn_decl);
|
||||||
|
|
||||||
|
let mut fn_param_exprs = vec![];
|
||||||
|
for param in &fn_params {
|
||||||
|
let param_ident = str_to_ident(param.as_str());
|
||||||
|
fn_param_exprs.push(quote_stmt!(ecx,
|
||||||
|
let $param_ident = match req.get_param($param) {
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(_) => return rocket::Response::error(200)
|
||||||
|
};
|
||||||
|
).unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut fn_param_idents: Vec<TokenTree> = vec![];
|
||||||
|
for i in 0..fn_params.len() {
|
||||||
|
let tokens = str_to_ident(fn_params[i].as_str()).to_tokens(ecx);
|
||||||
|
fn_param_idents.extend(tokens);
|
||||||
|
if i < fn_params.len() - 1 {
|
||||||
|
fn_param_idents.push(TokenTree::Token(DUMMY_SP, token::Comma));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
debug!("Final Params: {:?}", fn_params);
|
||||||
|
let route_fn_name = prepend_ident(FN_PREFIX, &item.ident);
|
||||||
|
let fn_name = item.ident;
|
||||||
|
let route_fn_item = quote_item!(ecx,
|
||||||
|
fn $route_fn_name(req: rocket::Request) -> rocket::Response {
|
||||||
|
$fn_param_exprs
|
||||||
|
let result = $fn_name($fn_param_idents);
|
||||||
|
rocket::Response::from(result)
|
||||||
|
}
|
||||||
|
).unwrap();
|
||||||
|
debug!("{}", item_to_string(&route_fn_item));
|
||||||
|
push(Annotatable::Item(route_fn_item));
|
||||||
|
|
||||||
|
let struct_name = prepend_ident(STRUCT_PREFIX, &item.ident);
|
||||||
|
let path = route_params.path;
|
||||||
|
let method = method_variant_to_expr(ecx, route_params.method);
|
||||||
|
push(Annotatable::Item(quote_item!(ecx,
|
||||||
|
#[allow(non_upper_case_globals)]
|
||||||
|
pub static $struct_name: rocket::Route<'static> = rocket::Route {
|
||||||
|
method: $method,
|
||||||
|
path: $path,
|
||||||
|
handler: $route_fn_name
|
||||||
|
};
|
||||||
|
).unwrap()));
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,48 @@
|
||||||
|
use super::{STRUCT_PREFIX};
|
||||||
|
use utils::prepend_ident;
|
||||||
|
|
||||||
|
use syntax::codemap::Span;
|
||||||
|
use syntax::ast::{Path, TokenTree};
|
||||||
|
use syntax::ext::base::{ExtCtxt, MacResult, MacEager};
|
||||||
|
use syntax::ext::build::AstBuilder;
|
||||||
|
use syntax::parse::parser::{Parser, PathParsingMode};
|
||||||
|
use syntax::parse::PResult;
|
||||||
|
use syntax::parse::token::Token;
|
||||||
|
|
||||||
|
fn get_paths<'a>(parser: &mut Parser<'a>) -> PResult<'a, Vec<Path>> {
|
||||||
|
if parser.eat(&Token::Eof) {
|
||||||
|
return Ok(vec![]);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut results = Vec::new();
|
||||||
|
loop {
|
||||||
|
results.push(try!(parser.parse_path(PathParsingMode::NoTypesAllowed)));
|
||||||
|
if !parser.eat(&Token::Comma) {
|
||||||
|
try!(parser.expect(&Token::Eof));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(results)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn routes_macro(ecx: &mut ExtCtxt, sp: Span, args: &[TokenTree])
|
||||||
|
-> Box<MacResult + 'static> {
|
||||||
|
let mut parser = ecx.new_parser_from_tts(args);
|
||||||
|
let mut paths = get_paths(&mut parser).unwrap_or_else(|mut e| {
|
||||||
|
e.emit();
|
||||||
|
vec![]
|
||||||
|
});
|
||||||
|
|
||||||
|
// Prefix each path terminator with STRUCT_PREFIX.
|
||||||
|
for p in &mut paths {
|
||||||
|
let last = p.segments.len() - 1;
|
||||||
|
let last_seg = &mut p.segments[last];
|
||||||
|
let new_ident = prepend_ident(STRUCT_PREFIX, &last_seg.identifier);
|
||||||
|
last_seg.identifier = new_ident;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build up the P<Expr> for each &path.
|
||||||
|
let path_exprs = paths.iter().map(|p| { quote_expr!(ecx, &$p) }).collect();
|
||||||
|
MacEager::expr(ecx.expr_vec_slice(sp, path_exprs))
|
||||||
|
}
|
Loading…
Reference in New Issue