diff --git a/lib/src/request.rs b/lib/src/request.rs index 0915fecc..17d0ed7e 100644 --- a/lib/src/request.rs +++ b/lib/src/request.rs @@ -4,7 +4,7 @@ use method::Method; pub use hyper::server::Request as HyperRequest; -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct Request<'a> { params: Option>, pub method: Method, diff --git a/lib/src/rocket.rs b/lib/src/rocket.rs index f2be3623..eacb51f6 100644 --- a/lib/src/rocket.rs +++ b/lib/src/rocket.rs @@ -93,12 +93,12 @@ impl Rocket { return handle_not_found(res); } - // Okay, we've got a route. Unwrap it, generate a request, and try to - // dispatch. - println!("\t=> {}", Magenta.paint("Dispatching request.")); + // Okay, we've got a route. Unwrap it, generate a request, and dispatch. let route = route.unwrap(); let params = route.get_params(uri); let request = Request::new(method, uri, Some(params), &buf); + + println!("\t=> {}", Magenta.paint("Dispatching request.")); let outcome = (route.handler)(request).respond(res); // TODO: keep trying lower ranked routes before dispatching a not found diff --git a/lib/src/router/uri.rs b/lib/src/router/uri.rs index 0a826a31..ec020b04 100644 --- a/lib/src/router/uri.rs +++ b/lib/src/router/uri.rs @@ -5,15 +5,26 @@ use std::fmt::{self, Write}; #[derive(Debug, Clone, PartialEq, Eq)] pub struct URI<'a> { + uri: &'a str, path: &'a str, + query: Option<&'a str>, segment_count: Cell> } impl<'a> URI<'a> { - pub fn new + ?Sized>(path: &'a T) -> URI<'a> { + pub fn new + ?Sized>(uri: &'a T) -> URI<'a> { + let uri = uri.as_ref(); + + let (path, query) = match uri.find('?') { + Some(index) => (&uri[..index], Some(&uri[index..])), + None => (uri, None) + }; + URI { segment_count: Cell::new(None), - path: path.as_ref(), + uri: uri, + path: path, + query: query, } } @@ -30,14 +41,14 @@ impl<'a> URI<'a> { } pub fn as_str(&self) -> &'a str { - self.path + self.uri } } impl<'a> fmt::Display for URI<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let mut last = '\0'; - for c in self.path.chars() { + for c in self.uri.chars() { if !(c == '/' && last == '/') { f.write_char(c)?; } last = c; } @@ -50,7 +61,7 @@ unsafe impl<'a> Sync for URI<'a> { /* It's safe! */ } #[derive(Debug, Clone, PartialEq, Eq)] pub struct URIBuf { - path: String, + uri: String, segment_count: Cell> } @@ -65,25 +76,25 @@ impl URIBuf { } pub fn segments(&self) -> Segments { - Segments(self.path.as_str()) + self.as_uri_uncached().segments() } fn as_uri_uncached(&self) -> URI { - URI::new(self.path.as_str()) + URI::new(self.uri.as_str()) } pub fn as_uri(&self) -> URI { - let mut uri = URI::new(self.path.as_str()); + let mut uri = URI::new(self.uri.as_str()); uri.segment_count = self.segment_count.clone(); uri } pub fn as_str(&self) -> &str { - self.path.as_str() + self.uri.as_str() } pub fn to_string(&self) -> String { - self.path.clone() + self.uri.clone() } } @@ -96,19 +107,19 @@ impl fmt::Display for URIBuf { } impl From for URIBuf { - fn from(path: String) -> URIBuf { + fn from(uri: String) -> URIBuf { URIBuf { segment_count: Cell::new(None), - path: path, + uri: uri, } } } impl<'a> From<&'a str> for URIBuf { - fn from(path: &'a str) -> URIBuf { + fn from(uri: &'a str) -> URIBuf { URIBuf { segment_count: Cell::new(None), - path: path.to_string(), + uri: uri.to_string(), } } } diff --git a/macros/src/error_decorator.rs b/macros/src/error_decorator.rs index b1de4e3e..3a42aa3d 100644 --- a/macros/src/error_decorator.rs +++ b/macros/src/error_decorator.rs @@ -17,17 +17,15 @@ struct Params { } 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(); + // Ensure we've been supplied with a k = v meta item. Error out if not. + let params = meta_item.expect_list(ecx, "Bad use. Expected: #[error(...)]"); // 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 code = kv_pairs.get("code").map_or(KVSpanned::dummy(404), |c| { let numeric_code = match c.node.parse() { Ok(n) => n, Err(_) => { diff --git a/macros/src/route_decorator.rs b/macros/src/route_decorator.rs index c1bdd1a3..0573a524 100644 --- a/macros/src/route_decorator.rs +++ b/macros/src/route_decorator.rs @@ -2,13 +2,10 @@ use super::{ROUTE_STRUCT_PREFIX, ROUTE_FN_PREFIX}; use utils::*; use std::str::FromStr; -use std::collections::HashSet; +use std::collections::HashMap; -use syntax::ext::quote::rt::ToTokens; use syntax::codemap::{Span, BytePos, /* DUMMY_SP, */ Spanned}; -use syntax::tokenstream::TokenTree; -use syntax::ast::{Ident, PatKind, Stmt}; -use syntax::ast::{Item, Expr, ItemKind, MetaItem, MetaItemKind, FnDecl, Ty}; +use syntax::ast::{Stmt, Item, Expr, ItemKind, MetaItem, MetaItemKind, FnDecl}; use syntax::ext::base::{Annotatable, ExtCtxt}; use syntax::ptr::P; use syntax::print::pprust::{item_to_string, stmt_to_string}; @@ -54,12 +51,10 @@ pub fn get_fn_decl<'a>(ecx: &mut ExtCtxt, sp: Span, annotated: &'a Annotatable) (item, wrap_span(&*fn_decl, item.span)) } -fn get_route_params(ecx: &mut ExtCtxt, meta_item: &MetaItem) -> Params { - // First, check that the macro was used in the #[route(a, b, ..)] form. - 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(); +// Parses the MetaItem derived from the route(...) macro. +fn parse_route(ecx: &mut ExtCtxt, meta_item: &MetaItem) -> Params { + // Ensure we've been supplied with a k = v meta item. Error out if not. + let params = meta_item.expect_list(ecx, "Bad use. Expected: #[route(...)]"); 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."); @@ -89,7 +84,7 @@ fn get_route_params(ecx: &mut ExtCtxt, meta_item: &MetaItem) -> Params { 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(dummy_kvspan("/".to_string()), |s| { + let path = kv_pairs.get("path").map_or(KVSpanned::dummy("/".to_string()), |s| { s.clone().map(String::from) }); @@ -107,7 +102,7 @@ fn get_route_params(ecx: &mut ExtCtxt, meta_item: &MetaItem) -> Params { .emit(); } - if f.node.chars().filter(|c| *c == '<').count() != 1 { + if f.node.chars().filter(|c| *c == '<' || *c == '>').count() != 2 { ecx.span_err(f.p_span, "`form` must contain exactly one parameter"); } @@ -121,29 +116,11 @@ fn get_route_params(ecx: &mut ExtCtxt, meta_item: &MetaItem) -> Params { } } -// 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 { - 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), - } -} // TODO: Put something like this in the library. Maybe as an iterator? -pub fn gen_params_hashset<'a>(ecx: &ExtCtxt, params: &Spanned<&'a str>) - -> HashSet<&'a str> { - let mut seen = HashSet::new(); +pub fn extract_params<'a>(ecx: &ExtCtxt, params: &Spanned<&'a str>) + -> Vec> { + let mut output_params = vec![]; let bad_match_err = "Parameter string is malformed."; let mut start = 0; @@ -163,13 +140,7 @@ pub fn gen_params_hashset<'a>(ecx: &ExtCtxt, params: &Spanned<&'a str>) if i > start + 1 { let param_name = ¶ms.node[(start + 1)..i]; - if seen.contains(param_name) { - let msg = format!("\"{}\" appears more than once in \ - the parameter string.", param_name); - ecx.span_err(param_span, msg.as_str()); - } - - seen.insert(param_name); + output_params.push(wrap_span(param_name, param_span)) } else { ecx.span_err(param_span, "Parameter names cannot be empty."); } @@ -180,137 +151,91 @@ pub fn gen_params_hashset<'a>(ecx: &ExtCtxt, params: &Spanned<&'a str>) } } - seen + output_params } -#[derive(Debug)] -struct SimpleArg { - name: String, - ty: P, - span: Span -} - -pub fn gen_kv_string_hashset<'a>(ecx: &ExtCtxt, params: &'a KVSpanned) - -> HashSet<&'a str> { +pub fn extract_params_from_kv<'a>(ecx: &ExtCtxt, params: &'a KVSpanned) + -> Vec> { let mut param_span = params.v_span; param_span.lo = params.v_span.lo + BytePos(1); - let params = Spanned { + extract_params(ecx, &Spanned { span: param_span, node: &*params.node - }; - - gen_params_hashset(ecx, ¶ms) + }) } -impl SimpleArg { - fn new(name: T, ty: P, sp: Span) -> SimpleArg { - SimpleArg { name: name.to_string(), ty: ty, span: sp } - } - - fn as_str(&self) -> &str { - self.name.as_str() - } -} - -impl ToTokens for SimpleArg { - fn to_tokens(&self, cx: &ExtCtxt) -> Vec { - str_to_ident(self.as_str()).to_tokens(cx) - } -} - -fn get_fn_params<'a>(ecx: &ExtCtxt, dec_span: Span, path: &'a KVSpanned, - fn_decl: &Spanned<&FnDecl>, mut external: HashSet<&'a str>) - -> Vec { +fn get_fn_params<'a, T: Iterator>>(ecx: &ExtCtxt, + declared_params: T, fn_decl: &Spanned<&FnDecl>) -> Vec { debug!("FUNCTION: {:?}", fn_decl); - let mut path_params = gen_kv_string_hashset(ecx, &path); - // Ensure that there are no collisions between path parameters and external - // params. If there are, get rid of one of them so we don't double error. - let new_external = external.clone(); - for param in path_params.intersection(&new_external) { - let msg = format!("'{}' appears as a parameter more than once.", param); - external.remove(param); - ecx.span_err(dec_span, msg.as_str()); + // First, check that all of the parameters are unique. + let mut seen: HashMap<&str, &Spanned<&str>> = HashMap::new(); + for item in declared_params { + if seen.contains_key(item.node) { + let msg = format!( + "\"{}\" was declared as a parameter more than once.", item.node); + ecx.span_err(item.span, msg.as_str()); + } else { + seen.insert(item.node, item); + } } - // Ensure every param in the function declaration is in `path`. Also add - // each param name in the declaration to the result vector. + // Ensure every param in the function declaration was declared by the user. let mut result = vec![]; for arg in &fn_decl.node.inputs { - let ident: &Ident = match arg.pat.node { - PatKind::Ident(_, ref ident, _) => &ident.node, - _ => { - ecx.span_err(arg.pat.span, "Expected an identifier."); - return result - } - }; - - let name = ident.to_string(); - if !path_params.remove(name.as_str()) && !external.remove(name.as_str()) { - let msg1 = format!("'{}' appears in the function declaration...", name); - let msg2 = format!("...but does not appear as a parameter \ - (e.g., <{}>).", name); - ecx.span_err(arg.pat.span, msg1.as_str()); - ecx.span_err(dec_span, msg2.as_str()); + let name = arg.pat.expect_ident(ecx, "Expected identifier."); + if seen.remove(&*name.to_string()).is_none() { + let msg = format!("'{}' appears in the function declaration \ + but does not appear as a parameter in the attribute.", name); + ecx.span_err(arg.pat.span, msg.as_str()); } result.push(SimpleArg::new(name, arg.ty.clone(), arg.pat.span)); } - // Ensure every param in `path` and `exclude` is in the function declaration. - for item in path_params { - let msg = format!("'{}' appears in the path string...", item); - ecx.span_err(path.v_span, msg.as_str()); - ecx.span_err(fn_decl.span, "...but does not appear in the function \ - declration."); - } - - // FIXME: need the spans for the external params - for item in external { - let msg = format!("'{}' appears as a parameter...", item); - ecx.span_err(dec_span, msg.as_str()); - ecx.span_err(fn_decl.span, "...but does not appear in the function \ - declaration."); + // Ensure every declared parameter is in the function declaration. + for item in seen.values() { + let msg = format!("'{}' was declared in the attribute...", item.node); + ecx.span_err(item.span, msg.as_str()); + ecx.span_err(fn_decl.span, "...but does not appear in the function \ + declaration."); } result } fn get_form_stmt(ecx: &ExtCtxt, fn_args: &mut Vec, - form_params: &HashSet<&str>) -> Option { + form_params: &[Spanned<&str>]) -> Option { if form_params.len() < 1 { return None } else if form_params.len() > 1 { panic!("Allowed more than 1 form parameter!"); } - - let param_ty; - let param_ident; - let param_name = form_params.iter().next().unwrap(); - - { + let param_name = &form_params[0].node; + let (param_ty, param_ident) = { // Get the first item in the hashset, i.e., the form params variable name. let fn_arg = fn_args.iter().filter(|a| &&*a.name == param_name).next(); if fn_arg.is_none() { // This happens when a form parameter doesn't appear in the function. + // We should have already caught this, so just return None. return None; } - param_ty = fn_arg.unwrap().ty.clone(); - param_ident = str_to_ident(param_name); - } + (fn_arg.unwrap().ty.clone(), str_to_ident(param_name)) + }; + // Remove the paramter from the function arguments. debug!("Form parameter variable: {}: {:?}", param_name, param_ty); let fn_arg_index = fn_args.iter().position(|a| &&*a.name == param_name).unwrap(); fn_args.remove(fn_arg_index); + + // The actual code we'll be inserting. quote_stmt!(ecx, - // TODO: Actually get the form parameters to pass into from_form_string. - // Alternatively, pass in some already parsed thing. 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::server_error(); }; match ::rocket::form::FromForm::from_form_string(form_string.unwrap()) { @@ -324,30 +249,50 @@ fn get_form_stmt(ecx: &ExtCtxt, fn_args: &mut Vec, ) } +// 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 { + 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), + } +} + // FIXME: Compilation fails when parameters have the same name as the function! pub fn route_decorator(ecx: &mut ExtCtxt, sp: Span, meta_item: &MetaItem, annotated: &Annotatable, push: &mut FnMut(Annotatable)) { + // Get the encompassing item and function declaration for the annotated func. let (item, fn_decl) = get_fn_decl(ecx, sp, annotated); - let route_params = get_route_params(ecx, meta_item); - // TODO: move this elsewhere - let mut external_params = HashSet::new(); - let mut form_param_hashset = HashSet::new(); - if let Some(ref form) = route_params.form { - form_param_hashset = gen_kv_string_hashset(ecx, form); - external_params.extend(&form_param_hashset); - } + // Parse and retrieve all of the parameters of the route. + let route = parse_route(ecx, meta_item); - let mut fn_params = get_fn_params(ecx, sp, &route_params.path, &fn_decl, - external_params.clone()); + // Get a list of the user declared parameters in `path` and `form`. + let path_params = extract_params_from_kv(ecx, &route.path); + let form_thing = route.form.unwrap_or_default(); // Default is empty string. + let form_params = extract_params_from_kv(ecx, &form_thing); + + // Ensure the params match the function declaration and return the params. + let all_params = path_params.iter().chain(form_params.iter()); + let mut fn_params = get_fn_params(ecx, all_params, &fn_decl); // Create a comma seperated list (token tree) of the function parameters // We pass this in to the user's function that we're wrapping. let fn_param_idents = token_separate(ecx, &fn_params, token::Comma); // Generate the statements that will attempt to parse forms during run-time. - // let form_span = route_params.form.map_or(DUMMY_SP, |f| f.span.clone()); - let form_stmt = get_form_stmt(ecx, &mut fn_params, &form_param_hashset); + // Calling this function also remove the form parameter from fn_params. + let form_stmt = get_form_stmt(ecx, &mut fn_params, &form_params); form_stmt.as_ref().map(|s| debug!("Form stmt: {:?}", stmt_to_string(s))); // Generate the statements that will attempt to parse the paramaters during @@ -384,8 +329,8 @@ pub fn route_decorator(ecx: &mut ExtCtxt, sp: Span, meta_item: &MetaItem, push(Annotatable::Item(route_fn_item)); 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); + let path = &route.path.node; + let method = method_variant_to_expr(ecx, route.method.node); push(Annotatable::Item(quote_item!(ecx, #[allow(non_upper_case_globals)] pub static $struct_name: rocket::StaticRouteInfo = rocket::StaticRouteInfo { diff --git a/macros/src/utils.rs b/macros/src/utils.rs index 5e349306..55c1d953 100644 --- a/macros/src/utils.rs +++ b/macros/src/utils.rs @@ -1,7 +1,7 @@ use syntax::parse::{token}; use syntax::parse::token::Token; use syntax::tokenstream::TokenTree; -use syntax::ast::{Path, Ident, MetaItem, MetaItemKind, LitKind}; +use syntax::ast::{Path, Ident, MetaItem, MetaItemKind, LitKind, Ty, self}; use syntax::ext::base::{ExtCtxt}; use syntax::codemap::{Span, Spanned, BytePos, DUMMY_SP}; use syntax::ext::quote::rt::ToTokens; @@ -50,22 +50,30 @@ pub fn dummy_span(t: T) -> Spanned { } } -#[inline] -pub fn dummy_kvspan(t: T) -> KVSpanned { - KVSpanned { - k_span: DUMMY_SP, - v_span: DUMMY_SP, - p_span: DUMMY_SP, - node: t, +#[derive(Debug, Clone)] +pub struct KVSpanned { + pub k_span: Span, // Span for the key. + pub v_span: Span, // Span for the value. + pub p_span: Span, // Span for the full parameter. + pub node: T // The value. +} + +impl KVSpanned { + #[inline] + pub fn dummy(t: T) -> KVSpanned { + KVSpanned { + k_span: DUMMY_SP, + v_span: DUMMY_SP, + p_span: DUMMY_SP, + node: t, + } } } -#[derive(Debug, Clone)] -pub struct KVSpanned { - pub k_span: Span, - pub v_span: Span, - pub p_span: Span, - pub node: T +impl Default for KVSpanned { + fn default() -> KVSpanned { + KVSpanned::dummy(T::default()) + } } impl ToTokens for KVSpanned { @@ -147,27 +155,38 @@ pub fn token_separate(ecx: &ExtCtxt, things: &[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>>; + fn expect_list<'a>(&'a self, ecx: &ExtCtxt, msg: &str) -> &'a Vec>; + fn expect_word<'a>(&'a self, ecx: &ExtCtxt, msg: &str) -> &'a str; } -impl MetaItemExt for MetaItemKind { - fn is_list(&self) -> bool { - self.get_list_items().is_some() +impl MetaItemExt for MetaItem { + fn expect_list<'a>(&'a self, ecx: &ExtCtxt, msg: &str) -> &'a Vec> { + match self.node { + MetaItemKind::List(_, ref params) => params, + _ => ecx.span_fatal(self.span, msg) + } } - fn get_list_items(&self) -> Option<&Vec>> { - match *self { - MetaItemKind::List(_, ref params) => Some(params), - _ => None + fn expect_word<'a>(&'a self, ecx: &ExtCtxt, msg: &str) -> &'a str { + match self.node { + MetaItemKind::Word(ref s) => &*s, + _ => ecx.span_fatal(self.span, msg) + } + } +} + +pub trait PatExt { + fn expect_ident<'a>(&'a self, ecx: &ExtCtxt, msg: &str) -> &'a Ident; +} + +impl PatExt for ast::Pat { + fn expect_ident<'a>(&'a self, ecx: &ExtCtxt, msg: &str) -> &'a Ident { + match self.node { + ast::PatKind::Ident(_, ref ident, _) => &ident.node, + _ => { + ecx.span_fatal(self.span, msg) + } } } } @@ -198,16 +217,26 @@ pub fn prefix_paths(prefix: &str, paths: &mut Vec) { } } -// 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 { -// if &**name == key { -// if let LitKind::Str(ref string, _) = value.node { -// return Some(String::from(&**string)); -// } -// } -// } -// } +#[derive(Debug)] +pub struct SimpleArg { + pub name: String, + pub ty: P, + pub span: Span +} + +impl SimpleArg { + pub fn new(name: T, ty: P, sp: Span) -> SimpleArg { + SimpleArg { name: name.to_string(), ty: ty, span: sp } + } + + pub fn as_str(&self) -> &str { + self.name.as_str() + } +} + +impl ToTokens for SimpleArg { + fn to_tokens(&self, cx: &ExtCtxt) -> Vec { + token::str_to_ident(self.as_str()).to_tokens(cx) + } +} -// None -// }