Progress on error handling. Calling them 'catchers' for now.

Added `error` decorator and `errors` macro.

The current idea is that you can have "catchers" for all valid errors code (in
range [400, 500). At the moment, catchers are just request handlers, and the
decorator expected an empty function signature for the error handler. Obviously,
this is pretty useless. Not sure on what the API should be here. But, progress.

Oh, one more thing: who should handle forwarding a request to a catcher?
Probably not the router. So, the main Rocket should?
This commit is contained in:
Sergio Benitez 2016-04-06 03:26:43 -07:00
parent 6e3d23b5f0
commit dc5ef6a421
13 changed files with 292 additions and 60 deletions

View File

@ -0,0 +1,8 @@
[package]
name = "errors"
version = "0.0.1"
authors = ["Sergio Benitez <sb@sergio.bz>"]
[dependencies]
rocket = { path = "../../lib" }
rocket_macros = { path = "../../macros" }

View File

@ -0,0 +1,23 @@
#![feature(plugin)]
#![plugin(rocket_macros)]
extern crate rocket;
use rocket::Rocket;
#[route(GET, path = "/hello/<name>/<age>")]
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();
}

32
lib/src/catcher.rs Normal file
View File

@ -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."))
}
}

View File

@ -7,3 +7,8 @@ pub struct StaticRouteInfo {
pub handler: Handler
}
pub struct StaticCatchInfo {
pub code: u16,
pub handler: Handler
}

View File

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

View File

@ -13,7 +13,8 @@ use hyper::server::Handler as HyperHandler;
pub struct Rocket {
address: &'static str,
port: isize,
router: Router
router: Router,
catchers: Vec<Catcher>,
}
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<Catcher>) -> &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!"));

View File

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

View File

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

View File

@ -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<MacResult + 'static> {
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<Expr> for each path.
let path_exprs: Vec<P<Expr>> = 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))
}

View File

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

View File

@ -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<Item>, Spanned<&'a FnDecl>) {
// `annotated` is the AST object for the annotated item.
let item: &P<Item> = 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<P<MetaItem>> = 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,

View File

@ -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<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| {
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<Expr> for each path.
let path_exprs: Vec<P<Expr>> = 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();

View File

@ -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<T: ToTokens>(ecx: &ExtCtxt, things: &Vec<T>,
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<P<MetaItem>>>;
}
impl MetaItemExt for MetaItemKind {
fn is_list(&self) -> bool {
self.get_list_items().is_some()
}
fn get_list_items(&self) -> Option<&Vec<P<MetaItem>>> {
match self {
&MetaItemKind::List(_, ref params) => Some(params),
_ => None
}
}
}
pub fn parse_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 prefix_paths(prefix: &str, paths: &mut Vec<Path>) {
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<MetaItem>]) -> Option<String> {
// for param in kv_params {
// if let MetaItemKind::NameValue(ref name, ref value) = param.node {