diff --git a/examples/errors/Cargo.toml b/examples/errors/Cargo.toml new file mode 100644 index 00000000..7663227a --- /dev/null +++ b/examples/errors/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "errors" +version = "0.0.1" +authors = ["Sergio Benitez "] + +[dependencies] +rocket = { path = "../../lib" } +rocket_macros = { path = "../../macros" } diff --git a/examples/errors/src/main.rs b/examples/errors/src/main.rs new file mode 100644 index 00000000..a55d7793 --- /dev/null +++ b/examples/errors/src/main.rs @@ -0,0 +1,23 @@ +#![feature(plugin)] +#![plugin(rocket_macros)] + +extern crate rocket; +use rocket::Rocket; + +#[route(GET, path = "/hello//")] +fn hello(name: &str, age: i8) -> String { + format!("Hello, {} year old named {}!", age, name) +} + +#[error(code = "404")] +fn not_found() -> &'static str { + "Sorry, I couldn't find what you're looking for." +} + +fn main() { + let mut rocket = Rocket::new("localhost", 8000); + rocket.mount("/", routes![hello]); + rocket.catch(errors![not_found]); + // rocket.catch_and_launch(errors![not_found]); + rocket.launch(); +} diff --git a/lib/src/catcher.rs b/lib/src/catcher.rs new file mode 100644 index 00000000..d4252bac --- /dev/null +++ b/lib/src/catcher.rs @@ -0,0 +1,32 @@ +use handler::Handler; +use codegen::StaticCatchInfo; + +use std::fmt; +use term_painter::ToStyle; +use term_painter::Color::*; + +pub struct Catcher { + pub code: u16, + pub handler: Handler, +} + +impl Catcher { + pub fn new(code: u16, handler: Handler) -> Catcher { + Catcher { + code: code, + handler: handler, + } + } +} + +impl<'a> From<&'a StaticCatchInfo> for Catcher { + fn from(info: &'a StaticCatchInfo) -> Catcher { + Catcher::new(info.code, info.handler) + } +} + +impl fmt::Display for Catcher { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{} {}", Blue.paint(&self.code), Blue.paint("catcher.")) + } +} diff --git a/lib/src/codegen.rs b/lib/src/codegen.rs index 35184aaa..585a3fb9 100644 --- a/lib/src/codegen.rs +++ b/lib/src/codegen.rs @@ -7,3 +7,8 @@ pub struct StaticRouteInfo { pub handler: Handler } +pub struct StaticCatchInfo { + pub code: u16, + pub handler: Handler +} + diff --git a/lib/src/lib.rs b/lib/src/lib.rs index 06647a4c..2f6c0a9c 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -10,6 +10,7 @@ mod param; mod router; mod rocket; mod codegen; +mod catcher; pub mod form; pub mod request; @@ -21,12 +22,13 @@ pub mod handler { pub type Handler = for<'r> fn(Request<'r>) -> Response<'r>; } -pub use codegen::StaticRouteInfo; +pub use codegen::{StaticRouteInfo, StaticCatchInfo}; pub use request::Request; pub use method::Method; pub use response::{Response, Responder}; pub use error::Error; pub use param::FromParam; pub use router::{Router, Route}; +pub use catcher::Catcher; pub use rocket::Rocket; pub use handler::Handler; diff --git a/lib/src/rocket.rs b/lib/src/rocket.rs index de4754d9..86a91564 100644 --- a/lib/src/rocket.rs +++ b/lib/src/rocket.rs @@ -13,7 +13,8 @@ use hyper::server::Handler as HyperHandler; pub struct Rocket { address: &'static str, port: isize, - router: Router + router: Router, + catchers: Vec, } impl HyperHandler for Rocket { @@ -52,7 +53,8 @@ impl Rocket { Rocket { address: address, port: port, - router: Router::new() + router: Router::new(), + catchers: Vec::new(), } } @@ -69,6 +71,16 @@ impl Rocket { self } + pub fn catch(&mut self, catchers: Vec) -> &mut Self { + println!("👾 {}:", Magenta.paint("Catchers")); + for catcher in catchers { + println!("\t* {}", catcher); + self.catchers.push(catcher); + } + + self + } + pub fn launch(self) { if self.router.has_collisions() { println!("{}", Yellow.paint("Warning: route collisions detected!")); diff --git a/macros/src/derive_form.rs b/macros/src/derive_form.rs index f22c9efa..63c72df5 100644 --- a/macros/src/derive_form.rs +++ b/macros/src/derive_form.rs @@ -97,9 +97,8 @@ pub fn from_form_derive(ecx: &mut ExtCtxt, span: Span, meta_item: &MetaItem, 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 { + // Check that we specified the methods to the argument correctly. let arg = if substr.nonself_args.len() == 1 { &substr.nonself_args[0] } else { @@ -110,6 +109,7 @@ fn from_form_substructure(cx: &mut ExtCtxt, trait_span: Span, substr: &Substruct debug!("argument is: {:?}", arg); + // Ensure the the fields are from a 'StaticStruct' and extract them. let fields = match substr.fields { &StaticStruct(var_data, _) => match var_data { &VariantData::Struct(ref fields, _) => fields, @@ -118,6 +118,7 @@ fn from_form_substructure(cx: &mut ExtCtxt, trait_span: Span, substr: &Substruct _ => cx.span_bug(trait_span, "impossible substructure in `from_form`") }; + // Create a vector of (ident, type) pairs, one for each field in struct. let mut fields_and_types = vec![]; for field in fields { let ident = match field.node.ident() { @@ -131,10 +132,12 @@ fn from_form_substructure(cx: &mut ExtCtxt, trait_span: Span, substr: &Substruct debug!("Fields and types: {:?}", fields_and_types); let mut stmts = Vec::new(); + // The thing to do when we wish to exit with an error. let return_err_stmt = quote_tokens!(cx, return Err(::rocket::Error::BadParse) ); + // Generating the code that checks that the number of fields is correct. let num_fields = fields_and_types.len(); let initial_block = quote_block!(cx, { let mut items = [("", ""); $num_fields]; @@ -146,12 +149,16 @@ fn from_form_substructure(cx: &mut ExtCtxt, trait_span: Span, substr: &Substruct stmts.extend(initial_block.unwrap().stmts); + // Generate the let bindings for parameters that will be unwrapped and + // placed into the final struct for &(ref ident, ref ty) in &fields_and_types { stmts.push(quote_stmt!(cx, let mut $ident: ::std::option::Option<$ty> = None; ).unwrap()); } + // Generating an arm for each struct field. This matches against the key and + // tries to parse the value according to the type. let mut arms = vec![]; for &(ref ident, _) in &fields_and_types { let ident_string = ident.to_string(); @@ -164,6 +171,7 @@ fn from_form_substructure(cx: &mut ExtCtxt, trait_span: Span, substr: &Substruct )); } + // The actual match statement. Uses the $arms generated above. stmts.push(quote_stmt!(cx, for &(k, v) in &items { match k { @@ -173,6 +181,8 @@ fn from_form_substructure(cx: &mut ExtCtxt, trait_span: Span, substr: &Substruct } ).unwrap()); + // This looks complicated but just generates the boolean condition checking + // that each parameter actually is Some(), IE, had a key/value and parsed. let mut failure_conditions = vec![]; for (i, &(ref ident, _)) in (&fields_and_types).iter().enumerate() { if i > 0 { @@ -182,6 +192,7 @@ fn from_form_substructure(cx: &mut ExtCtxt, trait_span: Span, substr: &Substruct } } + // The fields of the struct, which are just the let bindings declared above. let mut result_fields = vec![]; for &(ref ident, _) in &fields_and_types { result_fields.push(quote_tokens!(cx, @@ -189,6 +200,8 @@ fn from_form_substructure(cx: &mut ExtCtxt, trait_span: Span, substr: &Substruct )); } + // The final block: check the error conditions, and if all is well, return + // the structure. let self_ident = substr.type_ident; let final_block = quote_block!(cx, { if $failure_conditions { @@ -202,6 +215,5 @@ fn from_form_substructure(cx: &mut ExtCtxt, trait_span: Span, substr: &Substruct 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/error_decorator.rs b/macros/src/error_decorator.rs new file mode 100644 index 00000000..596637db --- /dev/null +++ b/macros/src/error_decorator.rs @@ -0,0 +1,82 @@ +use utils::*; +use super::{CATCH_STRUCT_PREFIX, CATCH_FN_PREFIX}; + +use route_decorator::get_fn_decl; + +use syntax::codemap::{Span}; +use syntax::ast::{MetaItem}; +use syntax::ext::base::{Annotatable, ExtCtxt}; +use syntax::print::pprust::{item_to_string}; + +#[allow(dead_code)] +const DEBUG: bool = true; + +#[derive(Debug)] +struct Params { + code: KVSpanned, +} + +fn get_error_params(ecx: &mut ExtCtxt, meta_item: &MetaItem) -> Params { + assert_meta_item_list(ecx, meta_item, "error"); + + // Ensure we can unwrap the k = v params. + let params = meta_item.node.get_list_items().unwrap(); + + // Now grab all of the required and optional parameters. + let req: [&'static str; 1] = ["code"]; + let kv_pairs = get_key_values(ecx, meta_item.span, &req, &[], &*params); + + // Ensure we have a code, just to keep parsing and generating errors. + let code = kv_pairs.get("code").map_or(dummy_kvspan(404), |c| { + let numeric_code = match c.node.parse() { + Ok(n) => n, + Err(_) => { + let msg = "Error codes must be integer strings. (e.g. \"404\")"; + ecx.span_err(c.v_span, msg); + 404 + } + }; + + if numeric_code < 100 || numeric_code > 599 { + ecx.span_err(c.v_span, "Error codes must be >= 100 and <= 599."); + } + + c.clone().map(|_| { numeric_code }) + }); + + Params { + code: code, + } +} + +pub fn error_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 error_params = get_error_params(ecx, meta_item); + debug!("Error parameters are: {:?}", error_params); + + let fn_name = item.ident; + let catch_fn_name = prepend_ident(CATCH_FN_PREFIX, &item.ident); + let catch_fn_item = quote_item!(ecx, + fn $catch_fn_name<'rocket>(_req: rocket::Request<'rocket>) + -> rocket::Response<'rocket> { + // TODO: Figure out what type signature of catcher should be. + let result = $fn_name(); + rocket::Response::new(result) + } + ).unwrap(); + + debug!("{}", item_to_string(&catch_fn_item)); + push(Annotatable::Item(catch_fn_item)); + + let struct_name = prepend_ident(CATCH_STRUCT_PREFIX, &item.ident); + let catch_code = error_params.code.node; + push(Annotatable::Item(quote_item!(ecx, + #[allow(non_upper_case_globals)] + pub static $struct_name: rocket::StaticCatchInfo = rocket::StaticCatchInfo { + code: $catch_code, + handler: $catch_fn_name + }; + ).unwrap())); +} + diff --git a/macros/src/errors_macro.rs b/macros/src/errors_macro.rs new file mode 100644 index 00000000..433690f8 --- /dev/null +++ b/macros/src/errors_macro.rs @@ -0,0 +1,32 @@ +use super::{CATCH_STRUCT_PREFIX}; +use utils::*; +use syntax::codemap::Span; +use syntax::ast::{TokenTree, Expr}; +use syntax::ext::base::{ExtCtxt, MacResult, MacEager}; +use syntax::parse::token::Token; +use syntax::ptr::P; + +#[allow(dead_code)] +const DEBUG: bool = false; + +pub fn errors_macro(ecx: &mut ExtCtxt, _sp: Span, args: &[TokenTree]) + -> Box { + let mut parser = ecx.new_parser_from_tts(args); + let mut paths = parse_paths(&mut parser).unwrap_or_else(|mut e| { + e.emit(); + vec![] + }); + + // Prefix each path terminator + prefix_paths(CATCH_STRUCT_PREFIX, &mut paths); + + // Build up the P for each path. + let path_exprs: Vec> = paths.iter().map(|p| { + quote_expr!(ecx, rocket::Catcher::from(&$p)) + }).collect(); + + // Now put them all in one vector and return the thing. + let path_list = token_separate(ecx, &path_exprs, Token::Comma); + let output = quote_expr!(ecx, vec![$path_list]).unwrap(); + MacEager::expr(P(output)) +} diff --git a/macros/src/lib.rs b/macros/src/lib.rs index 9cfd32af..225e2a30 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -9,7 +9,9 @@ extern crate rocket; #[macro_use] mod utils; mod routes_macro; +mod errors_macro; mod route_decorator; +mod error_decorator; mod derive_form; use rustc_plugin::Registry; @@ -17,17 +19,24 @@ use syntax::ext::base::SyntaxExtension; use syntax::parse::token::intern; use routes_macro::routes_macro; +use errors_macro::errors_macro; use route_decorator::route_decorator; +use error_decorator::error_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_"; +const ROUTE_STRUCT_PREFIX: &'static str = "static_rocket_route_info_for_"; +const CATCH_STRUCT_PREFIX: &'static str = "static_rocket_catch_info_for_"; +const ROUTE_FN_PREFIX: &'static str = "rocket_route_fn_"; +const CATCH_FN_PREFIX: &'static str = "rocket_catch_fn_"; #[plugin_registrar] pub fn plugin_registrar(reg: &mut Registry) { reg.register_syntax_extension(intern("route"), SyntaxExtension::MultiDecorator(Box::new(route_decorator))); + reg.register_syntax_extension(intern("error"), + SyntaxExtension::MultiDecorator(Box::new(error_decorator))); reg.register_syntax_extension(intern("derive_FromForm"), SyntaxExtension::MultiDecorator(Box::new(from_form_derive))); reg.register_macro("routes", routes_macro); + reg.register_macro("errors", errors_macro); } diff --git a/macros/src/route_decorator.rs b/macros/src/route_decorator.rs index 19883b45..ede8d492 100644 --- a/macros/src/route_decorator.rs +++ b/macros/src/route_decorator.rs @@ -1,4 +1,4 @@ -use super::{STRUCT_PREFIX, FN_PREFIX}; +use super::{ROUTE_STRUCT_PREFIX, ROUTE_FN_PREFIX}; use utils::*; use std::str::FromStr; @@ -37,7 +37,7 @@ fn bad_method_err(ecx: &mut ExtCtxt, dec_sp: Span, message: &str) -> Method { Method::Get } -fn get_fn_decl<'a>(ecx: &mut ExtCtxt, sp: Span, annotated: &'a Annotatable) +pub fn get_fn_decl<'a>(ecx: &mut ExtCtxt, sp: Span, annotated: &'a Annotatable) -> (&'a P, Spanned<&'a FnDecl>) { // `annotated` is the AST object for the annotated item. let item: &P = match annotated { @@ -56,13 +56,10 @@ fn get_fn_decl<'a>(ecx: &mut ExtCtxt, sp: Span, annotated: &'a Annotatable) 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> = match meta_item.node { - MetaItemKind::List(_, ref params) => params, - _ => ecx.span_fatal(meta_item.span, - "Incorrect use of macro. correct form is: #[route(...)]"), - }; + assert_meta_item_list(ecx, meta_item, "error"); // Ensure we can unwrap the k = v params. + let params = meta_item.node.get_list_items().unwrap(); 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."); @@ -371,7 +368,7 @@ pub fn route_decorator(ecx: &mut ExtCtxt, sp: Span, meta_item: &MetaItem, } debug!("Final Params: {:?}", fn_params); - let route_fn_name = prepend_ident(FN_PREFIX, &item.ident); + let route_fn_name = prepend_ident(ROUTE_FN_PREFIX, &item.ident); let fn_name = item.ident; let route_fn_item = quote_item!(ecx, fn $route_fn_name<'rocket>(_req: rocket::Request<'rocket>) @@ -386,7 +383,7 @@ pub fn route_decorator(ecx: &mut ExtCtxt, sp: Span, meta_item: &MetaItem, debug!("{}", item_to_string(&route_fn_item)); push(Annotatable::Item(route_fn_item)); - let struct_name = prepend_ident(STRUCT_PREFIX, &item.ident); + let struct_name = prepend_ident(ROUTE_STRUCT_PREFIX, &item.ident); let path = route_params.path.node; let method = method_variant_to_expr(ecx, route_params.method.node); push(Annotatable::Item(quote_item!(ecx, diff --git a/macros/src/routes_macro.rs b/macros/src/routes_macro.rs index add5a5ad..47018d3c 100644 --- a/macros/src/routes_macro.rs +++ b/macros/src/routes_macro.rs @@ -1,56 +1,30 @@ -use super::{STRUCT_PREFIX}; -use utils::{prepend_ident, token_separate}; +use super::{ROUTE_STRUCT_PREFIX}; +use utils::*; use syntax::codemap::Span; -use syntax::ast::{Path, TokenTree, Expr}; +use syntax::ast::{TokenTree, Expr}; 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; use syntax::ptr::P; +#[allow(dead_code)] const DEBUG: bool = false; -fn get_paths<'a>(parser: &mut Parser<'a>) -> PResult<'a, Vec> { - 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 { let mut parser = ecx.new_parser_from_tts(args); - let mut paths = get_paths(&mut parser).unwrap_or_else(|mut e| { + let mut paths = parse_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; - } + // Prefix each path terminator. + prefix_paths(ROUTE_STRUCT_PREFIX, &mut paths); - debug!("Found paths: {:?}", paths); // Build up the P for each path. let path_exprs: Vec> = paths.iter().map(|p| { quote_expr!(ecx, rocket::Route::from(&$p)) }).collect(); - debug!("Path Exprs: {:?}", path_exprs); // Now put them all in one vector and return the thing. let path_list = token_separate(ecx, &path_exprs, Token::Comma); let output = quote_expr!(ecx, vec![$path_list]).unwrap(); diff --git a/macros/src/utils.rs b/macros/src/utils.rs index 3aac043a..3388e0ad 100644 --- a/macros/src/utils.rs +++ b/macros/src/utils.rs @@ -1,22 +1,15 @@ use syntax::parse::{token}; use syntax::parse::token::Token; -use syntax::ast::{Ident, MetaItem, MetaItemKind, LitKind, TokenTree}; +use syntax::ast::{Path, Ident, MetaItem, MetaItemKind, LitKind, TokenTree}; use syntax::ext::base::{ExtCtxt}; use syntax::codemap::{Span, Spanned, BytePos, DUMMY_SP}; use syntax::ext::quote::rt::ToTokens; +use syntax::parse::PResult; +use syntax::parse::parser::{PathParsingMode, Parser}; use syntax::ptr::P; use std::collections::{HashSet, HashMap}; -// macro_rules! debug { -// ($session:expr, $span:expr, $($message:tt)*) => ({ -// if cfg!(debug) { -// span_note!($session, $span, "{}:{}", file!(), line!()); -// span_note!($session, $span, $($message)*); -// } -// }) -// } - macro_rules! debug { ($($message:tt)*) => ({ if DEBUG { @@ -153,6 +146,57 @@ pub fn token_separate(ecx: &ExtCtxt, things: &Vec, output } +pub fn assert_meta_item_list(ecx: &ExtCtxt, meta_item: &MetaItem, s: &str) { + if !meta_item.node.is_list() { + let msg = format!("Incorrect use of macro. Expected: #[{}(...)]", s); + ecx.span_fatal(meta_item.span, msg.as_str()); + } +} + +pub trait MetaItemExt { + fn is_list(&self) -> bool; + fn get_list_items(&self) -> Option<&Vec>>; +} + +impl MetaItemExt for MetaItemKind { + fn is_list(&self) -> bool { + self.get_list_items().is_some() + } + + fn get_list_items(&self) -> Option<&Vec>> { + match self { + &MetaItemKind::List(_, ref params) => Some(params), + _ => None + } + } +} + +pub fn parse_paths<'a>(parser: &mut Parser<'a>) -> PResult<'a, Vec> { + 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 prefix_paths(prefix: &str, paths: &mut Vec) { + for p in paths { + let last = p.segments.len() - 1; + let last_seg = &mut p.segments[last]; + let new_ident = prepend_ident(prefix, &last_seg.identifier); + last_seg.identifier = new_ident; + } +} + // pub fn find_value_for(key: &str, kv_params: &[P]) -> Option { // for param in kv_params { // if let MetaItemKind::NameValue(ref name, ref value) = param.node {