diff --git a/examples/handlebars_templates/src/tests.rs b/examples/handlebars_templates/src/tests.rs index 82af93a7..977142f1 100644 --- a/examples/handlebars_templates/src/tests.rs +++ b/examples/handlebars_templates/src/tests.rs @@ -7,10 +7,10 @@ use rocket_contrib::Template; const TEMPLATE_ROOT: &'static str = "templates/"; -macro_rules! run_test { - ($req:expr, $test_fn:expr) => ({ +macro_rules! dispatch { + ($method:expr, $path:expr, $test_fn:expr) => ({ let rocket = rocket(); - let mut req = $req; + let mut req = MockRequest::new($method, $path); $test_fn(req.dispatch_with(&rocket)); }) } @@ -19,8 +19,7 @@ macro_rules! run_test { fn test_root() { // Check that the redirect works. for method in &[Get, Head] { - let req = MockRequest::new(*method, "/"); - run_test!(req, |mut response: Response| { + dispatch!(*method, "/", |mut response: Response| { assert_eq!(response.status(), Status::SeeOther); assert!(response.body().is_none()); @@ -31,8 +30,7 @@ fn test_root() { // Check that other request methods are not accepted (and instead caught). for method in &[Post, Put, Delete, Options, Trace, Connect, Patch] { - let req = MockRequest::new(*method, "/"); - run_test!(req, |mut response: Response| { + dispatch!(*method, "/", |mut response: Response| { let mut map = ::std::collections::HashMap::new(); map.insert("path", "/"); let expected = Template::show(TEMPLATE_ROOT, "error/404", &map).unwrap(); @@ -46,16 +44,14 @@ fn test_root() { #[test] fn test_name() { // Check that the /hello/ route works. - let req = MockRequest::new(Get, "/hello/Jack"); - run_test!(req, |mut response: Response| { - assert_eq!(response.status(), Status::Ok); - + dispatch!(Get, "/hello/Jack", |mut response: Response| { let context = super::TemplateContext { name: "Jack".to_string(), items: vec!["One", "Two", "Three"].iter().map(|s| s.to_string()).collect() }; let expected = Template::show(TEMPLATE_ROOT, "index", &context).unwrap(); + assert_eq!(response.status(), Status::Ok); assert_eq!(response.body_string(), Some(expected)); }); } @@ -63,14 +59,12 @@ fn test_name() { #[test] fn test_404() { // Check that the error catcher works. - let req = MockRequest::new(Get, "/hello/"); - run_test!(req, |mut response: Response| { - assert_eq!(response.status(), Status::NotFound); - + dispatch!(Get, "/hello/", |mut response: Response| { let mut map = ::std::collections::HashMap::new(); map.insert("path", "/hello/"); let expected = Template::show(TEMPLATE_ROOT, "error/404", &map).unwrap(); + assert_eq!(response.status(), Status::NotFound); assert_eq!(response.body_string(), Some(expected)); }); } diff --git a/lib/src/request/from_request.rs b/lib/src/request/from_request.rs index beb08c70..52b1b297 100644 --- a/lib/src/request/from_request.rs +++ b/lib/src/request/from_request.rs @@ -1,6 +1,7 @@ use std::fmt::Debug; use std::net::SocketAddr; +use router::Route; use request::Request; use outcome::{self, IntoOutcome}; use outcome::Outcome::*; @@ -74,6 +75,13 @@ impl IntoOutcome for Result { /// Rocket implements `FromRequest` for several built-in types. Their behavior /// is documented here. /// +/// * **Method** +/// +/// Extracts the [Method](/rocket/http/enum.Method.html) from the incoming +/// request. +/// +/// _This implementation always returns successfully._ +/// /// * **&URI** /// /// Extracts the [URI](/rocket/http/uri/struct.URI.html) from the incoming @@ -81,12 +89,14 @@ impl IntoOutcome for Result { /// /// _This implementation always returns successfully._ /// -/// * **Method** +/// * **&Route** /// -/// Extracts the [Method](/rocket/http/enum.Method.html) from the incoming -/// request. +/// Extracts the [Route](/rocket/struct.Route.html) from the request if one +/// is available. If a route is not available, the request is forwarded. /// -/// _This implementation always returns successfully._ +/// For information of when a route is avaiable, see the +/// [`Request::route`](/rocket/struct.Request.html#method.route) +/// documentation. /// /// * **Cookies** /// @@ -191,6 +201,14 @@ pub trait FromRequest<'a, 'r>: Sized { fn from_request(request: &'a Request<'r>) -> Outcome; } +impl<'a, 'r> FromRequest<'a, 'r> for Method { + type Error = (); + + fn from_request(request: &'a Request<'r>) -> Outcome { + Success(request.method()) + } +} + impl<'a, 'r> FromRequest<'a, 'r> for &'a URI<'a> { type Error = (); @@ -199,11 +217,14 @@ impl<'a, 'r> FromRequest<'a, 'r> for &'a URI<'a> { } } -impl<'a, 'r> FromRequest<'a, 'r> for Method { +impl<'a, 'r> FromRequest<'a, 'r> for &'r Route { type Error = (); fn from_request(request: &'a Request<'r>) -> Outcome { - Success(request.method()) + match request.route() { + Some(route) => Success(route), + None => Forward(()) + } } } diff --git a/lib/src/request/request.rs b/lib/src/request/request.rs index e05df064..b6513c96 100644 --- a/lib/src/request/request.rs +++ b/lib/src/request/request.rs @@ -1,4 +1,4 @@ -use std::cell::RefCell; +use std::cell::{Cell, RefCell}; use std::net::SocketAddr; use std::fmt; use std::str; @@ -28,6 +28,7 @@ struct PresetState<'r> { struct RequestState<'r> { preset: Option>, params: RefCell>, + route: Cell>, cookies: RefCell, session: RefCell, accept: Storage>, @@ -64,7 +65,7 @@ impl<'r> Request<'r> { /// let request = Request::new(Method::Get, "/uri"); /// ``` #[inline(always)] - pub fn new>>(method: Method, uri: U) -> Request<'r> { + pub fn new<'s: 'r, U: Into>>(method: Method, uri: U) -> Request<'r> { Request { method: method, uri: uri.into(), @@ -72,6 +73,7 @@ impl<'r> Request<'r> { remote: None, state: RequestState { preset: None, + route: Cell::new(None), params: RefCell::new(Vec::new()), cookies: RefCell::new(CookieJar::new()), session: RefCell::new(CookieJar::new()), @@ -358,6 +360,18 @@ impl<'r> Request<'r> { } } + /// Get the limits. + pub fn limits(&self) -> &'r Limits { + &self.preset().config.limits + } + + /// Get the current route, if any. + /// + /// No route will be avaiable before routing. So not during request fairing. + pub fn route(&self) -> Option<&'r Route> { + self.state.route.get() + } + /// Retrieves and parses into `T` the 0-indexed `n`th dynamic parameter from /// the request. Returns `Error::NoKey` if `n` is greater than the number of /// params. Returns `Error::BadParse` if the parameter type `T` can't be @@ -450,11 +464,6 @@ impl<'r> Request<'r> { Some(Segments(&path[i..j])) } - /// Get the limits. - pub fn limits(&self) -> &'r Limits { - &self.preset().config.limits - } - #[inline(always)] fn preset(&self) -> &PresetState<'r> { match self.state.preset { @@ -471,7 +480,8 @@ impl<'r> Request<'r> { /// use may result in out of bounds indexing. /// TODO: Figure out the mount path from here. #[inline] - pub(crate) fn set_params(&self, route: &Route) { + pub(crate) fn set_route(&self, route: &'r Route) { + self.state.route.set(Some(route)); *self.state.params.borrow_mut() = route.get_param_indexes(self.uri()); } diff --git a/lib/src/rocket.rs b/lib/src/rocket.rs index 125ea843..015eaaa7 100644 --- a/lib/src/rocket.rs +++ b/lib/src/rocket.rs @@ -276,15 +276,29 @@ impl Rocket { /// until one of the handlers returns success or failure, or there are no /// additional routes to try (forward). The corresponding outcome for each /// condition is returned. + // + // FIXME: We _should_ be able to take an `&mut` here and mutate the request + // at any pointer _before_ we pass it to a handler as long as we drop the + // outcome. That should be safe. Since no mutable borrow can be held + // (ensuring `handler` takes an immutable borrow), any caller to `route` + // should be able to supply an `&mut` and retain an `&` after the call. + // + // Why are we even thinking about this? Because we want to set the route + // that the current request is trying to respond to so guards/handlers can + // get information about it. But we can't use a RefCell<&'r _> since that + // cuases lifetime issues since RefCell contains an UnsafeCell + // (weirdness...). IDEA: Have an `AtomicRef` type that does a pointer swap + // atomically when the ptr's size can be used for atomics. #[inline] - pub(crate) fn route<'r>(&self, request: &'r Request, mut data: Data) - -> handler::Outcome<'r> { + pub(crate) fn route<'s, 'r>(&'s self, + request: &'r Request<'s>, + mut data: Data) -> handler::Outcome<'r> { // Go through the list of matching routes until we fail or succeed. let matches = self.router.route(request); for route in matches { // Retrieve and set the requests parameters. info_!("Matched: {}", route); - request.set_params(route); + request.set_route(route); // Dispatch the request to the handler. let outcome = (route.handler)(request, data); @@ -486,6 +500,7 @@ impl Rocket { for mut route in routes { let path = format!("{}/{}", base, route.path); + route.set_base(base); route.set_path(path); info_!("{}", route); diff --git a/lib/src/router/route.rs b/lib/src/router/route.rs index 95678ba6..55da3f23 100644 --- a/lib/src/router/route.rs +++ b/lib/src/router/route.rs @@ -15,7 +15,10 @@ pub struct Route { pub method: Method, /// A function that should be called when the route matches. pub handler: Handler, - /// The path (in Rocket format) that should be matched against. + /// The base mount point of this `Route`. + pub base: URI<'static>, + /// The path (in Rocket format) that should be matched against. This path + /// already includes the base mount point. pub path: URI<'static>, /// The rank of this route. Lower ranks have higher priorities. pub rank: isize, @@ -48,6 +51,7 @@ impl Route { method: m, handler: handler, rank: default_rank(&uri), + base: URI::from("/"), path: uri, format: None, } @@ -59,13 +63,20 @@ impl Route { { Route { method: m, - path: URI::from(path.as_ref().to_string()), handler: handler, + base: URI::from("/"), + path: URI::from(path.as_ref().to_string()), rank: rank, format: None, } } + /// Sets the base mount point of the route. Does not update the rank or any + /// other parameters. + pub fn set_base(&mut self, path: S) where S: AsRef { + self.base = URI::from(path.as_ref().to_string()); + } + /// Sets the path of the route. Does not update the rank or any other /// parameters. pub fn set_path(&mut self, path: S) where S: AsRef { @@ -104,6 +115,7 @@ impl Clone for Route { method: self.method, handler: self.handler, rank: self.rank, + base: self.base.clone(), path: self.path.clone(), format: self.format.clone(), } diff --git a/lib/tests/route_guard.rs b/lib/tests/route_guard.rs new file mode 100644 index 00000000..54d4cdb0 --- /dev/null +++ b/lib/tests/route_guard.rs @@ -0,0 +1,38 @@ +#![feature(plugin, custom_derive)] +#![plugin(rocket_codegen)] + +extern crate rocket; + +use std::path::PathBuf; +use rocket::Route; + +#[get("/")] +fn files(route: &Route, path: PathBuf) -> String { + format!("{}/{}", route.base.path(), path.to_string_lossy()) +} + +mod route_guard_tests { + use super::*; + + use rocket::Rocket; + use rocket::testing::MockRequest; + use rocket::http::Method::*; + + fn assert_path(rocket: &Rocket, path: &str) { + let mut req = MockRequest::new(Get, path); + let mut res = req.dispatch_with(&rocket); + assert_eq!(res.body_string(), Some(path.into())); + } + + #[test] + fn check_mount_path() { + let rocket = rocket::ignite() + .mount("/first", routes![files]) + .mount("/second", routes![files]); + + assert_path(&rocket, "/first/some/path"); + assert_path(&rocket, "/second/some/path"); + assert_path(&rocket, "/first/second/b/c"); + assert_path(&rocket, "/second/a/b/c"); + } +}