mirror of https://github.com/rwf2/Rocket.git
Add `rank` to route attribute. Macrofy is_some ContentType methods.
This commit is contained in:
parent
2fe13b2fe8
commit
8b99016af4
|
@ -4,7 +4,7 @@
|
||||||
extern crate rocket;
|
extern crate rocket;
|
||||||
extern crate serde_json;
|
extern crate serde_json;
|
||||||
|
|
||||||
use rocket::{Rocket, Request, Error};
|
use rocket::{Rocket, Request, Error, ContentType};
|
||||||
use rocket::response::JSON;
|
use rocket::response::JSON;
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
@ -13,13 +13,15 @@ struct Person {
|
||||||
age: i8,
|
age: i8,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FIXME: Change 'content' to 'format'. Look at 'accept' header to match.
|
||||||
#[GET(path = "/<name>/<age>", content = "application/json")]
|
#[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 {
|
let person = Person {
|
||||||
name: name,
|
name: name,
|
||||||
age: age,
|
age: age,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
println!("ContentType: {}", content_type);
|
||||||
JSON(serde_json::to_string(&person).unwrap())
|
JSON(serde_json::to_string(&person).unwrap())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,12 +9,11 @@ fn hello(name: &str, age: i8) -> String {
|
||||||
format!("Hello, {} year old named {}!", age, name)
|
format!("Hello, {} year old named {}!", age, name)
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: Add 'rank = 2'.
|
#[GET(path = "/hello/<name>/<age>", rank = "2")]
|
||||||
#[GET(path = "/hello/<name>/<age>")]
|
|
||||||
fn hi(name: &str, age: &str) -> String {
|
fn hi(name: &str, age: &str) -> String {
|
||||||
format!("Hi {}! You age ({}) is kind of funky.", name, age)
|
format!("Hi {}! You age ({}) is kind of funky.", name, age)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
Rocket::new("localhost", 8000).mount_and_launch("/", routes![hello, hi]);
|
Rocket::new("localhost", 8000).mount_and_launch("/", routes![hi, hello]);
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ pub struct StaticRouteInfo {
|
||||||
pub path: &'static str,
|
pub path: &'static str,
|
||||||
pub content_type: ContentType,
|
pub content_type: ContentType,
|
||||||
pub handler: Handler,
|
pub handler: Handler,
|
||||||
|
pub rank: Option<isize>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct StaticCatchInfo {
|
pub struct StaticCatchInfo {
|
||||||
|
|
|
@ -4,14 +4,20 @@ use response::mime::{Param};
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::borrow::Borrow;
|
use std::borrow::Borrow;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use self::TopLevel::{Text, Application};
|
|
||||||
use self::SubLevel::{Json, Html};
|
|
||||||
|
|
||||||
use router::Collider;
|
use router::Collider;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct ContentType(pub TopLevel, pub SubLevel, pub Option<Vec<Param>>);
|
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 {
|
impl ContentType {
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn new(t: TopLevel, s: SubLevel, params: Option<Vec<Param>>) -> ContentType {
|
pub fn new(t: TopLevel, s: SubLevel, params: Option<Vec<Param>>) -> ContentType {
|
||||||
|
@ -28,14 +34,6 @@ impl ContentType {
|
||||||
ContentType::of(TopLevel::Star, SubLevel::Star)
|
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 {
|
pub fn is_ext(&self) -> bool {
|
||||||
if let TopLevel::Ext(_) = self.0 {
|
if let TopLevel::Ext(_) = self.0 {
|
||||||
true
|
true
|
||||||
|
@ -46,9 +44,10 @@ impl ContentType {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_html(&self) -> bool {
|
is_some!(is_json: Application/Json);
|
||||||
self.0 == Text && self.1 == Html
|
is_some!(is_xml: Application/Xml);
|
||||||
}
|
is_some!(is_any: Star/Star);
|
||||||
|
is_some!(is_html: Application/Html);
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Into<Mime> for ContentType {
|
impl Into<Mime> for ContentType {
|
||||||
|
|
|
@ -33,6 +33,14 @@ impl Method {
|
||||||
HyperMethod::Extension(_) => None
|
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 {
|
impl FromStr for Method {
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
use request::*;
|
use request::*;
|
||||||
use method::Method;
|
use method::Method;
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
|
use content_type::ContentType;
|
||||||
|
|
||||||
pub trait FromRequest<'r, 'c>: Sized {
|
pub trait FromRequest<'r, 'c>: Sized {
|
||||||
type Error: Debug;
|
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 {
|
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> {
|
fn from_request(request: &'r Request<'c>) -> Result<Self, Self::Error> {
|
||||||
Ok(request.method)
|
Ok(request.method)
|
||||||
|
@ -25,7 +26,7 @@ impl<'r, 'c> FromRequest<'r, 'c> for Method {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'r, 'c> FromRequest<'r, 'c> for Cookies {
|
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> {
|
fn from_request(request: &'r Request<'c>) -> Result<Self, Self::Error> {
|
||||||
match request.headers().get::<HyperCookie>() {
|
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> {
|
impl<'r, 'c, T: FromRequest<'r, 'c>> FromRequest<'r, 'c> for Option<T> {
|
||||||
type Error = ();
|
type Error = ();
|
||||||
|
|
||||||
|
|
|
@ -53,6 +53,7 @@ impl<'a> Request<'a> {
|
||||||
&self.headers
|
&self.headers
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FIXME: This should be an Option. Not all requests have content types.
|
||||||
pub fn content_type(&self) -> ContentType {
|
pub fn content_type(&self) -> ContentType {
|
||||||
let hyp_ct = self.headers().get::<header::ContentType>();
|
let hyp_ct = self.headers().get::<header::ContentType>();
|
||||||
hyp_ct.map_or(ContentType::any(), |ct| ContentType::from(&ct.0))
|
hyp_ct.map_or(ContentType::any(), |ct| ContentType::from(&ct.0))
|
||||||
|
|
|
@ -15,5 +15,6 @@ macro_rules! impl_data_type_responder {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl_data_type_responder!(JSON: Application/Json);
|
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!(HTML: Text/Html);
|
||||||
impl_data_type_responder!(Plain: Text/Plain);
|
impl_data_type_responder!(Plain: Text/Plain);
|
||||||
|
|
|
@ -71,6 +71,10 @@ impl fmt::Display for Route {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
write!(f, "{} {}", Green.paint(&self.method), Blue.paint(&self.path))?;
|
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() {
|
if !self.content_type.is_any() {
|
||||||
write!(f, " {}", Yellow.paint(&self.content_type))
|
write!(f, " {}", Yellow.paint(&self.content_type))
|
||||||
} else {
|
} else {
|
||||||
|
@ -83,6 +87,10 @@ impl<'a> From<&'a StaticRouteInfo> for Route {
|
||||||
fn from(info: &'a StaticRouteInfo) -> Route {
|
fn from(info: &'a StaticRouteInfo) -> Route {
|
||||||
let mut route = Route::new(info.method, info.path, info.handler);
|
let mut route = Route::new(info.method, info.path, info.handler);
|
||||||
route.content_type = info.content_type.clone();
|
route.content_type = info.content_type.clone();
|
||||||
|
if let Some(rank) = info.rank {
|
||||||
|
route.rank = rank;
|
||||||
|
}
|
||||||
|
|
||||||
route
|
route
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -120,6 +120,7 @@ pub struct RouteParams {
|
||||||
pub path: KVSpanned<String>,
|
pub path: KVSpanned<String>,
|
||||||
pub form: Option<KVSpanned<String>>,
|
pub form: Option<KVSpanned<String>>,
|
||||||
pub content_type: KVSpanned<ContentType>,
|
pub content_type: KVSpanned<ContentType>,
|
||||||
|
pub rank: Option<KVSpanned<isize>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait RouteDecoratorExt {
|
pub trait RouteDecoratorExt {
|
||||||
|
@ -170,7 +171,7 @@ impl<'a, 'c> RouteDecoratorExt for MetaItemParser<'a, 'c> {
|
||||||
|
|
||||||
// Now grab all of the required and optional parameters.
|
// Now grab all of the required and optional parameters.
|
||||||
let req: [&'static str; 1] = ["path"];
|
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,
|
let kv_pairs = get_key_values(self.ctxt, self.meta_item.span,
|
||||||
&req, &opt, kv_params);
|
&req, &opt, kv_params);
|
||||||
|
|
||||||
|
@ -218,11 +219,27 @@ impl<'a, 'c> RouteDecoratorExt for MetaItemParser<'a, 'c> {
|
||||||
}
|
}
|
||||||
}).unwrap_or_else(|| KVSpanned::dummy(ContentType::any()));
|
}).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 {
|
RouteParams {
|
||||||
method: method,
|
method: method,
|
||||||
path: path,
|
path: path,
|
||||||
form: form,
|
form: form,
|
||||||
content_type: content_type,
|
content_type: content_type,
|
||||||
|
rank: rank
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
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)));
|
form_stmt.as_ref().map(|s| debug!("Form stmt: {:?}", stmt_to_string(s)));
|
||||||
|
|
||||||
// Generate the statements that will attempt to parse the paramaters during
|
// Generate the statements that will parse paramaters during run-time.
|
||||||
// run-time.
|
|
||||||
let mut fn_param_exprs = vec![];
|
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));
|
// Push all of the declared parameters.
|
||||||
fn_param_exprs.push(param_fn_item);
|
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);
|
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 path = &route.path.node;
|
||||||
let method = method_variant_to_expr(ecx, route.method.node);
|
let method = method_variant_to_expr(ecx, route.method.node);
|
||||||
let content_type = content_type_to_expr(ecx, &route.content_type.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,
|
let static_item = quote_item!(ecx,
|
||||||
#[allow(non_upper_case_globals)]
|
#[allow(non_upper_case_globals)]
|
||||||
|
@ -271,6 +276,7 @@ pub fn route_decorator(known_method: Option<Spanned<Method>>, ecx: &mut ExtCtxt,
|
||||||
path: $path,
|
path: $path,
|
||||||
handler: $route_fn_name,
|
handler: $route_fn_name,
|
||||||
content_type: $content_type,
|
content_type: $content_type,
|
||||||
|
rank: $rank,
|
||||||
};
|
};
|
||||||
).unwrap();
|
).unwrap();
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@ use std::ops::Deref;
|
||||||
use syntax::parse::{token};
|
use syntax::parse::{token};
|
||||||
use syntax::parse::token::Token;
|
use syntax::parse::token::Token;
|
||||||
use syntax::tokenstream::TokenTree;
|
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::ext::base::{ExtCtxt};
|
||||||
use syntax::codemap::{Span, Spanned, BytePos, DUMMY_SP};
|
use syntax::codemap::{Span, Spanned, BytePos, DUMMY_SP};
|
||||||
use syntax::ext::quote::rt::ToTokens;
|
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)]
|
#[derive(Debug)]
|
||||||
pub struct SimpleArg {
|
pub struct SimpleArg {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
|
Loading…
Reference in New Issue