From 8b99016af40952790de4f2a9f9449dfb51db3963 Mon Sep 17 00:00:00 2001 From: Sergio Benitez Date: Sat, 27 Aug 2016 05:10:29 -0700 Subject: [PATCH] Add `rank` to route attribute. Macrofy is_some ContentType methods. --- examples/content_types/src/main.rs | 6 ++- examples/hello_ranks/src/main.rs | 5 +-- lib/src/codegen.rs | 1 + lib/src/content_type.rs | 25 ++++++----- lib/src/method.rs | 8 ++++ lib/src/request/from_request.rs | 13 +++++- lib/src/request/request.rs | 1 + lib/src/response/data_type.rs | 1 + lib/src/router/route.rs | 8 ++++ macros/src/meta_item_parser.rs | 19 ++++++++- macros/src/route_decorator.rs | 68 ++++++++++++++++-------------- macros/src/utils.rs | 9 +++- 12 files changed, 111 insertions(+), 53 deletions(-) diff --git a/examples/content_types/src/main.rs b/examples/content_types/src/main.rs index ab9f6f88..df3bbb55 100644 --- a/examples/content_types/src/main.rs +++ b/examples/content_types/src/main.rs @@ -4,7 +4,7 @@ extern crate rocket; extern crate serde_json; -use rocket::{Rocket, Request, Error}; +use rocket::{Rocket, Request, Error, ContentType}; use rocket::response::JSON; #[derive(Debug, Serialize, Deserialize)] @@ -13,13 +13,15 @@ struct Person { age: i8, } +// FIXME: Change 'content' to 'format'. Look at 'accept' header to match. #[GET(path = "//", content = "application/json")] -fn hello(name: String, age: i8) -> JSON { +fn hello(content_type: ContentType, name: String, age: i8) -> JSON { let person = Person { name: name, age: age, }; + println!("ContentType: {}", content_type); JSON(serde_json::to_string(&person).unwrap()) } diff --git a/examples/hello_ranks/src/main.rs b/examples/hello_ranks/src/main.rs index 37e1824c..ea36a72f 100644 --- a/examples/hello_ranks/src/main.rs +++ b/examples/hello_ranks/src/main.rs @@ -9,12 +9,11 @@ fn hello(name: &str, age: i8) -> String { format!("Hello, {} year old named {}!", age, name) } -// FIXME: Add 'rank = 2'. -#[GET(path = "/hello//")] +#[GET(path = "/hello//", rank = "2")] fn hi(name: &str, age: &str) -> String { format!("Hi {}! You age ({}) is kind of funky.", name, age) } fn main() { - Rocket::new("localhost", 8000).mount_and_launch("/", routes![hello, hi]); + Rocket::new("localhost", 8000).mount_and_launch("/", routes![hi, hello]); } diff --git a/lib/src/codegen.rs b/lib/src/codegen.rs index ce0b275c..e48dd511 100644 --- a/lib/src/codegen.rs +++ b/lib/src/codegen.rs @@ -6,6 +6,7 @@ pub struct StaticRouteInfo { pub path: &'static str, pub content_type: ContentType, pub handler: Handler, + pub rank: Option, } pub struct StaticCatchInfo { diff --git a/lib/src/content_type.rs b/lib/src/content_type.rs index 36648afc..40641b1a 100644 --- a/lib/src/content_type.rs +++ b/lib/src/content_type.rs @@ -4,14 +4,20 @@ use response::mime::{Param}; use std::str::FromStr; use std::borrow::Borrow; use std::fmt; -use self::TopLevel::{Text, Application}; -use self::SubLevel::{Json, Html}; use router::Collider; #[derive(Debug, Clone)] pub struct ContentType(pub TopLevel, pub SubLevel, pub Option>); +macro_rules! is_some { + ($name:ident: $top:ident/$sub:ident) => { + pub fn $name(&self) -> bool { + self.0 == TopLevel::$top && self.1 == SubLevel::$sub + } + }; +} + impl ContentType { #[inline(always)] pub fn new(t: TopLevel, s: SubLevel, params: Option>) -> ContentType { @@ -28,14 +34,6 @@ impl ContentType { ContentType::of(TopLevel::Star, SubLevel::Star) } - pub fn is_json(&self) -> bool { - self.0 == Application && self.1 == Json - } - - pub fn is_any(&self) -> bool { - self.0 == TopLevel::Star && self.1 == SubLevel::Star - } - pub fn is_ext(&self) -> bool { if let TopLevel::Ext(_) = self.0 { true @@ -46,9 +44,10 @@ impl ContentType { } } - pub fn is_html(&self) -> bool { - self.0 == Text && self.1 == Html - } + is_some!(is_json: Application/Json); + is_some!(is_xml: Application/Xml); + is_some!(is_any: Star/Star); + is_some!(is_html: Application/Html); } impl Into for ContentType { diff --git a/lib/src/method.rs b/lib/src/method.rs index 0b727d1b..ffdb8c7a 100644 --- a/lib/src/method.rs +++ b/lib/src/method.rs @@ -33,6 +33,14 @@ impl Method { HyperMethod::Extension(_) => None } } + + /// Whether the method supports a payload or not. + pub fn supports_payload(&self) -> bool { + match *self { + Put | Post | Delete | Patch => true, + Get | Head | Connect | Trace | Options => false, + } + } } impl FromStr for Method { diff --git a/lib/src/request/from_request.rs b/lib/src/request/from_request.rs index d1a9608d..98a675f3 100644 --- a/lib/src/request/from_request.rs +++ b/lib/src/request/from_request.rs @@ -1,6 +1,7 @@ use request::*; use method::Method; use std::fmt::Debug; +use content_type::ContentType; pub trait FromRequest<'r, 'c>: Sized { type Error: Debug; @@ -17,7 +18,7 @@ impl<'r, 'c> FromRequest<'r, 'c> for &'r Request<'c> { } impl<'r, 'c> FromRequest<'r, 'c> for Method { - type Error = &'static str; + type Error = (); fn from_request(request: &'r Request<'c>) -> Result { Ok(request.method) @@ -25,7 +26,7 @@ impl<'r, 'c> FromRequest<'r, 'c> for Method { } impl<'r, 'c> FromRequest<'r, 'c> for Cookies { - type Error = &'static str; + type Error = (); fn from_request(request: &'r Request<'c>) -> Result { match request.headers().get::() { @@ -36,6 +37,14 @@ impl<'r, 'c> FromRequest<'r, 'c> for Cookies { } } +impl<'r, 'c> FromRequest<'r, 'c> for ContentType { + type Error = (); + + fn from_request(request: &'r Request<'c>) -> Result { + Ok(request.content_type()) + } +} + impl<'r, 'c, T: FromRequest<'r, 'c>> FromRequest<'r, 'c> for Option { type Error = (); diff --git a/lib/src/request/request.rs b/lib/src/request/request.rs index e9ed1cb8..c2e8ae6a 100644 --- a/lib/src/request/request.rs +++ b/lib/src/request/request.rs @@ -53,6 +53,7 @@ impl<'a> Request<'a> { &self.headers } + // FIXME: This should be an Option. Not all requests have content types. pub fn content_type(&self) -> ContentType { let hyp_ct = self.headers().get::(); hyp_ct.map_or(ContentType::any(), |ct| ContentType::from(&ct.0)) diff --git a/lib/src/response/data_type.rs b/lib/src/response/data_type.rs index c96ecb2c..27f4bbae 100644 --- a/lib/src/response/data_type.rs +++ b/lib/src/response/data_type.rs @@ -15,5 +15,6 @@ macro_rules! impl_data_type_responder { } impl_data_type_responder!(JSON: Application/Json); +impl_data_type_responder!(XML: Application/Xml); impl_data_type_responder!(HTML: Text/Html); impl_data_type_responder!(Plain: Text/Plain); diff --git a/lib/src/router/route.rs b/lib/src/router/route.rs index a6ca8720..cb653964 100644 --- a/lib/src/router/route.rs +++ b/lib/src/router/route.rs @@ -71,6 +71,10 @@ impl fmt::Display for Route { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{} {}", Green.paint(&self.method), Blue.paint(&self.path))?; + if self.rank > 1 { + write!(f, " [{}]", White.paint(&self.rank))?; + } + if !self.content_type.is_any() { write!(f, " {}", Yellow.paint(&self.content_type)) } else { @@ -83,6 +87,10 @@ impl<'a> From<&'a StaticRouteInfo> for Route { fn from(info: &'a StaticRouteInfo) -> Route { let mut route = Route::new(info.method, info.path, info.handler); route.content_type = info.content_type.clone(); + if let Some(rank) = info.rank { + route.rank = rank; + } + route } } diff --git a/macros/src/meta_item_parser.rs b/macros/src/meta_item_parser.rs index 046ae89e..5880bd4f 100644 --- a/macros/src/meta_item_parser.rs +++ b/macros/src/meta_item_parser.rs @@ -120,6 +120,7 @@ pub struct RouteParams { pub path: KVSpanned, pub form: Option>, pub content_type: KVSpanned, + pub rank: Option>, } pub trait RouteDecoratorExt { @@ -170,7 +171,7 @@ impl<'a, 'c> RouteDecoratorExt for MetaItemParser<'a, 'c> { // Now grab all of the required and optional parameters. let req: [&'static str; 1] = ["path"]; - let opt: [&'static str; 2] = ["form", "content"]; + let opt: [&'static str; 3] = ["form", "content", "rank"]; let kv_pairs = get_key_values(self.ctxt, self.meta_item.span, &req, &opt, kv_params); @@ -218,11 +219,27 @@ impl<'a, 'c> RouteDecoratorExt for MetaItemParser<'a, 'c> { } }).unwrap_or_else(|| KVSpanned::dummy(ContentType::any())); + let rank = kv_pairs.get("rank").and_then(|data| { + debug!("Found data: {:?}", data); + if let Ok(rank) = isize::from_str(data.node) { + if rank < 0 { + self.ctxt.span_err(data.v_span, "rank must be nonnegative"); + None + } else { + Some(data.clone().map(|_| rank)) + } + } else { + self.ctxt.span_err(data.v_span, "rank value must be an integer"); + None + } + }); + RouteParams { method: method, path: path, form: form, content_type: content_type, + rank: rank } } diff --git a/macros/src/route_decorator.rs b/macros/src/route_decorator.rs index 07c4252e..5363e2fa 100644 --- a/macros/src/route_decorator.rs +++ b/macros/src/route_decorator.rs @@ -208,39 +208,43 @@ pub fn route_decorator(known_method: Option>, ecx: &mut ExtCtxt, let form_stmt = get_form_stmt(ecx, &mut user_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 - // run-time. + // Generate the statements that will parse paramaters during run-time. let mut fn_param_exprs = vec![]; - for (i, param) in user_params.iter().enumerate() { - let ident = str_to_ident(param.as_str()); - let ty = ¶m.ty; - let param_fn_item = - if param.declared { - quote_stmt!(ecx, - let $ident: $ty = match _req.get_param($i) { - Ok(v) => v, - Err(_) => return ::rocket::Response::forward() - }; - ).unwrap() - } else { - quote_stmt!(ecx, - let $ident: $ty = match - <$ty as ::rocket::request::FromRequest>::from_request(&_req) { - Ok(v) => v, - Err(e) => { - // TODO: Add $ident and $ty to the string. - // TODO: Add some kind of loggin facility in Rocket - // to get the formatting right (IE, so it idents - // correctly). - // debug!("Failed to parse: {:?}", e); - return ::rocket::Response::forward(); - } - }; - ).unwrap() - }; - debug!("Param FN: {}", stmt_to_string(¶m_fn_item)); - fn_param_exprs.push(param_fn_item); + // Push all of the declared parameters. + for (i, param) in user_params.iter().filter(|p| p.declared).enumerate() { + let (ident, ty) = (str_to_ident(param.as_str()), ¶m.ty); + let fn_item = quote_stmt!(ecx, + let $ident: $ty = match _req.get_param($i) { + Ok(v) => v, + Err(_) => return ::rocket::Response::forward() + }; + ).unwrap(); + + debug!("Declared FN: {}", stmt_to_string(&fn_item)); + fn_param_exprs.push(fn_item); + } + + // Push all of the undeclared (FromRequest) parameters. + for param in user_params.iter().filter(|p| !p.declared) { + let (ident, ty) = (str_to_ident(param.as_str()), ¶m.ty); + let fn_item = quote_stmt!(ecx, + let $ident: $ty = match + <$ty as ::rocket::request::FromRequest>::from_request(&_req) { + Ok(v) => v, + Err(_e) => { + // TODO: Add $ident and $ty to the string. + // TODO: Add some kind of loggin facility in Rocket + // to get the formatting right (IE, so it idents + // correctly). + // debug!("Failed to parse: {:?}", e); + return ::rocket::Response::forward(); + } + }; + ).unwrap(); + + debug!("Param FN: {}", stmt_to_string(&fn_item)); + fn_param_exprs.push(fn_item); } let route_fn_name = prepend_ident(ROUTE_FN_PREFIX, &item.ident); @@ -262,6 +266,7 @@ pub fn route_decorator(known_method: Option>, ecx: &mut ExtCtxt, let path = &route.path.node; let method = method_variant_to_expr(ecx, route.method.node); let content_type = content_type_to_expr(ecx, &route.content_type.node); + let rank = option_as_expr(ecx, &route.rank); let static_item = quote_item!(ecx, #[allow(non_upper_case_globals)] @@ -271,6 +276,7 @@ pub fn route_decorator(known_method: Option>, ecx: &mut ExtCtxt, path: $path, handler: $route_fn_name, content_type: $content_type, + rank: $rank, }; ).unwrap(); diff --git a/macros/src/utils.rs b/macros/src/utils.rs index 18e1b5f2..8846ba4d 100644 --- a/macros/src/utils.rs +++ b/macros/src/utils.rs @@ -3,7 +3,7 @@ use std::ops::Deref; use syntax::parse::{token}; use syntax::parse::token::Token; use syntax::tokenstream::TokenTree; -use syntax::ast::{Path, Ident, MetaItem, MetaItemKind, LitKind, Ty, self}; +use syntax::ast::{Path, Expr, 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; @@ -227,6 +227,13 @@ pub fn prefix_paths(prefix: &str, paths: &mut Vec) { } } +pub fn option_as_expr(ecx: &ExtCtxt, opt: &Option) -> P { + match *opt { + Some(ref item) => quote_expr!(ecx, Some($item)), + None => quote_expr!(ecx, None) + } +} + #[derive(Debug)] pub struct SimpleArg { pub name: String,