mirror of https://github.com/rwf2/Rocket.git
Major overhual: Request, ErrorHandler, ContentType.
This commit is contained in:
parent
2b7b733e83
commit
90d8621adf
|
@ -4,9 +4,7 @@
|
|||
extern crate rocket;
|
||||
extern crate serde_json;
|
||||
|
||||
use rocket::{Rocket, RoutingError};
|
||||
use rocket::ContentType;
|
||||
use rocket::Error;
|
||||
use rocket::{Rocket, Request, ContentType, Error};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
struct Person {
|
||||
|
@ -25,23 +23,23 @@ fn hello(name: String, age: i8) -> String {
|
|||
}
|
||||
|
||||
#[error(code = "404")]
|
||||
fn not_found(error: RoutingError) -> String {
|
||||
match error.error {
|
||||
// Error::BadMethod if !error.request.content_type.is_json() => {
|
||||
// format!("<p>This server only supports JSON requests, not '{}'.</p>",
|
||||
// error.request.data)
|
||||
// }
|
||||
Error::BadMethod => {
|
||||
fn not_found<'r>(error: Error, request: &'r Request<'r>) -> String {
|
||||
match error {
|
||||
Error::NoRoute if !request.content_type().is_json() => {
|
||||
format!("<p>This server only supports JSON requests, not '{}'.</p>",
|
||||
request.content_type())
|
||||
}
|
||||
Error::NoRoute => {
|
||||
format!("<p>Sorry, this server but '{}' is not a valid path!</p>
|
||||
<p>Try visiting /hello/<name>/<age> instead.</p>",
|
||||
error.request.uri)
|
||||
request.uri())
|
||||
}
|
||||
_ => format!("<p>Bad Request</p>"),
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let mut rocket = Rocket::new("localhost", 8000);
|
||||
let mut rocket = Rocket::new("0.0.0.0", 8000);
|
||||
rocket.mount("/hello", routes![hello]);
|
||||
rocket.catch(errors![not_found]);
|
||||
rocket.launch();
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
#![plugin(rocket_macros)]
|
||||
|
||||
extern crate rocket;
|
||||
use rocket::{Rocket, RoutingError};
|
||||
use rocket::{Rocket, Error, Request};
|
||||
|
||||
#[route(GET, path = "/hello/<name>/<age>")]
|
||||
fn hello(name: &str, age: i8) -> String {
|
||||
|
@ -10,10 +10,10 @@ fn hello(name: &str, age: i8) -> String {
|
|||
}
|
||||
|
||||
#[error(code = "404")]
|
||||
fn not_found(error: RoutingError) -> String {
|
||||
fn not_found<'r>(_error: Error, request: &'r Request<'r>) -> String {
|
||||
format!("<p>Sorry, but '{}' is not a valid path!</p>
|
||||
<p>Try visiting /hello/<name>/<age> instead.</p>",
|
||||
error.request.uri)
|
||||
request.uri)
|
||||
}
|
||||
|
||||
fn main() {
|
||||
|
|
|
@ -3,14 +3,14 @@ extern crate rocket;
|
|||
use rocket::{Rocket, Request, Response, Route};
|
||||
use rocket::Method::*;
|
||||
|
||||
fn root(req: Request) -> Response {
|
||||
fn root<'r>(req: &'r Request<'r>) -> Response<'r> {
|
||||
let name = req.get_param(0).unwrap_or("unnamed");
|
||||
Response::new(format!("Hello, {}!", name))
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn echo_url<'a>(req: Request<'a>) -> Response<'a> {
|
||||
Response::new(req.get_uri().split_at(6).1)
|
||||
fn echo_url<'a>(req: &'a Request<'a>) -> Response<'a> {
|
||||
Response::new(req.uri().as_str().split_at(6).1)
|
||||
}
|
||||
|
||||
fn main() {
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
use handler::Handler;
|
||||
use handler::ErrorHandler;
|
||||
use response::Response;
|
||||
use error::RoutingError;
|
||||
use codegen::StaticCatchInfo;
|
||||
use error::Error;
|
||||
use request::Request;
|
||||
|
||||
use std::fmt;
|
||||
use term_painter::ToStyle;
|
||||
|
@ -9,7 +10,7 @@ use term_painter::Color::*;
|
|||
|
||||
pub struct Catcher {
|
||||
pub code: u16,
|
||||
handler: Handler,
|
||||
handler: ErrorHandler,
|
||||
is_default: bool
|
||||
}
|
||||
|
||||
|
@ -18,15 +19,15 @@ pub struct Catcher {
|
|||
// interface here?
|
||||
|
||||
impl Catcher {
|
||||
pub fn new(code: u16, handler: Handler) -> Catcher {
|
||||
pub fn new(code: u16, handler: ErrorHandler) -> Catcher {
|
||||
Catcher::new_with_default(code, handler, false)
|
||||
}
|
||||
|
||||
pub fn handle<'a>(&'a self, error: RoutingError<'a>) -> Response {
|
||||
(self.handler)(error.request)
|
||||
pub fn handle<'r>(&self, error: Error, request: &'r Request<'r>) -> Response<'r> {
|
||||
(self.handler)(error, request)
|
||||
}
|
||||
|
||||
fn new_with_default(code: u16, handler: Handler, default: bool) -> Catcher {
|
||||
fn new_with_default(code: u16, handler: ErrorHandler, default: bool) -> Catcher {
|
||||
Catcher {
|
||||
code: code,
|
||||
handler: handler,
|
||||
|
@ -55,9 +56,10 @@ pub mod defaults {
|
|||
use request::Request;
|
||||
use response::{StatusCode, Response};
|
||||
use super::Catcher;
|
||||
use error::Error;
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub fn not_found(_request: Request) -> Response {
|
||||
pub fn not_found<'r>(_error: Error, _request: &'r Request<'r>) -> Response<'r> {
|
||||
Response::with_status(StatusCode::NotFound, "\
|
||||
<head>\
|
||||
<meta charset=\"utf-8\">\
|
||||
|
@ -72,7 +74,8 @@ pub mod defaults {
|
|||
")
|
||||
}
|
||||
|
||||
pub fn internal_error(_request: Request) -> Response {
|
||||
pub fn internal_error<'r>(_error: Error, _request: &'r Request<'r>)
|
||||
-> Response<'r> {
|
||||
Response::with_status(StatusCode::InternalServerError, "\
|
||||
<head>\
|
||||
<meta charset=\"utf-8\">\
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use ::{Method, Handler};
|
||||
use ::{Method, Handler, ErrorHandler};
|
||||
use content_type::ContentType;
|
||||
|
||||
pub struct StaticRouteInfo {
|
||||
|
@ -10,6 +10,6 @@ pub struct StaticRouteInfo {
|
|||
|
||||
pub struct StaticCatchInfo {
|
||||
pub code: u16,
|
||||
pub handler: Handler
|
||||
pub handler: ErrorHandler
|
||||
}
|
||||
|
||||
|
|
|
@ -1,14 +1,23 @@
|
|||
pub use mime::{Mime, TopLevel, SubLevel};
|
||||
pub use hyper::mime::{Mime, TopLevel, SubLevel};
|
||||
|
||||
use std::str::FromStr;
|
||||
use mime::{Param};
|
||||
use std::borrow::Borrow;
|
||||
use std::fmt;
|
||||
use hyper::mime::{Param};
|
||||
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>>);
|
||||
|
||||
impl ContentType {
|
||||
#[inline(always)]
|
||||
pub fn new(t: TopLevel, s: SubLevel, params: Option<Vec<Param>>) -> ContentType {
|
||||
ContentType(t, s, params)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn of(t: TopLevel, s: SubLevel) -> ContentType {
|
||||
ContentType(t, s, None)
|
||||
|
@ -20,17 +29,11 @@ impl ContentType {
|
|||
}
|
||||
|
||||
pub fn is_json(&self) -> bool {
|
||||
match *self {
|
||||
ContentType(Application, Json, _) => true,
|
||||
_ => false,
|
||||
}
|
||||
self.0 == Application && self.1 == Json
|
||||
}
|
||||
|
||||
pub fn is_any(&self) -> bool {
|
||||
match *self {
|
||||
ContentType(TopLevel::Star, SubLevel::Star, None) => true,
|
||||
_ => false,
|
||||
}
|
||||
self.0 == TopLevel::Star && self.1 == SubLevel::Star
|
||||
}
|
||||
|
||||
pub fn is_ext(&self) -> bool {
|
||||
|
@ -44,10 +47,7 @@ impl ContentType {
|
|||
}
|
||||
|
||||
pub fn is_html(&self) -> bool {
|
||||
match *self {
|
||||
ContentType(Text, Html, _) => true,
|
||||
_ => false,
|
||||
}
|
||||
self.0 == Text && self.1 == Html
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -57,6 +57,13 @@ impl Into<Mime> for ContentType {
|
|||
}
|
||||
}
|
||||
|
||||
impl<T: Borrow<Mime>> From<T> for ContentType {
|
||||
default fn from(mime: T) -> ContentType {
|
||||
let mime: Mime = mime.borrow().clone();
|
||||
ContentType::from(mime)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Mime> for ContentType {
|
||||
fn from(mime: Mime) -> ContentType {
|
||||
let params = match mime.2.len() {
|
||||
|
@ -68,11 +75,153 @@ impl From<Mime> for ContentType {
|
|||
}
|
||||
}
|
||||
|
||||
impl FromStr for ContentType {
|
||||
type Err = ();
|
||||
|
||||
fn from_str(raw: &str) -> Result<ContentType, ()> {
|
||||
let mime = Mime::from_str(raw)?;
|
||||
Ok(ContentType::from(mime))
|
||||
fn is_valid_first_char(c: char) -> bool {
|
||||
match c {
|
||||
'a'...'z' | 'A'...'Z' | '0'...'9' | '*' => true,
|
||||
_ => false
|
||||
}
|
||||
}
|
||||
|
||||
fn is_valid_char(c: char) -> bool {
|
||||
is_valid_first_char(c) || match c {
|
||||
'!' | '#' | '$' | '&' | '-' | '^' | '.' | '+' | '_' => true,
|
||||
_ => false
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for ContentType {
|
||||
type Err = &'static str;
|
||||
|
||||
fn from_str(raw: &str) -> Result<ContentType, &'static str> {
|
||||
let slash = match raw.find('/') {
|
||||
Some(i) => i,
|
||||
None => return Err("Missing / in MIME type.")
|
||||
};
|
||||
|
||||
let top_str = &raw[..slash];
|
||||
let (sub_str, rest) = match raw.find(';') {
|
||||
Some(j) => (&raw[(slash + 1)..j], Some(&raw[(j + 1)..])),
|
||||
None => (&raw[(slash + 1)..], None)
|
||||
};
|
||||
|
||||
if top_str.len() < 1 || sub_str.len() < 1 {
|
||||
return Err("Empty string.")
|
||||
}
|
||||
|
||||
if !is_valid_first_char(top_str.chars().next().unwrap())
|
||||
|| !is_valid_first_char(sub_str.chars().next().unwrap()) {
|
||||
return Err("Invalid first char.")
|
||||
}
|
||||
|
||||
if top_str.contains(|c| !is_valid_char(c))
|
||||
|| sub_str.contains(|c| !is_valid_char(c)) {
|
||||
return Err("Invalid character in string.")
|
||||
}
|
||||
|
||||
let (top_str, sub_str) = (&*top_str.to_lowercase(), &*sub_str.to_lowercase());
|
||||
let top_level = TopLevel::from_str(top_str).map_err(|_| "Bad TopLevel")?;
|
||||
let sub_level = SubLevel::from_str(sub_str).map_err(|_| "Bad SubLevel")?;
|
||||
// FIXME: Use `rest` to find params.
|
||||
Ok(ContentType::new(top_level, sub_level, None))
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for ContentType {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}/{}", self.0.as_str(), self.1.as_str())?;
|
||||
|
||||
self.2.as_ref().map_or(Ok(()), |params| {
|
||||
for param in params.iter() {
|
||||
let (ref attr, ref value) = *param;
|
||||
write!(f, "; {}={}", attr, value)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Collider for ContentType {
|
||||
fn collides_with(&self, other: &ContentType) -> bool {
|
||||
self.0.collides_with(&other.0) && self.1.collides_with(&other.1)
|
||||
}
|
||||
}
|
||||
|
||||
impl Collider for TopLevel {
|
||||
fn collides_with(&self, other: &TopLevel) -> bool {
|
||||
*self == TopLevel::Star
|
||||
|| *other == TopLevel::Star
|
||||
|| *self == *other
|
||||
}
|
||||
}
|
||||
|
||||
impl Collider for SubLevel {
|
||||
fn collides_with(&self, other: &SubLevel) -> bool {
|
||||
*self == SubLevel::Star
|
||||
|| *other == SubLevel::Star
|
||||
|| *self == *other
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::ContentType;
|
||||
use hyper::mime::{TopLevel, SubLevel};
|
||||
use std::str::FromStr;
|
||||
|
||||
|
||||
macro_rules! assert_no_parse {
|
||||
($string:expr) => ({
|
||||
let result = ContentType::from_str($string);
|
||||
if !result.is_err() {
|
||||
println!("{} parsed!", $string);
|
||||
}
|
||||
|
||||
assert!(result.is_err());
|
||||
});
|
||||
}
|
||||
|
||||
macro_rules! assert_parse {
|
||||
($string:expr) => ({
|
||||
let result = ContentType::from_str($string);
|
||||
assert!(result.is_ok());
|
||||
result.unwrap()
|
||||
});
|
||||
($string:expr, $top:tt/$sub:tt) => ({
|
||||
let c = assert_parse!($string);
|
||||
assert_eq!(c.0, TopLevel::$top);
|
||||
assert_eq!(c.1, SubLevel::$sub);
|
||||
c
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_simple() {
|
||||
assert_parse!("application/json", Application/Json);
|
||||
assert_parse!("*/json", Star/Json);
|
||||
assert_parse!("text/html", Text/Html);
|
||||
assert_parse!("TEXT/html", Text/Html);
|
||||
assert_parse!("*/*", Star/Star);
|
||||
assert_parse!("application/*", Application/Star);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_params() {
|
||||
assert_parse!("application/json; charset=utf8", Application/Json);
|
||||
assert_parse!("application/*;charset=utf8;else=1", Application/Star);
|
||||
assert_parse!("*/*;charset=utf8;else=1", Star/Star);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bad_parses() {
|
||||
assert_no_parse!("application//json");
|
||||
assert_no_parse!("application///json");
|
||||
assert_no_parse!("/json");
|
||||
assert_no_parse!("text/");
|
||||
assert_no_parse!("text//");
|
||||
assert_no_parse!("/");
|
||||
assert_no_parse!("*/");
|
||||
assert_no_parse!("/*");
|
||||
assert_no_parse!("///");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,35 +1,7 @@
|
|||
use request::Request;
|
||||
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||
pub enum Error {
|
||||
BadMethod,
|
||||
BadParse,
|
||||
NoRoute,
|
||||
NoRoute, // FIXME: Add a chain of routes attempted.
|
||||
NoKey
|
||||
}
|
||||
|
||||
pub struct RoutingError<'r> {
|
||||
pub error: Error,
|
||||
pub request: Request<'r>,
|
||||
pub chain: Option<&'r [&'r str]>
|
||||
}
|
||||
|
||||
impl<'a> RoutingError<'a> {
|
||||
pub fn unchained(request: Request<'a>)
|
||||
-> RoutingError<'a> {
|
||||
RoutingError {
|
||||
error: Error::NoRoute,
|
||||
request: request,
|
||||
chain: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new(error: Error, request: Request<'a>, chain: &'a [&'a str])
|
||||
-> RoutingError<'a> {
|
||||
RoutingError {
|
||||
error: error,
|
||||
request: request,
|
||||
chain: Some(chain)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,9 +23,10 @@ mod codegen;
|
|||
mod catcher;
|
||||
|
||||
pub mod handler {
|
||||
use super::{Request, Response};
|
||||
use super::{Request, Response, Error};
|
||||
|
||||
pub type Handler = for<'r> fn(Request<'r>) -> Response<'r>;
|
||||
pub type Handler = for<'r> fn(&'r Request<'r>) -> Response<'r>;
|
||||
pub type ErrorHandler = for<'r> fn(error: Error, &'r Request<'r>) -> Response<'r>;
|
||||
}
|
||||
|
||||
pub use logger::RocketLogger;
|
||||
|
@ -34,9 +35,9 @@ pub use codegen::{StaticRouteInfo, StaticCatchInfo};
|
|||
pub use request::Request;
|
||||
pub use method::Method;
|
||||
pub use response::{Response, Responder};
|
||||
pub use error::{Error, RoutingError};
|
||||
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;
|
||||
pub use handler::{Handler, ErrorHandler};
|
||||
|
|
|
@ -75,8 +75,9 @@ impl Log for RocketLogger {
|
|||
}
|
||||
Debug => {
|
||||
let loc = record.location();
|
||||
println!("{} {}:{}", Cyan.paint("-->"), loc.file(), loc.line());
|
||||
println!("{}", Cyan.paint(record.args()));
|
||||
print!("\n{} ", Blue.bold().paint("-->"));
|
||||
println!("{}:{}", Blue.paint(loc.file()), Blue.paint(loc.line()));
|
||||
println!("{}", record.args());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,7 +30,7 @@ impl Method {
|
|||
HyperMethod::Trace => Some(Trace),
|
||||
HyperMethod::Connect => Some(Connect),
|
||||
HyperMethod::Patch => Some(Patch),
|
||||
_ => None
|
||||
HyperMethod::Extension(_) => None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -49,7 +49,7 @@ impl FromStr for Method {
|
|||
"TRACE" => Ok(Trace),
|
||||
"CONNECT" => Ok(Connect),
|
||||
"PATCH" => Ok(Patch),
|
||||
_ => Err(Error::BadMethod)
|
||||
_ => Err(Error::BadMethod),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,7 +28,7 @@ impl<'r, 'c> FromRequest<'r, 'c> for Cookies {
|
|||
type Error = &'static str;
|
||||
|
||||
fn from_request(request: &'r Request<'c>) -> Result<Self, Self::Error> {
|
||||
match request.headers.get::<HyperCookie>() {
|
||||
match request.headers().get::<HyperCookie>() {
|
||||
// TODO: What to do about key?
|
||||
Some(cookie) => Ok(cookie.to_cookie_jar(&[])),
|
||||
None => Ok(Cookies::new(&[]))
|
||||
|
|
|
@ -1,38 +1,96 @@
|
|||
use std::io::{Read};
|
||||
use std::cell::RefCell;
|
||||
|
||||
use error::Error;
|
||||
use param::FromParam;
|
||||
use method::Method;
|
||||
use request::HyperHeaders;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
use content_type::ContentType;
|
||||
use hyper::uri::RequestUri as HyperRequestUri;
|
||||
use hyper::header;
|
||||
use router::URIBuf;
|
||||
use router::URI;
|
||||
use router::Route;
|
||||
|
||||
// Hyper stuff.
|
||||
use request::{HyperHeaders, HyperRequest};
|
||||
|
||||
pub struct Request<'a> {
|
||||
params: Option<Vec<&'a str>>,
|
||||
pub headers: &'a HyperHeaders, // TODO: Don't make pub?....
|
||||
pub params: RefCell<Option<Vec<&'a str>>>, // This also sucks.
|
||||
pub method: Method,
|
||||
pub uri: &'a str,
|
||||
pub data: &'a [u8]
|
||||
pub uri: URIBuf, // FIXME: Should be URI (without Hyper).
|
||||
pub data: Vec<u8>, // FIXME: Don't read this! (bad Hyper.)
|
||||
headers: HyperHeaders, // This sucks.
|
||||
}
|
||||
|
||||
impl<'a> Request<'a> {
|
||||
pub fn new(headers: &'a HyperHeaders, method: Method, uri: &'a str,
|
||||
params: Option<Vec<&'a str>>, data: &'a [u8]) -> Request<'a> {
|
||||
Request {
|
||||
headers: headers,
|
||||
method: method,
|
||||
params: params,
|
||||
uri: uri,
|
||||
data: data
|
||||
pub fn get_param<T: FromParam<'a>>(&'a self, n: usize) -> Result<T, Error> {
|
||||
let params = self.params.borrow();
|
||||
if params.is_none() || n >= params.as_ref().unwrap().len() {
|
||||
Err(Error::NoKey)
|
||||
} else {
|
||||
T::from_param(params.as_ref().unwrap()[n])
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_uri(&self) -> &'a str {
|
||||
self.uri
|
||||
#[cfg(test)]
|
||||
pub fn mock(method: Method, uri: &str) -> Request {
|
||||
Request {
|
||||
params: RefCell::new(None),
|
||||
method: method,
|
||||
uri: URIBuf::from(uri),
|
||||
data: vec![],
|
||||
headers: HyperHeaders::new()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_param<T: FromParam<'a>>(&'a self, n: usize) -> Result<T, Error> {
|
||||
if self.params.is_none() || n >= self.params.as_ref().unwrap().len() {
|
||||
Err(Error::NoKey)
|
||||
} else {
|
||||
T::from_param(self.params.as_ref().unwrap()[n])
|
||||
|
||||
// FIXME: Get rid of Hyper.
|
||||
#[inline(always)]
|
||||
pub fn headers(&self) -> &HyperHeaders {
|
||||
&self.headers
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
|
||||
pub fn uri(&'a self) -> URI<'a> {
|
||||
self.uri.as_uri()
|
||||
}
|
||||
|
||||
pub fn set_params(&'a self, route: &Route) {
|
||||
*self.params.borrow_mut() = Some(route.get_params(self.uri.as_uri()))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn set_content_type(&mut self, ct: ContentType) {
|
||||
let hyper_ct = header::ContentType(ct.into());
|
||||
self.headers.set::<header::ContentType>(hyper_ct)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
impl<'a, 'h, 'k> From<HyperRequest<'h, 'k>> for Request<'a> {
|
||||
fn from(hyper_req: HyperRequest<'h, 'k>) -> Request<'a> {
|
||||
let (_, h_method, h_headers, h_uri, _, mut h_body) = hyper_req.deconstruct();
|
||||
|
||||
let uri = match h_uri {
|
||||
HyperRequestUri::AbsolutePath(s) => URIBuf::from(s),
|
||||
_ => panic!("Can only accept absolute paths!")
|
||||
};
|
||||
|
||||
// FIXME: GRRR.
|
||||
let mut data = vec![];
|
||||
h_body.read_to_end(&mut data).unwrap();
|
||||
|
||||
Request {
|
||||
params: RefCell::new(None),
|
||||
method: Method::from_hyp(&h_method).unwrap(),
|
||||
uri: uri,
|
||||
data: data,
|
||||
headers: h_headers,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,20 +28,13 @@ fn uri_is_absolute(uri: &HyperRequestUri) -> bool {
|
|||
}
|
||||
}
|
||||
|
||||
fn unwrap_absolute_path(uri: &HyperRequestUri) -> &str {
|
||||
match *uri {
|
||||
HyperRequestUri::AbsolutePath(ref s) => s.as_str(),
|
||||
_ => panic!("Can only accept absolute paths!")
|
||||
}
|
||||
}
|
||||
|
||||
fn method_is_valid(method: &HyperMethod) -> bool {
|
||||
Method::from_hyp(method).is_some()
|
||||
}
|
||||
|
||||
impl HyperHandler for Rocket {
|
||||
fn handle<'a, 'k>(&'a self, req: HyperRequest<'a, 'k>,
|
||||
mut res: FreshHyperResponse<'a>) {
|
||||
fn handle<'h, 'k>(&self, req: HyperRequest<'h, 'k>,
|
||||
mut res: FreshHyperResponse<'h>) {
|
||||
info!("{:?} '{}':", Green.paint(&req.method), Blue.paint(&req.uri));
|
||||
|
||||
let finalize = |mut req: HyperRequest, _res: FreshHyperResponse| {
|
||||
|
@ -68,51 +61,38 @@ impl HyperHandler for Rocket {
|
|||
}
|
||||
|
||||
impl Rocket {
|
||||
fn dispatch<'h, 'k>(&self, mut req: HyperRequest<'h, 'k>,
|
||||
fn dispatch<'h, 'k>(&self, hyper_req: HyperRequest<'h, 'k>,
|
||||
res: FreshHyperResponse<'h>) {
|
||||
// We read all of the contents now because we have to do it at some
|
||||
// point thanks to Hyper. FIXME: Simple DOS attack here.
|
||||
let mut buf = vec![];
|
||||
let _ = req.read_to_end(&mut buf);
|
||||
let req = Request::from(hyper_req);
|
||||
let route = self.router.route(&req);
|
||||
if let Some(route) = route {
|
||||
// Retrieve and set the requests parameters.
|
||||
req.set_params(&route);
|
||||
|
||||
// Extract the method, uri, and try to find a route.
|
||||
let method = Method::from_hyp(&req.method).unwrap();
|
||||
let uri = unwrap_absolute_path(&req.uri);
|
||||
let route = self.router.route(method, uri);
|
||||
// Here's the magic: dispatch the request to the handler.
|
||||
let outcome = (route.handler)(&req).respond(res);
|
||||
info_!("{} {}", White.paint("Outcome:"), outcome);
|
||||
|
||||
// A closure which we call when we know there is no route.
|
||||
let handle_not_found = |response: FreshHyperResponse| {
|
||||
error_!("Dispatch failed. Returning 404.");
|
||||
|
||||
let request = Request::new(&req.headers, method, uri, None, &buf);
|
||||
let catcher = self.catchers.get(&404).unwrap();
|
||||
catcher.handle(RoutingError::unchained(request)).respond(response);
|
||||
};
|
||||
|
||||
// No route found. Handle the not_found error and return.
|
||||
if route.is_none() {
|
||||
// // TODO: keep trying lower ranked routes before dispatching a not
|
||||
// // found error.
|
||||
// outcome.map_forward(|res| {
|
||||
// error_!("No further matching routes.");
|
||||
// // TODO: Have some way to know why this was failed forward. Use that
|
||||
// // instead of always using an unchained error.
|
||||
// self.handle_not_found(req, res);
|
||||
// });
|
||||
} else {
|
||||
error_!("No matching routes.");
|
||||
return handle_not_found(res);
|
||||
return self.handle_not_found(&req, res);
|
||||
}
|
||||
}
|
||||
|
||||
// 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(&req.headers, method, uri, Some(params), &buf);
|
||||
|
||||
// TODO: Paint these magenta.
|
||||
trace_!("Dispatching request.");
|
||||
let outcome = (route.handler)(request).respond(res);
|
||||
|
||||
// TODO: keep trying lower ranked routes before dispatching a not found
|
||||
// error.
|
||||
info_!("{} {}", White.paint("Outcome:"), outcome);
|
||||
outcome.map_forward(|res| {
|
||||
error_!("No further matching routes.");
|
||||
// TODO: Have some way to know why this was failed forward. Use that
|
||||
// instead of always using an unchained error.
|
||||
handle_not_found(res);
|
||||
});
|
||||
// A closure which we call when we know there is no route.
|
||||
fn handle_not_found<'r>(&self, request: &'r Request<'r>,
|
||||
response: FreshHyperResponse) {
|
||||
error_!("Dispatch failed. Returning 404.");
|
||||
let catcher = self.catchers.get(&404).unwrap();
|
||||
catcher.handle(Error::NoRoute, request).respond(response);
|
||||
}
|
||||
|
||||
pub fn new(address: &'static str, port: isize) -> Rocket {
|
||||
|
|
|
@ -45,10 +45,12 @@ mod tests {
|
|||
use Method;
|
||||
use Method::*;
|
||||
use {Request, Response};
|
||||
use content_type::{ContentType, TopLevel, SubLevel};
|
||||
use std::str::FromStr;
|
||||
|
||||
type SimpleRoute = (Method, &'static str);
|
||||
|
||||
fn dummy_handler(_req: Request) -> Response<'static> {
|
||||
fn dummy_handler(_req: &Request) -> Response<'static> {
|
||||
Response::empty()
|
||||
}
|
||||
|
||||
|
@ -183,4 +185,52 @@ mod tests {
|
|||
assert!(!s_s_collide("/a/<b>", "/b/<b>"));
|
||||
assert!(!s_s_collide("/a<a>/<b>", "/b/<b>"));
|
||||
}
|
||||
|
||||
fn ct_route(m: Method, s: &str, ct: &str) -> Route {
|
||||
let mut route_a = Route::new(m, s, dummy_handler);
|
||||
route_a.content_type = ContentType::from_str(ct).expect("Whoops!");
|
||||
route_a
|
||||
}
|
||||
|
||||
fn ct_ct_collide(ct1: &str, ct2: &str) -> bool {
|
||||
let ct_a = ContentType::from_str(ct1).expect(ct1);
|
||||
let ct_b = ContentType::from_str(ct2).expect(ct2);
|
||||
ct_a.collides_with(&ct_b)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_content_type_colliions() {
|
||||
assert!(ct_ct_collide("application/json", "application/json"));
|
||||
assert!(ct_ct_collide("*/json", "application/json"));
|
||||
assert!(ct_ct_collide("*/*", "application/json"));
|
||||
assert!(ct_ct_collide("application/*", "application/json"));
|
||||
assert!(ct_ct_collide("application/*", "*/json"));
|
||||
assert!(ct_ct_collide("something/random", "something/random"));
|
||||
|
||||
assert!(!ct_ct_collide("text/*", "application/*"));
|
||||
assert!(!ct_ct_collide("*/text", "*/json"));
|
||||
assert!(!ct_ct_collide("*/text", "application/test"));
|
||||
assert!(!ct_ct_collide("something/random", "something_else/random"));
|
||||
assert!(!ct_ct_collide("something/random", "*/else"));
|
||||
assert!(!ct_ct_collide("*/random", "*/else"));
|
||||
assert!(!ct_ct_collide("something/*", "random/else"));
|
||||
}
|
||||
|
||||
fn r_ct_ct_collide(m1: Method, ct1: &str, m2: Method, ct2: &str) -> bool {
|
||||
let a_route = ct_route(m1, "a", ct1);
|
||||
let b_route = ct_route(m2, "a", ct2);
|
||||
a_route.collides_with(&b_route)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_route_content_type_colliions() {
|
||||
assert!(r_ct_ct_collide(Get, "application/json", Get, "application/json"));
|
||||
assert!(r_ct_ct_collide(Get, "*/json", Get, "application/json"));
|
||||
assert!(r_ct_ct_collide(Get, "*/json", Get, "application/*"));
|
||||
assert!(r_ct_ct_collide(Get, "text/html", Get, "text/*"));
|
||||
|
||||
assert!(!r_ct_ct_collide(Get, "text/html", Get, "application/*"));
|
||||
assert!(!r_ct_ct_collide(Get, "application/html", Get, "text/*"));
|
||||
assert!(!r_ct_ct_collide(Get, "*/json", Get, "text/html"));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ pub use self::route::Route;
|
|||
|
||||
use std::collections::hash_map::HashMap;
|
||||
use method::Method;
|
||||
use request::Request;
|
||||
|
||||
type Selector = (Method, usize);
|
||||
|
||||
|
@ -31,13 +32,15 @@ impl Router {
|
|||
// `Route` structure is inflexible. Have it be an associated type.
|
||||
// FIXME: Figure out a way to get more than one route, i.e., to correctly
|
||||
// handle ranking.
|
||||
pub fn route<'b>(&'b self, method: Method, uri: &str) -> Option<&'b Route> {
|
||||
let mut matched_route: Option<&Route> = None;
|
||||
// TODO: Should the Selector include the content-type? If it does, can't
|
||||
// warn the user that a match was found for the wrong content-type. It
|
||||
// doesn't, can, but this method is slower.
|
||||
pub fn route<'b>(&'b self, req: &Request) -> Option<&'b Route> {
|
||||
let num_segments = req.uri.segment_count();
|
||||
|
||||
let path = URI::new(uri);
|
||||
let num_segments = path.segment_count();
|
||||
if let Some(routes) = self.routes.get(&(method, num_segments)) {
|
||||
for route in routes.iter().filter(|r| r.collides_with(uri)) {
|
||||
let mut matched_route: Option<&Route> = None;
|
||||
if let Some(routes) = self.routes.get(&(req.method, num_segments)) {
|
||||
for route in routes.iter().filter(|r| r.collides_with(req)) {
|
||||
info_!("Matched: {}", route);
|
||||
if let Some(existing_route) = matched_route {
|
||||
if route.rank > existing_route.rank {
|
||||
|
@ -71,11 +74,13 @@ impl Router {
|
|||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use Method::*;
|
||||
use method::Method;
|
||||
use method::Method::*;
|
||||
use super::{Router, Route};
|
||||
use {Response, Request};
|
||||
use super::URI;
|
||||
|
||||
fn dummy_handler(_req: Request) -> Response<'static> {
|
||||
fn dummy_handler(_req: &Request) -> Response<'static> {
|
||||
Response::empty()
|
||||
}
|
||||
|
||||
|
@ -132,65 +137,70 @@ mod test {
|
|||
assert!(!router.has_collisions());
|
||||
}
|
||||
|
||||
fn route<'a>(router: &'a Router, method: Method, uri: &str) -> Option<&'a Route> {
|
||||
let request = Request::mock(method, uri);
|
||||
router.route(&request)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ok_routing() {
|
||||
let router = router_with_routes(&["/hello"]);
|
||||
assert!(router.route(Get, "/hello").is_some());
|
||||
assert!(route(&router, Get, "/hello").is_some());
|
||||
|
||||
let router = router_with_routes(&["/<a>"]);
|
||||
assert!(router.route(Get, "/hello").is_some());
|
||||
assert!(router.route(Get, "/hi").is_some());
|
||||
assert!(router.route(Get, "/bobbbbbbbbbby").is_some());
|
||||
assert!(router.route(Get, "/dsfhjasdf").is_some());
|
||||
assert!(route(&router, Get, "/hello").is_some());
|
||||
assert!(route(&router, Get, "/hi").is_some());
|
||||
assert!(route(&router, Get, "/bobbbbbbbbbby").is_some());
|
||||
assert!(route(&router, Get, "/dsfhjasdf").is_some());
|
||||
|
||||
let router = router_with_routes(&["/<a>/<b>"]);
|
||||
assert!(router.route(Get, "/hello/hi").is_some());
|
||||
assert!(router.route(Get, "/a/b/").is_some());
|
||||
assert!(router.route(Get, "/i/a").is_some());
|
||||
assert!(router.route(Get, "/jdlk/asdij").is_some());
|
||||
assert!(route(&router, Get, "/hello/hi").is_some());
|
||||
assert!(route(&router, Get, "/a/b/").is_some());
|
||||
assert!(route(&router, Get, "/i/a").is_some());
|
||||
assert!(route(&router, Get, "/jdlk/asdij").is_some());
|
||||
|
||||
let mut router = Router::new();
|
||||
router.add(Route::new(Put, "/hello".to_string(), dummy_handler));
|
||||
router.add(Route::new(Post, "/hello".to_string(), dummy_handler));
|
||||
router.add(Route::new(Delete, "/hello".to_string(), dummy_handler));
|
||||
assert!(router.route(Put, "/hello").is_some());
|
||||
assert!(router.route(Post, "/hello").is_some());
|
||||
assert!(router.route(Delete, "/hello").is_some());
|
||||
assert!(route(&router, Put, "/hello").is_some());
|
||||
assert!(route(&router, Post, "/hello").is_some());
|
||||
assert!(route(&router, Delete, "/hello").is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_err_routing() {
|
||||
let router = router_with_routes(&["/hello"]);
|
||||
assert!(router.route(Put, "/hello").is_none());
|
||||
assert!(router.route(Post, "/hello").is_none());
|
||||
assert!(router.route(Options, "/hello").is_none());
|
||||
assert!(router.route(Get, "/hell").is_none());
|
||||
assert!(router.route(Get, "/hi").is_none());
|
||||
assert!(router.route(Get, "/hello/there").is_none());
|
||||
assert!(router.route(Get, "/hello/i").is_none());
|
||||
assert!(router.route(Get, "/hillo").is_none());
|
||||
assert!(route(&router, Put, "/hello").is_none());
|
||||
assert!(route(&router, Post, "/hello").is_none());
|
||||
assert!(route(&router, Options, "/hello").is_none());
|
||||
assert!(route(&router, Get, "/hell").is_none());
|
||||
assert!(route(&router, Get, "/hi").is_none());
|
||||
assert!(route(&router, Get, "/hello/there").is_none());
|
||||
assert!(route(&router, Get, "/hello/i").is_none());
|
||||
assert!(route(&router, Get, "/hillo").is_none());
|
||||
|
||||
let router = router_with_routes(&["/<a>"]);
|
||||
assert!(router.route(Put, "/hello").is_none());
|
||||
assert!(router.route(Post, "/hello").is_none());
|
||||
assert!(router.route(Options, "/hello").is_none());
|
||||
assert!(router.route(Get, "/hello/there").is_none());
|
||||
assert!(router.route(Get, "/hello/i").is_none());
|
||||
assert!(route(&router, Put, "/hello").is_none());
|
||||
assert!(route(&router, Post, "/hello").is_none());
|
||||
assert!(route(&router, Options, "/hello").is_none());
|
||||
assert!(route(&router, Get, "/hello/there").is_none());
|
||||
assert!(route(&router, Get, "/hello/i").is_none());
|
||||
|
||||
let router = router_with_routes(&["/<a>/<b>"]);
|
||||
assert!(router.route(Get, "/a/b/c").is_none());
|
||||
assert!(router.route(Get, "/a").is_none());
|
||||
assert!(router.route(Get, "/a/").is_none());
|
||||
assert!(router.route(Get, "/a/b/c/d").is_none());
|
||||
assert!(router.route(Put, "/hello/hi").is_none());
|
||||
assert!(router.route(Put, "/a/b").is_none());
|
||||
assert!(router.route(Put, "/a/b").is_none());
|
||||
assert!(route(&router, Get, "/a/b/c").is_none());
|
||||
assert!(route(&router, Get, "/a").is_none());
|
||||
assert!(route(&router, Get, "/a/").is_none());
|
||||
assert!(route(&router, Get, "/a/b/c/d").is_none());
|
||||
assert!(route(&router, Put, "/hello/hi").is_none());
|
||||
assert!(route(&router, Put, "/a/b").is_none());
|
||||
assert!(route(&router, Put, "/a/b").is_none());
|
||||
}
|
||||
|
||||
macro_rules! assert_ranked_routes {
|
||||
($routes:expr, $to:expr, $want:expr) => ({
|
||||
let router = router_with_routes($routes);
|
||||
let route_path = router.route(Get, $to).unwrap().path.as_str();
|
||||
let route_path = route(&router, Get, $to).unwrap().path.as_str();
|
||||
assert_eq!(route_path as &str, $want as &str);
|
||||
})
|
||||
}
|
||||
|
@ -211,8 +221,8 @@ mod test {
|
|||
}
|
||||
|
||||
fn match_params(router: &Router, path: &str, expected: &[&str]) -> bool {
|
||||
router.route(Get, path).map_or(false, |route| {
|
||||
let params = route.get_params(path);
|
||||
route(router, Get, path).map_or(false, |route| {
|
||||
let params = route.get_params(URI::new(path));
|
||||
if params.len() != expected.len() {
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ use term_painter::Color::*;
|
|||
|
||||
use std::fmt;
|
||||
use std::convert::From;
|
||||
use request::Request;
|
||||
|
||||
pub struct Route {
|
||||
pub method: Method,
|
||||
|
@ -17,17 +18,6 @@ pub struct Route {
|
|||
}
|
||||
|
||||
impl Route {
|
||||
pub fn full<S>(rank: isize, m: Method, path: S, handler: Handler, t: ContentType)
|
||||
-> Route where S: AsRef<str> {
|
||||
Route {
|
||||
method: m,
|
||||
path: URIBuf::from(path.as_ref()),
|
||||
handler: handler,
|
||||
rank: rank,
|
||||
content_type: t,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn ranked<S>(rank: isize, m: Method, path: S, handler: Handler)
|
||||
-> Route where S: AsRef<str> {
|
||||
Route {
|
||||
|
@ -39,8 +29,7 @@ impl Route {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn new<S>(m: Method, path: S, handler: Handler)
|
||||
-> Route where S: AsRef<str> {
|
||||
pub fn new<S>(m: Method, path: S, handler: Handler) -> Route where S: AsRef<str> {
|
||||
Route {
|
||||
method: m,
|
||||
handler: handler,
|
||||
|
@ -57,9 +46,9 @@ impl Route {
|
|||
// FIXME: Decide whether a component has to be fully variable or not. That
|
||||
// is, whether you can have: /a<a>b/ or even /<a>:<b>/
|
||||
// TODO: Don't return a Vec...take in an &mut [&'a str] (no alloc!)
|
||||
pub fn get_params<'a>(&self, uri: &'a str) -> Vec<&'a str> {
|
||||
pub fn get_params<'a>(&self, uri: URI<'a>) -> Vec<&'a str> {
|
||||
let route_components = self.path.segments();
|
||||
let uri_components = URI::new(uri).segments();
|
||||
let uri_components = uri.segments();
|
||||
|
||||
let mut result = Vec::with_capacity(self.path.segment_count());
|
||||
for (route_seg, uri_seg) in route_components.zip(uri_components) {
|
||||
|
@ -74,25 +63,31 @@ impl Route {
|
|||
|
||||
impl fmt::Display for Route {
|
||||
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.content_type.is_any() {
|
||||
write!(f, "{}", Yellow.paint(&self.content_type))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a StaticRouteInfo> for Route {
|
||||
fn from(info: &'a StaticRouteInfo) -> 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
|
||||
}
|
||||
}
|
||||
|
||||
impl Collider for Route {
|
||||
fn collides_with(&self, b: &Route) -> bool {
|
||||
if self.path.segment_count() != b.path.segment_count()
|
||||
|| self.method != b.method
|
||||
|| self.rank != b.rank {
|
||||
return false;
|
||||
}
|
||||
|
||||
self.path.collides_with(&b.path)
|
||||
self.path.segment_count() == b.path.segment_count()
|
||||
&& self.method == b.method
|
||||
&& self.rank == b.rank
|
||||
&& self.content_type.collides_with(&b.content_type)
|
||||
&& self.path.collides_with(&b.path)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -108,3 +103,11 @@ impl Collider<str> for Route {
|
|||
other.collides_with(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'r> Collider<Request<'r>> for Route {
|
||||
fn collides_with(&self, req: &Request) -> bool {
|
||||
self.method == req.method
|
||||
&& req.uri.collides_with(&self.path)
|
||||
&& req.content_type().collides_with(&self.content_type)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,3 +8,5 @@ plugin = true
|
|||
|
||||
[dependencies]
|
||||
rocket = { path = "../lib/" }
|
||||
log = "*"
|
||||
env_logger = "*"
|
||||
|
|
|
@ -7,9 +7,6 @@ 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>,
|
||||
|
@ -58,10 +55,11 @@ pub fn error_decorator(ecx: &mut ExtCtxt, sp: Span, meta_item: &MetaItem,
|
|||
let catch_fn_name = prepend_ident(CATCH_FN_PREFIX, &item.ident);
|
||||
let catch_code = error_params.code.node;
|
||||
let catch_fn_item = quote_item!(ecx,
|
||||
fn $catch_fn_name<'rocket>(_req: rocket::Request<'rocket>)
|
||||
-> rocket::Response<'rocket> {
|
||||
fn $catch_fn_name<'rocket>(err: ::rocket::Error,
|
||||
req: &'rocket ::rocket::Request<'rocket>)
|
||||
-> ::rocket::Response<'rocket> {
|
||||
// TODO: Figure out what type signature of catcher should be.
|
||||
let result = $fn_name(RoutingError::unchained(_req));
|
||||
let result = $fn_name(err, req);
|
||||
rocket::Response::with_raw_status($catch_code, result)
|
||||
}
|
||||
).unwrap();
|
||||
|
|
|
@ -7,6 +7,7 @@ extern crate syntax_ext;
|
|||
extern crate rustc;
|
||||
extern crate rustc_plugin;
|
||||
extern crate rocket;
|
||||
extern crate env_logger;
|
||||
|
||||
#[macro_use] mod utils;
|
||||
mod routes_macro;
|
||||
|
|
|
@ -8,9 +8,6 @@ use syntax::ptr::P;
|
|||
use utils::*;
|
||||
use rocket::{Method, ContentType};
|
||||
|
||||
#[allow(dead_code)]
|
||||
const DEBUG: bool = true;
|
||||
|
||||
pub struct MetaItemParser<'a, 'c: 'a> {
|
||||
attr_name: &'a str,
|
||||
ctxt: &'a ExtCtxt<'c>,
|
||||
|
@ -122,7 +119,7 @@ pub struct RouteParams {
|
|||
pub method: Spanned<Method>,
|
||||
pub path: KVSpanned<String>,
|
||||
pub form: Option<KVSpanned<String>>,
|
||||
pub content_type: Option<KVSpanned<ContentType>>,
|
||||
pub content_type: KVSpanned<ContentType>,
|
||||
}
|
||||
|
||||
pub trait RouteDecoratorExt {
|
||||
|
@ -219,8 +216,7 @@ impl<'a, 'c> RouteDecoratorExt for MetaItemParser<'a, 'c> {
|
|||
self.ctxt.span_err(data.v_span, &msg);
|
||||
None
|
||||
}
|
||||
});
|
||||
|
||||
}).unwrap_or(KVSpanned::dummy(ContentType::any()));
|
||||
|
||||
RouteParams {
|
||||
method: method,
|
||||
|
|
|
@ -11,7 +11,8 @@ use syntax::ptr::P;
|
|||
use syntax::print::pprust::{item_to_string, stmt_to_string};
|
||||
use syntax::parse::token::{self, str_to_ident};
|
||||
|
||||
use rocket::Method;
|
||||
use rocket::{Method, ContentType};
|
||||
use rocket::content_type::{TopLevel, SubLevel};
|
||||
|
||||
pub fn extract_params_from_kv<'a>(parser: &MetaItemParser,
|
||||
params: &'a KVSpanned<String>) -> Vec<Spanned<&'a str>> {
|
||||
|
@ -93,11 +94,12 @@ fn get_form_stmt(ecx: &ExtCtxt, fn_args: &mut Vec<UserParam>,
|
|||
// The actual code we'll be inserting.
|
||||
quote_stmt!(ecx,
|
||||
let $param_ident: $param_ty =
|
||||
if let Ok(form_string) = ::std::str::from_utf8(_req.data) {
|
||||
if let Ok(form_string) = ::std::str::from_utf8(_req.data.as_slice()) {
|
||||
match ::rocket::form::FromForm::from_form_string(form_string) {
|
||||
Ok(v) => v,
|
||||
Err(_) => {
|
||||
debug!("\t=> Form failed to parse.");
|
||||
// TODO:
|
||||
// debug!("\t=> Form failed to parse.");
|
||||
return ::rocket::Response::not_found();
|
||||
}
|
||||
}
|
||||
|
@ -107,9 +109,9 @@ fn get_form_stmt(ecx: &ExtCtxt, fn_args: &mut Vec<UserParam>,
|
|||
)
|
||||
}
|
||||
|
||||
// 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,
|
||||
// TODO: 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<Expr> {
|
||||
|
@ -126,10 +128,61 @@ fn method_variant_to_expr(ecx: &ExtCtxt, method: Method) -> P<Expr> {
|
|||
}
|
||||
}
|
||||
|
||||
// Same here.
|
||||
fn top_level_to_expr(ecx: &ExtCtxt, level: &TopLevel) -> P<Expr> {
|
||||
use rocket::content_type::TopLevel::*;
|
||||
match *level {
|
||||
Star => quote_expr!(ecx, ::rocket::content_type::TopLevel::Star),
|
||||
Text => quote_expr!(ecx, ::rocket::content_type::TopLevel::Text),
|
||||
Image => quote_expr!(ecx, ::rocket::content_type::TopLevel::Image),
|
||||
Audio => quote_expr!(ecx, ::rocket::content_type::TopLevel::Audio),
|
||||
Video => quote_expr!(ecx, ::rocket::content_type::TopLevel::Video),
|
||||
Application => quote_expr!(ecx, ::rocket::content_type::TopLevel::Application),
|
||||
Multipart => quote_expr!(ecx, ::rocket::content_type::TopLevel::Multipart),
|
||||
Message => quote_expr!(ecx, ::rocket::content_type::TopLevel::Message),
|
||||
Model => quote_expr!(ecx, ::rocket::content_type::TopLevel::Model),
|
||||
Ext(ref s) => quote_expr!(ecx, ::rocket::content_type::TopLevel::Ext($s)),
|
||||
}
|
||||
}
|
||||
|
||||
// Same here.
|
||||
fn sub_level_to_expr(ecx: &ExtCtxt, level: &SubLevel) -> P<Expr> {
|
||||
use rocket::content_type::SubLevel::*;
|
||||
match *level {
|
||||
Star => quote_expr!(ecx, ::rocket::content_type::SubLevel::Star),
|
||||
Plain => quote_expr!(ecx, ::rocket::content_type::SubLevel::Plain),
|
||||
Html => quote_expr!(ecx, ::rocket::content_type::SubLevel::Html),
|
||||
Xml => quote_expr!(ecx, ::rocket::content_type::SubLevel::Xml),
|
||||
Javascript => quote_expr!(ecx, ::rocket::content_type::SubLevel::Javascript),
|
||||
Css => quote_expr!(ecx, ::rocket::content_type::SubLevel::Css),
|
||||
EventStream => quote_expr!(ecx, ::rocket::content_type::SubLevel::EventStream),
|
||||
Json => quote_expr!(ecx, ::rocket::content_type::SubLevel::Json),
|
||||
WwwFormUrlEncoded =>
|
||||
quote_expr!(ecx, ::rocket::content_type::SubLevel::WwwFormUrlEncoded),
|
||||
Msgpack => quote_expr!(ecx, ::rocket::content_type::SubLevel::Msgpack),
|
||||
OctetStream =>
|
||||
quote_expr!(ecx, ::rocket::content_type::SubLevel::OctetStream),
|
||||
FormData => quote_expr!(ecx, ::rocket::content_type::SubLevel::FormData),
|
||||
Png => quote_expr!(ecx, ::rocket::content_type::SubLevel::Png),
|
||||
Gif => quote_expr!(ecx, ::rocket::content_type::SubLevel::Gif),
|
||||
Bmp => quote_expr!(ecx, ::rocket::content_type::SubLevel::Bmp),
|
||||
Jpeg => quote_expr!(ecx, ::rocket::content_type::SubLevel::Jpeg),
|
||||
Ext(ref s) => quote_expr!(ecx, ::rocket::content_type::SubLevel::Ext($s)),
|
||||
}
|
||||
}
|
||||
|
||||
fn content_type_to_expr(ecx: &ExtCtxt, content_type: &ContentType) -> P<Expr> {
|
||||
let top_level = top_level_to_expr(ecx, &content_type.0);
|
||||
let sub_level = sub_level_to_expr(ecx, &content_type.1);
|
||||
quote_expr!(ecx, ::rocket::ContentType($top_level, $sub_level, None))
|
||||
}
|
||||
|
||||
// FIXME: Compilation fails when parameters have the same name as the function!
|
||||
pub fn route_decorator(known_method: Option<Spanned<Method>>, ecx: &mut ExtCtxt,
|
||||
sp: Span, meta_item: &MetaItem, annotated: &Annotatable,
|
||||
push: &mut FnMut(Annotatable)) {
|
||||
::rocket::logger::init(::rocket::logger::Level::Debug);
|
||||
|
||||
// Get the encompassing item and function declaration for the annotated func.
|
||||
let parser = MetaItemParser::new(ecx, meta_item, annotated, &sp);
|
||||
let (item, fn_decl) = (parser.expect_item(), parser.expect_fn_decl());
|
||||
|
@ -186,14 +239,14 @@ pub fn route_decorator(known_method: Option<Spanned<Method>>, ecx: &mut ExtCtxt,
|
|||
).unwrap()
|
||||
};
|
||||
|
||||
debug!("Param FN: {:?}", stmt_to_string(¶m_fn_item));
|
||||
debug!("Param FN: {}", stmt_to_string(¶m_fn_item));
|
||||
fn_param_exprs.push(param_fn_item);
|
||||
}
|
||||
|
||||
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>)
|
||||
fn $route_fn_name<'rocket>(_req: &'rocket ::rocket::Request<'rocket>)
|
||||
-> ::rocket::Response<'rocket> {
|
||||
$form_stmt
|
||||
$fn_param_exprs
|
||||
|
@ -208,19 +261,21 @@ pub fn route_decorator(known_method: Option<Spanned<Method>>, ecx: &mut ExtCtxt,
|
|||
let struct_name = prepend_ident(ROUTE_STRUCT_PREFIX, &item.ident);
|
||||
let path = &route.path.node;
|
||||
let method = method_variant_to_expr(ecx, route.method.node);
|
||||
push(Annotatable::Item(quote_item!(ecx,
|
||||
let content_type = content_type_to_expr(ecx, &route.content_type.node);
|
||||
|
||||
let static_item = quote_item!(ecx,
|
||||
#[allow(non_upper_case_globals)]
|
||||
pub static $struct_name: ::rocket::StaticRouteInfo =
|
||||
::rocket::StaticRouteInfo {
|
||||
method: $method,
|
||||
path: $path,
|
||||
handler: $route_fn_name,
|
||||
content_type: ::rocket::ContentType(
|
||||
::rocket::content_type::TopLevel::Star,
|
||||
::rocket::content_type::SubLevel::Star,
|
||||
None)
|
||||
content_type: $content_type,
|
||||
};
|
||||
).unwrap()));
|
||||
).unwrap();
|
||||
|
||||
debug!("Emitting static: {}", item_to_string(&static_item));
|
||||
push(Annotatable::Item(static_item));
|
||||
}
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue