Add `rank` to route attribute. Macrofy is_some ContentType methods.

This commit is contained in:
Sergio Benitez 2016-08-27 05:10:29 -07:00
parent 2fe13b2fe8
commit 8b99016af4
12 changed files with 111 additions and 53 deletions

View File

@ -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 = "/<name>/<age>", content = "application/json")]
fn hello(name: String, age: i8) -> JSON<String> {
fn hello(content_type: ContentType, name: String, age: i8) -> JSON<String> {
let person = Person {
name: name,
age: age,
};
println!("ContentType: {}", content_type);
JSON(serde_json::to_string(&person).unwrap())
}

View File

@ -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/<name>/<age>")]
#[GET(path = "/hello/<name>/<age>", 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]);
}

View File

@ -6,6 +6,7 @@ pub struct StaticRouteInfo {
pub path: &'static str,
pub content_type: ContentType,
pub handler: Handler,
pub rank: Option<isize>,
}
pub struct StaticCatchInfo {

View File

@ -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<Vec<Param>>);
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<Vec<Param>>) -> 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<Mime> for ContentType {

View File

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

View File

@ -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<Self, Self::Error> {
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<Self, Self::Error> {
match request.headers().get::<HyperCookie>() {
@ -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<Self, Self::Error> {
Ok(request.content_type())
}
}
impl<'r, 'c, T: FromRequest<'r, 'c>> FromRequest<'r, 'c> for Option<T> {
type Error = ();

View File

@ -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::<header::ContentType>();
hyp_ct.map_or(ContentType::any(), |ct| ContentType::from(&ct.0))

View File

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

View File

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

View File

@ -120,6 +120,7 @@ pub struct RouteParams {
pub path: KVSpanned<String>,
pub form: Option<KVSpanned<String>>,
pub content_type: KVSpanned<ContentType>,
pub rank: Option<KVSpanned<isize>>,
}
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
}
}

View File

@ -208,39 +208,43 @@ pub fn route_decorator(known_method: Option<Spanned<Method>>, 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 = &param.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(&param_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()), &param.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()), &param.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<Spanned<Method>>, 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<Spanned<Method>>, ecx: &mut ExtCtxt,
path: $path,
handler: $route_fn_name,
content_type: $content_type,
rank: $rank,
};
).unwrap();

View File

@ -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<Path>) {
}
}
pub fn option_as_expr<T: ToTokens>(ecx: &ExtCtxt, opt: &Option<T>) -> P<Expr> {
match *opt {
Some(ref item) => quote_expr!(ecx, Some($item)),
None => quote_expr!(ecx, None)
}
}
#[derive(Debug)]
pub struct SimpleArg {
pub name: String,