From 28a1ef09167161da6a83bf25fbf3471ad3d77300 Mon Sep 17 00:00:00 2001 From: Sergio Benitez Date: Wed, 17 May 2017 01:39:36 -0700 Subject: [PATCH] Fairings, v3. Modifying the `Rocket` structure just before launch doesn't make sense for several reasons: 1) those affects can't influence the launch, and 2) they won't be observed in tests. Thus, an `Attach` fairing kind was added that ameliorates these issues. --- examples/fairings/Rocket.toml | 2 + examples/fairings/src/main.rs | 19 ++++++-- examples/fairings/src/tests.rs | 11 +++++ lib/src/fairing/ad_hoc.rs | 76 +++++++++++++++++++++++------ lib/src/fairing/fairings.rs | 45 +++++++++++------ lib/src/fairing/info_kind.rs | 35 ++++++++++--- lib/src/fairing/mod.rs | 89 +++++++++++++++++++++++----------- lib/src/rocket.rs | 44 +++++++++-------- lib/src/testing.rs | 20 +++++--- 9 files changed, 249 insertions(+), 92 deletions(-) create mode 100644 examples/fairings/Rocket.toml diff --git a/examples/fairings/Rocket.toml b/examples/fairings/Rocket.toml new file mode 100644 index 00000000..062cf500 --- /dev/null +++ b/examples/fairings/Rocket.toml @@ -0,0 +1,2 @@ +[global] +token = 123 diff --git a/examples/fairings/src/main.rs b/examples/fairings/src/main.rs index 579e8720..f55c3da6 100644 --- a/examples/fairings/src/main.rs +++ b/examples/fairings/src/main.rs @@ -6,10 +6,12 @@ extern crate rocket; use std::io::Cursor; use std::sync::atomic::{AtomicUsize, Ordering}; -use rocket::{Request, Data, Response}; +use rocket::{Request, State, Data, Response}; use rocket::fairing::{AdHoc, Fairing, Info, Kind}; use rocket::http::{Method, ContentType, Status}; +struct Token(i64); + #[cfg(test)] mod tests; #[derive(Default)] @@ -56,13 +58,22 @@ fn hello() -> &'static str { "Hello, world!" } +#[get("/token")] +fn token(token: State) -> String { + format!("{}", token.0) +} + fn rocket() -> rocket::Rocket { rocket::ignite() - .mount("/", routes![hello]) + .mount("/", routes![hello, token]) .attach(Counter::default()) + .attach(AdHoc::on_attach(|rocket| { + println!("Adding token managed state..."); + let token_val = rocket.config().get_int("token").unwrap_or(-1); + Ok(rocket.manage(Token(token_val))) + })) .attach(AdHoc::on_launch(|rocket| { - println!("Rocket is about to launch! Exciting! Here we go..."); - Ok(rocket) + println!("Rocket is about to launch!"); })) .attach(AdHoc::on_request(|req, _| { println!(" => Incoming request: {}", req); diff --git a/examples/fairings/src/tests.rs b/examples/fairings/src/tests.rs index ecba4a6a..a50e2e4f 100644 --- a/examples/fairings/src/tests.rs +++ b/examples/fairings/src/tests.rs @@ -9,6 +9,7 @@ fn rewrite_get_put() { let mut response = req.dispatch_with(&rocket); assert_eq!(response.body_string(), Some("Hello, fairings!".into())); } + #[test] fn counts() { let rocket = rocket(); @@ -33,3 +34,13 @@ fn counts() { let mut response = req.dispatch_with(&rocket); assert_eq!(response.body_string(), Some("Get: 4\nPost: 1".into())); } + +#[test] +fn token() { + let rocket = rocket(); + + // Ensure the token is '123', which is what we have in `Rocket.toml`. + let mut req = MockRequest::new(Get, "/token"); + let mut res = req.dispatch_with(&rocket); + assert_eq!(res.body_string(), Some("123".into())); +} diff --git a/lib/src/fairing/ad_hoc.rs b/lib/src/fairing/ad_hoc.rs index 3ba630f6..d353ed65 100644 --- a/lib/src/fairing/ad_hoc.rs +++ b/lib/src/fairing/ad_hoc.rs @@ -8,7 +8,7 @@ use fairing::{Fairing, Kind, Info}; /// /// # Usage /// -/// Use the [`on_launch`](#method.on_launch), +/// Use the [`on_attach`](#method.on_attach), [`on_launch`](#method.on_launch), /// [`on_request`](#method.on_request), or [`on_response`](#method.on_response) /// constructors to create an `AdHoc` structure from a function or closure. /// Then, simply attach the structure to the `Rocket` instance. @@ -25,28 +25,47 @@ use fairing::{Fairing, Kind, Info}; /// use rocket::http::Method; /// /// rocket::ignite() -/// .attach(AdHoc::on_launch(|rocket| { +/// .attach(AdHoc::on_launch(|_| { /// println!("Rocket is about to launch! Exciting! Here we go..."); -/// Ok(rocket) /// })) /// .attach(AdHoc::on_request(|req, _| { /// req.set_method(Method::Put); /// })); /// ``` pub enum AdHoc { + /// An ad-hoc **attach** fairing. Called when the fairing is attached. + #[doc(hidden)] + Attach(Box Result + Send + Sync + 'static>), /// An ad-hoc **launch** fairing. Called just before Rocket launches. #[doc(hidden)] - Launch(Box Result + Send + Sync + 'static>), + Launch(Box), /// An ad-hoc **request** fairing. Called when a request is received. #[doc(hidden)] Request(Box), /// An ad-hoc **response** fairing. Called when a response is ready to be /// sent to a client. #[doc(hidden)] - Response(Box) + Response(Box), } impl AdHoc { + /// Constructs an `AdHoc` attach fairing. The function `f` will be called by + /// Rocket when this fairing is attached. + /// + /// # Example + /// + /// ```rust + /// use rocket::fairing::AdHoc; + /// + /// // The no-op attach fairing. + /// let fairing = AdHoc::on_attach(|rocket| Ok(rocket)); + /// ``` + pub fn on_attach(f: F) -> AdHoc + where F: Fn(Rocket) -> Result + Send + Sync + 'static + { + AdHoc::Attach(Box::new(f)) + } + /// Constructs an `AdHoc` launch fairing. The function `f` will be called by /// Rocket just prior to launching. /// @@ -55,11 +74,13 @@ impl AdHoc { /// ```rust /// use rocket::fairing::AdHoc; /// - /// // The no-op launch fairing. - /// let fairing = AdHoc::on_launch(|rocket| Ok(rocket)); + /// // A fairing that prints a message just before launching. + /// let fairing = AdHoc::on_launch(|rocket| { + /// println!("Launching in T-3..2..1.."); + /// }); /// ``` pub fn on_launch(f: F) -> AdHoc - where F: Fn(Rocket) -> Result + Send + Sync + 'static + where F: Fn(&Rocket) + Send + Sync + 'static { AdHoc::Launch(Box::new(f)) } @@ -109,16 +130,43 @@ impl Fairing for AdHoc { fn info(&self) -> Info { use self::AdHoc::*; match *self { - Launch(_) => Info { name: "AdHoc::Launch", kind: Kind::Launch }, - Request(_) => Info { name: "AdHoc::Request", kind: Kind::Request }, - Response(_) => Info { name: "AdHoc::Response", kind: Kind::Response } + Attach(_) => { + Info { + name: "AdHoc::Attach", + kind: Kind::Attach, + } + } + Launch(_) => { + Info { + name: "AdHoc::Launch", + kind: Kind::Launch, + } + } + Request(_) => { + Info { + name: "AdHoc::Request", + kind: Kind::Request, + } + } + Response(_) => { + Info { + name: "AdHoc::Response", + kind: Kind::Response, + } + } } } - fn on_launch(&self, rocket: Rocket) -> Result { + fn on_attach(&self, rocket: Rocket) -> Result { match *self { - AdHoc::Launch(ref launch_fn) => launch_fn(rocket), - _ => Ok(rocket) + AdHoc::Attach(ref callback) => callback(rocket), + _ => Ok(rocket), + } + } + + fn on_launch(&self, rocket: &Rocket) { + if let AdHoc::Launch(ref callback) = *self { + callback(rocket) } } diff --git a/lib/src/fairing/fairings.rs b/lib/src/fairing/fairings.rs index 4129d444..0afc0891 100644 --- a/lib/src/fairing/fairings.rs +++ b/lib/src/fairing/fairings.rs @@ -4,6 +4,7 @@ use fairing::{Fairing, Kind}; #[derive(Default)] pub struct Fairings { all_fairings: Vec>, + attach_failure: bool, launch: Vec<&'static Fairing>, request: Vec<&'static Fairing>, response: Vec<&'static Fairing>, @@ -15,11 +16,16 @@ impl Fairings { Fairings::default() } - #[inline] - pub fn attach(&mut self, fairing: Box) { + pub fn attach(&mut self, fairing: Box, mut rocket: Rocket) -> Rocket { // Get the kind information. let kind = fairing.info().kind; + // Run the `on_attach` callback if this is an 'attach' fairing. + if kind.is(Kind::Attach) { + rocket = fairing.on_attach(rocket) + .unwrap_or_else(|r| { self.attach_failure = true; r }) + } + // The `Fairings` structure separates `all_fairings` into kind groups so // we don't have to search through all fairings and do a comparison at // runtime. We need references since a single structure can be multiple @@ -36,22 +42,25 @@ impl Fairings { // deallocating `Box` structures. As such, the references will // always be valid. Note: `ptr` doesn't point into the `Vec`, so // reallocations there are irrelvant. Instead, it points into the heap. - let ptr: &'static Fairing = unsafe { ::std::mem::transmute(&*fairing) }; + // + // Also, we don't save attach fairings since we don't need them anymore. + if !kind.is_exactly(Kind::Attach) { + let ptr: &'static Fairing = unsafe { ::std::mem::transmute(&*fairing) }; - self.all_fairings.push(fairing); - if kind.is(Kind::Launch) { self.launch.push(ptr); } - if kind.is(Kind::Request) { self.request.push(ptr); } - if kind.is(Kind::Response) { self.response.push(ptr); } + self.all_fairings.push(fairing); + if kind.is(Kind::Launch) { self.launch.push(ptr); } + if kind.is(Kind::Request) { self.request.push(ptr); } + if kind.is(Kind::Response) { self.response.push(ptr); } + } + + rocket } #[inline(always)] - pub fn handle_launch(&mut self, mut rocket: Rocket) -> Option { - let mut success = Some(()); - for f in &self.launch { - rocket = f.on_launch(rocket).unwrap_or_else(|r| { success = None; r }); + pub fn handle_launch(&self, rocket: &Rocket) { + for fairing in &self.launch { + fairing.on_launch(rocket); } - - success.map(|_| rocket) } #[inline(always)] @@ -68,6 +77,10 @@ impl Fairings { } } + pub fn had_failure(&self) -> bool { + self.attach_failure + } + pub fn pretty_print_counts(&self) { use term_painter::ToStyle; use term_painter::Color::{White, Magenta}; @@ -78,8 +91,10 @@ impl Fairings { fn info_if_nonempty(kind: &str, fairings: &[&Fairing]) { let names: Vec<&str> = fairings.iter().map(|f| f.info().name).collect(); - info_!("{} {}: {}", White.paint(fairings.len()), kind, - White.paint(names.join(", "))); + info_!("{} {}: {}", + White.paint(fairings.len()), + kind, + White.paint(names.join(", "))); } info_if_nonempty("launch", &self.launch); diff --git a/lib/src/fairing/info_kind.rs b/lib/src/fairing/info_kind.rs index ac0091ec..a950ce8b 100644 --- a/lib/src/fairing/info_kind.rs +++ b/lib/src/fairing/info_kind.rs @@ -10,7 +10,7 @@ use std::ops::BitOr; /// # Example /// /// A simple `Info` structure that can be used for a `Fairing` that implements -/// all three callbacks: +/// all four callbacks: /// /// ``` /// use rocket::fairing::{Info, Kind}; @@ -18,7 +18,7 @@ use std::ops::BitOr; /// # let _unused_info = /// Info { /// name: "Example Fairing", -/// kind: Kind::Launch | Kind::Request | Kind::Response +/// kind: Kind::Attach | Kind::Launch | Kind::Request | Kind::Response /// } /// # ; /// ``` @@ -35,6 +35,7 @@ pub struct Info { /// A fairing can request any combination of any of the following kinds of /// callbacks: /// +/// * Attach /// * Launch /// * Request /// * Response @@ -42,18 +43,20 @@ pub struct Info { /// Two `Kind` structures can be `or`d together to represent a combination. For /// instance, to represent a fairing that is both a launch and request fairing, /// use `Kind::Launch | Kind::Request`. Similarly, to represent a fairing that -/// is all three kinds, use `Kind::Launch | Kind::Request | Kind::Response`. +/// is only an attach fairing, use `Kind::Attach`. #[derive(Debug, Clone, Copy)] pub struct Kind(usize); #[allow(non_upper_case_globals)] impl Kind { + /// `Kind` flag representing a request for an 'attach' callback. + pub const Attach: Kind = Kind(0b0001); /// `Kind` flag representing a request for a 'launch' callback. - pub const Launch: Kind = Kind(0b001); + pub const Launch: Kind = Kind(0b0010); /// `Kind` flag representing a request for a 'request' callback. - pub const Request: Kind = Kind(0b010); + pub const Request: Kind = Kind(0b0100); /// `Kind` flag representing a request for a 'response' callback. - pub const Response: Kind = Kind(0b100); + pub const Response: Kind = Kind(0b1000); /// Returns `true` if `self` is a superset of `other`. In other words, /// returns `true` if all of the kinds in `other` are also in `self`. @@ -77,6 +80,26 @@ impl Kind { pub fn is(self, other: Kind) -> bool { (other.0 & self.0) == other.0 } + + /// Returns `true` if `self` is exactly `other`. + /// + /// # Example + /// + /// ```rust + /// use rocket::fairing::Kind; + /// + /// let launch_and_req = Kind::Launch | Kind::Request; + /// assert!(launch_and_req.is_exactly(Kind::Launch | Kind::Request)); + /// + /// assert!(!launch_and_req.is_exactly(Kind::Launch)); + /// assert!(!launch_and_req.is_exactly(Kind::Request)); + /// assert!(!launch_and_req.is_exactly(Kind::Response)); + /// assert!(!launch_and_req.is_exactly(Kind::Launch | Kind::Response)); + /// ``` + #[inline] + pub fn is_exactly(self, other: Kind) -> bool { + self.0 == other.0 + } } impl BitOr for Kind { diff --git a/lib/src/fairing/mod.rs b/lib/src/fairing/mod.rs index f2e2f08e..d96bac12 100644 --- a/lib/src/fairing/mod.rs +++ b/lib/src/fairing/mod.rs @@ -1,4 +1,4 @@ -//! Fairings: structured interposition at launch, request, and response time. +//! Fairings: callbacks at attach, launch, request, and response time. //! //! Fairings allow for structured interposition at various points in the //! application lifetime. Fairings can be seen as a restricted form of @@ -26,7 +26,7 @@ //! ``` //! //! Once a fairing is attached, Rocket will execute it at the appropiate time, -//! which varies depending on the fairing type. See the +//! which varies depending on the fairing implementation. See the //! [`Fairing`](/rocket/fairing/trait.Fairing.html) trait documentation for more //! information on the dispatching of fairing methods. //! @@ -37,7 +37,8 @@ //! fairing callbacks may not be commutative, it is important to communicate to //! the user every consequence of a fairing. Furthermore, a `Fairing` should //! take care to act locally so that the actions of other `Fairings` are not -//! jeopardized. +//! jeopardized. For instance, unless it is made abundantly clear, a fairing +//! should not rewrite every request. use {Rocket, Request, Response, Data}; mod fairings; @@ -90,12 +91,29 @@ pub use self::info_kind::{Info, Kind}; /// /// ## Fairing Callbacks /// -/// There are three kinds of fairing callbacks: launch, request, and response. -/// As mentioned above, a fairing can request any combination of these callbacks -/// through the `kind` field of the `Info` structure returned from the `info` -/// method. Rocket will only invoke the callbacks set in the `kind` field. +/// There are four kinds of fairing callbacks: attach, launch, request, and +/// response. As mentioned above, a fairing can request any combination of these +/// callbacks through the `kind` field of the `Info` structure returned from the +/// `info` method. Rocket will only invoke the callbacks set in the `kind` +/// field. /// -/// The three callback kinds are as follows: +/// The four callback kinds are as follows: +/// +/// * **Attach (`on_attach`)** +/// +/// An attach callback, represented by the +/// [`on_attach`](/rocket/fairing/trait.Fairing.html#method.on_attach) +/// method, is called when a fairing is first attached via the +/// [`attach`](/rocket/struct.Rocket.html#method.attach) method. The state +/// of the `Rocket` instance is, at this point, not finalized, as the user +/// may still add additional information to the `Rocket` instance. As a +/// result, it is unwise to depend on the state of the `Rocket` instance. +/// +/// An attach callback can arbitrarily modify the `Rocket` instance being +/// constructed. It returns `Ok` if it would like launching to proceed +/// nominally and `Err` otherwise. If a launch callback returns `Err`, +/// launch will be aborted. All attach callbacks are executed on `launch`, +/// even if one or more signal a failure. /// /// * **Launch (`on_launch`)** /// @@ -103,10 +121,8 @@ pub use self::info_kind::{Info, Kind}; /// [`on_launch`](/rocket/fairing/trait.Fairing.html#method.on_launch) /// method, is called immediately before the Rocket application has /// launched. At this point, Rocket has opened a socket for listening but -/// has not yet begun accepting connections. A launch callback can -/// arbitrarily modify the `Rocket` instance being launched. It returns `Ok` -/// if it would like launching to proceed nominally and `Err` otherwise. If -/// a launch callback returns `Err`, launch is aborted. +/// has not yet begun accepting connections. A launch callback can inspect +/// the `Rocket` instance being launched. /// /// * **Request (`on_request`)** /// @@ -136,15 +152,15 @@ pub use self::info_kind::{Info, Kind}; /// # Implementing /// /// A `Fairing` implementation has one required method: `info`. A `Fairing` can -/// also implement any of the available callbacks: `on_launch`, `on_request`, -/// and `on_response`. A `Fairing` _must_ set the appropriate callback kind in -/// the `kind` field of the returned `Info` structure from `info` for a callback -/// to actually be issued by Rocket. +/// also implement any of the available callbacks: `on_attach`, `on_launch`, +/// `on_request`, and `on_response`. A `Fairing` _must_ set the appropriate +/// callback kind in the `kind` field of the returned `Info` structure from +/// `info` for a callback to actually be issued by Rocket. /// /// A `Fairing` must be `Send + Sync + 'static`. This means that the fairing /// must be sendable across thread boundaries (`Send`), thread-safe (`Sync`), /// and have no non-`'static` reference (`'static`). Note that these bounds _do -/// not_ prohibit a `Fairing` from having state: the state need simply be +/// not_ prohibit a `Fairing` from holding state: the state need simply be /// thread-safe and statically available or heap allocated. /// /// # Example @@ -154,7 +170,7 @@ pub use self::info_kind::{Info, Kind}; /// guards](/rocket/request/trait.FromRequest.html) and [managed /// state](/rocket/request/struct.State.html), it would require us to annotate /// every `GET` and `POST` request with custom types, polluting handler -/// signatures. Instead, we can create a simple fairing that does this globally. +/// signatures. Instead, we can create a simple fairing that acts globally. /// /// The `Counter` fairing below records the number of all `GET` and `POST` /// requests received. It makes these counts available at a special `'/counts'` @@ -238,23 +254,38 @@ pub trait Fairing: Send + Sync + 'static { /// fn info(&self) -> Info { /// Info { /// name: "My Custom Fairing", - /// kind: Kind::Launch | Kind::Response + /// kind: Kind::Attach | Kind::Launch | Kind::Response /// } /// } /// } /// ``` fn info(&self) -> Info; - /// The launch callback. Returns `Ok` if launch should proceed and `Err` if + /// The attach callback. Returns `Ok` if launch should proceed and `Err` if /// launch should be aborted. /// - /// This method is called just prior to launching an application if - /// `Kind::Launch` is in the `kind` field of the `Info` structure for this - /// fairing. The `rocket` parameter is the `Rocket` instance that was built - /// for this application. + /// This method is called when a fairing is attached if `Kind::Attach` is in + /// the `kind` field of the `Info` structure for this fairing. The `rocket` + /// parameter is the `Rocket` instance that is currently being built for + /// this application. + /// + /// ## Default Implementation /// /// The default implementation of this method simply returns `Ok(rocket)`. - fn on_launch(&self, rocket: Rocket) -> Result { Ok(rocket) } + fn on_attach(&self, rocket: Rocket) -> Result { Ok(rocket) } + + /// The launch callback. + /// + /// This method is called just prior to launching the application if + /// `Kind::Launch` is in the `kind` field of the `Info` structure for this + /// fairing. The `&Rocket` parameter curresponds to the application that + /// will be launched. + /// + /// ## Default Implementation + /// + /// The default implementation of this method does nothing. + #[allow(unused_variables)] + fn on_launch(&self, rocket: &Rocket) {} /// The request callback. /// @@ -263,9 +294,11 @@ pub trait Fairing: Send + Sync + 'static { /// `&mut Request` parameter is the incoming request, and the `&Data` /// parameter is the incoming data in the request. /// + /// ## Default Implementation + /// /// The default implementation of this method does nothing. #[allow(unused_variables)] - fn on_request(&self, request: &mut Request, data: &Data) { } + fn on_request(&self, request: &mut Request, data: &Data) {} /// The response callback. /// @@ -274,7 +307,9 @@ pub trait Fairing: Send + Sync + 'static { /// this fairing. The `&Request` parameter is the request that was routed, /// and the `&mut Response` parameter is the resulting response. /// + /// ## Default Implementation + /// /// The default implementation of this method does nothing. #[allow(unused_variables)] - fn on_response(&self, request: &Request, response: &mut Response) { } + fn on_response(&self, request: &Request, response: &mut Response) {} } diff --git a/lib/src/rocket.rs b/lib/src/rocket.rs index 2b5c8104..6eb378cc 100644 --- a/lib/src/rocket.rs +++ b/lib/src/rocket.rs @@ -284,7 +284,6 @@ impl Rocket { for route in matches { // Retrieve and set the requests parameters. info_!("Matched: {}", route); - // FIXME: Users should not be able to use this. request.set_params(route); // Dispatch the request to the handler. @@ -598,25 +597,37 @@ impl Rocket { /// use rocket::Rocket; /// use rocket::fairing::AdHoc; /// - /// fn youll_see(rocket: Rocket) -> Result { - /// println!("Rocket is about to launch! You just see..."); - /// Ok(rocket) - /// } - /// /// fn main() { /// # if false { // We don't actually want to launch the server in an example. /// rocket::ignite() - /// .attach(AdHoc::on_launch(youll_see)) + /// .attach(AdHoc::on_launch(|_| { + /// println!("Rocket is about to launch! You just see..."); + /// })) /// .launch(); /// # } /// } /// ``` #[inline] pub fn attach(mut self, fairing: F) -> Self { - self.fairings.attach(Box::new(fairing)); + // Attach the fairings, which requires us to move `self`. + let mut fairings = mem::replace(&mut self.fairings, Fairings::new()); + self = fairings.attach(Box::new(fairing), self); + + // Make sure we keep the fairings around! + self.fairings = fairings; self } + pub(crate) fn prelaunch_check(&self) -> Option { + if self.router.has_collisions() { + Some(LaunchError::from(LaunchErrorKind::Collision)) + } else if self.fairings.had_failure() { + Some(LaunchError::from(LaunchErrorKind::FailedFairing)) + } else { + None + } + } + /// Starts the application server and begins listening for and dispatching /// requests to mounted routes and catchers. Unless there is an error, this /// function does not return and blocks until program termination. @@ -637,9 +648,9 @@ impl Rocket { /// rocket::ignite().launch(); /// # } /// ``` - pub fn launch(mut self) -> LaunchError { - if self.router.has_collisions() { - return LaunchError::from(LaunchErrorKind::Collision); + pub fn launch(self) -> LaunchError { + if let Some(error) = self.prelaunch_check() { + return error; } self.fairings.pretty_print_counts(); @@ -657,15 +668,8 @@ impl Rocket { Err(e) => return LaunchError::from(e) }; - // Run all of the launch fairings. - let mut fairings = mem::replace(&mut self.fairings, Fairings::new()); - self = match fairings.handle_launch(self) { - Some(rocket) => rocket, - None => return LaunchError::from(LaunchErrorKind::FailedFairing) - }; - - // Make sure we keep the request/response fairings! - self.fairings = fairings; + // Run the launch fairings. + self.fairings.handle_launch(&self); launch_info!("🚀 {} {}{}", White.paint("Rocket has launched from"), diff --git a/lib/src/testing.rs b/lib/src/testing.rs index 46445aa4..8bd79510 100644 --- a/lib/src/testing.rs +++ b/lib/src/testing.rs @@ -76,7 +76,7 @@ //! ``` use ::{Rocket, Request, Response, Data}; -use http::{Method, Header, Cookie}; +use http::{Method, Status, Header, Cookie}; use std::net::SocketAddr; @@ -201,11 +201,12 @@ impl<'r> MockRequest<'r> { /// Dispatch this request using a given instance of Rocket. /// - /// Returns the body of the response if there was a response. The return - /// value is `None` if any of the following occurs: - /// - /// 1. The returned body was not valid UTF8. - /// 2. The application failed to respond. + /// It is possible that the supplied `rocket` instance contains malformed + /// input such as colliding or invalid routes or failed fairings. When this + /// is the case, the returned `Response` will contain a status of + /// `InternalServerError`, and the body will contain the error that + /// occurred. In all other cases, the returned `Response` will be that of + /// the application. /// /// # Examples /// @@ -234,6 +235,13 @@ impl<'r> MockRequest<'r> { /// # } /// ``` pub fn dispatch_with<'s>(&'s mut self, rocket: &'r Rocket) -> Response<'s> { + if let Some(error) = rocket.prelaunch_check() { + return Response::build() + .status(Status::InternalServerError) + .sized_body(::std::io::Cursor::new(error.to_string())) + .finalize() + } + let data = ::std::mem::replace(&mut self.data, Data::local(vec![])); rocket.dispatch(&mut self.request, data) }