diff --git a/lib/src/catcher.rs b/lib/src/catcher.rs index 5a984b09..45cbad8a 100644 --- a/lib/src/catcher.rs +++ b/lib/src/catcher.rs @@ -8,6 +8,7 @@ use std::fmt; use term_painter::ToStyle; use term_painter::Color::*; +/// An error catching route. pub struct Catcher { pub code: u16, handler: ErrorHandler, @@ -15,23 +16,32 @@ pub struct Catcher { } impl Catcher { + /// Creates a catcher for the given status code using the given error + /// handler. + #[inline(always)] pub fn new(code: u16, handler: ErrorHandler) -> Catcher { Catcher { code: code, handler: handler, is_default: false } } + #[doc(hidden)] + #[inline(always)] pub fn handle<'r>(&self, err: Error, request: &'r Request) -> Response<'r> { (self.handler)(err, request) } + #[inline(always)] fn new_default(code: u16, handler: ErrorHandler) -> Catcher { Catcher { code: code, handler: handler, is_default: true, } } + #[doc(hidden)] + #[inline(always)] pub fn is_default(&self) -> bool { self.is_default } } +#[doc(hidden)] impl<'a> From<&'a StaticCatchInfo> for Catcher { fn from(info: &'a StaticCatchInfo) -> Catcher { Catcher::new(info.code, info.handler) diff --git a/lib/src/lib.rs b/lib/src/lib.rs index a58b17a6..57525ede 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -137,7 +137,14 @@ pub use error::Error; pub use catcher::Catcher; pub use rocket::Rocket; -/// Alias to Rocket::ignite(). +/// Alias to [Rocket::ignite()](/rocket/struct.Rocket.html#method.ignite). +/// Creates a new instance of `Rocket`. pub fn ignite() -> Rocket { Rocket::ignite() } + +/// Alias to [Rocket::custom()](/rocket/struct.Rocket.html#method.custom). +/// Creates a new instance of `Rocket` with a custom configuration. +pub fn custom(config: &config::Config) -> Rocket { + Rocket::custom(config) +} diff --git a/lib/src/outcome.rs b/lib/src/outcome.rs index 1a94f9ec..38e17101 100644 --- a/lib/src/outcome.rs +++ b/lib/src/outcome.rs @@ -129,6 +129,29 @@ impl Outcome { } } + /// Unwraps the Outcome, yielding the contents of a Success. + /// + /// # Panics + /// + /// If the value is not `Success`, panics with the given `message`. + /// + /// # Examples + /// + /// ```rust + /// # use rocket::outcome::Outcome; + /// # use rocket::outcome::Outcome::*; + /// # + /// let x: Outcome = Success(10); + /// assert_eq!(x.expect("success value"), 10); + /// ``` + #[inline(always)] + pub fn expect(self, message: &str) -> S { + match self { + Success(val) => val, + _ => panic!("Outcome::expect() failed: {}", message) + } + } + /// Return true if this `Outcome` is a `Success`. /// /// # Examples diff --git a/lib/src/rocket.rs b/lib/src/rocket.rs index ab5cbeea..c2b9aa92 100644 --- a/lib/src/rocket.rs +++ b/lib/src/rocket.rs @@ -6,8 +6,8 @@ use std::process; use term_painter::Color::*; use term_painter::ToStyle; -use config; use logger; +use config::{self, Config}; use request::{Request, FormItems}; use data::Data; use response::Responder; @@ -20,7 +20,7 @@ use http::{Method, StatusCode}; use http::hyper::{HyperRequest, FreshHyperResponse}; use http::hyper::{HyperServer, HyperHandler, HyperSetCookie, header}; -/// The Rocket type used to mount routes and catchers and launch the +/// The main `Rocket` type: used to mount routes and catchers and launch the /// application. pub struct Rocket { address: String, @@ -32,6 +32,11 @@ pub struct Rocket { #[doc(hidden)] impl HyperHandler for Rocket { + // This function tries to hide all of the Hyper-ness from Rocket. It + // essentially converts Hyper types into Rocket types, then calls the + // `dispatch` function, which knows nothing about Hyper. Because responding + // depends on the `HyperResponse` type, this function does the actual + // response processing. fn handle<'h, 'k>(&self, hyp_req: HyperRequest<'h, 'k>, mut res: FreshHyperResponse<'h>) { @@ -115,6 +120,11 @@ impl Rocket { } } + /// Tries to find a `Responder` for a given `request`. It does this by + /// routing the request and calling the handler for each matching route + /// until one of the handlers returns success or failure. If a handler + /// returns a failure, or there are no matching handlers willing to accept + /// the request, this function returns an `Err` with the status code. #[doc(hidden)] pub fn dispatch<'r>(&self, request: &'r Request, mut data: Data) -> Result, StatusCode> { @@ -143,7 +153,11 @@ impl Rocket { Err(StatusCode::NotFound) } - // Call when no route was found. Returns true if there was a response. + // Attempts to send a response to the client by using the catcher for the + // given status code. If no catcher is found (including the defaults), the + // 500 internal server error catcher is used. If the catcher fails to + // respond, this function returns `false`. It returns `true` if a response + // was sucessfully sent to the client. #[doc(hidden)] pub fn handle_error<'r>(&self, code: StatusCode, @@ -153,7 +167,7 @@ impl Rocket { let catcher = self.catchers.get(&code.to_u16()).unwrap_or_else(|| { error_!("No catcher found for {}.", code); warn_!("Using internal server error catcher."); - self.catchers.get(&500).expect("500 Catcher") + self.catchers.get(&500).expect("500 catcher should exist!") }); if let Some(mut responder) = catcher.handle(Error::NoRoute, req).responder() { @@ -169,12 +183,129 @@ impl Rocket { let catcher = self.default_catchers.get(&code.to_u16()) .unwrap_or(self.default_catchers.get(&500).expect("500 default")); let responder = catcher.handle(Error::Internal, req).responder(); - responder.unwrap().respond(response).unwrap() + responder.unwrap().respond(response).expect("default catcher failed") } true } + /// Create a new `Rocket` application using the configuration information in + /// `Rocket.toml`. If the file does not exist or if there is an I/O error + /// reading the file, the defaults are used. See the + /// [config](/rocket/config/index.html) documentation for more information + /// on defaults. + /// + /// This method is typically called through the `rocket::ignite` alias. + /// + /// # Panics + /// + /// If there is an error parsing the `Rocket.toml` file, this functions + /// prints a nice error message and then exits the process. + /// + /// # Examples + /// + /// ```rust + /// # { + /// rocket::ignite() + /// # }; + /// ``` + pub fn ignite() -> Rocket { + // Note: init() will exit the process under config errors. + let (config, initted) = config::init(); + if initted { + logger::init(config.log_level); + } + + Rocket::custom(config) + } + + /// Creates a new `Rocket` application using the supplied custom + /// configuration information. Ignores the `Rocket.toml` file. + /// + /// This method is typically called through the `rocket::custom` alias. + /// + /// # Examples + /// + /// ```rust + /// use rocket::config::{Config, Environment}; + /// # use rocket::config::ConfigError; + /// + /// # fn try_config() -> Result<(), ConfigError> { + /// let config = Config::default_for(Environment::active()?, "/custom")? + /// .address("1.2.3.4".into()) + /// .port(9234); + /// + /// let app = rocket::custom(&config); + /// # Ok(()) + /// # } + /// ``` + pub fn custom(config: &Config) -> Rocket { + logger::init(config.log_level); + + info!("🔧 Configured for {}.", config.env); + info_!("listening: {}:{}", + White.paint(&config.address), + White.paint(&config.port)); + info_!("logging: {:?}", White.paint(config.log_level)); + info_!("session key: {}", White.paint(config.take_session_key().is_some())); + for (name, value) in config.extras() { + info_!("{} {}: {}", Yellow.paint("[extra]"), name, White.paint(value)); + } + + Rocket { + address: config.address.clone(), + port: config.port, + router: Router::new(), + default_catchers: catcher::defaults::get(), + catchers: catcher::defaults::get(), + } + } + + /// Mounts all of the routes in the supplied vector at the given `base` + /// path. Mounting a route with path `path` at path `base` makes the route + /// available at `base/path`. + /// + /// # Examples + /// + /// Use the `routes!` macro to mount routes created using the code + /// generation facilities. Requests to the `/hello/world` URI will be + /// dispatched to the `hi` route. + /// + /// ```rust + /// # #![feature(plugin)] + /// # #![plugin(rocket_codegen)] + /// # extern crate rocket; + /// # + /// #[get("/world")] + /// fn hi() -> &'static str { + /// "Hello!" + /// } + /// + /// fn main() { + /// # if false { // We don't actually want to launch the server in an example. + /// rocket::ignite().mount("/hello", routes![hi]) + /// # .launch() + /// # } + /// } + /// ``` + /// + /// Manually create a route named `hi` at path `"/world"` mounted at base + /// `"/hello"`. Requests to the `/hello/world` URI will be dispatched to the + /// `hi` route. + /// + /// ```rust + /// use rocket::{Request, Response, Route, Data}; + /// use rocket::http::Method::*; + /// + /// fn hi(_: &Request, _: Data) -> Response<'static> { + /// Response::success("Hello!") + /// } + /// + /// # if false { // We don't actually want to launch the server in an example. + /// rocket::ignite().mount("/hello", vec![Route::new(Get, "/world", hi)]) + /// # :.launch() + /// # } + /// ``` pub fn mount(mut self, base: &str, routes: Vec) -> Self { info!("🛰 {} '{}':", Magenta.paint("Mounting"), base); for mut route in routes { @@ -188,6 +319,7 @@ impl Rocket { self } + /// Registers all of the catchers in the supplied vector. pub fn catch(mut self, catchers: Vec) -> Self { info!("👾 {}:", Magenta.paint("Catchers")); for c in catchers { @@ -204,6 +336,22 @@ impl Rocket { self } + /// Starts the application server and begins listening for and dispatching + /// requests to mounted routes and catchers. + /// + /// # Panics + /// + /// If the server could not be started, this method prints the reason and + /// then exits the process. + /// + /// # Examples + /// + /// ```rust + /// # if false { + /// rocket::ignite().launch() + /// # } + /// ``` + #[cfg(not(feature = "testing"))] pub fn launch(self) { if self.router.has_collisions() { warn!("Route collisions detected!"); @@ -226,29 +374,4 @@ impl Rocket { server.handle(self).unwrap(); } - pub fn ignite() -> Rocket { - // Note: init() will exit the process under config errors. - let (config, initted) = config::init(); - if initted { - logger::init(config.log_level); - } - - info!("🔧 Configured for {}.", config.env); - info_!("listening: {}:{}", - White.paint(&config.address), - White.paint(&config.port)); - info_!("logging: {:?}", White.paint(config.log_level)); - info_!("session key: {}", White.paint(config.take_session_key().is_some())); - for (name, value) in config.extras() { - info_!("{} {}: {}", Yellow.paint("[extra]"), name, White.paint(value)); - } - - Rocket { - address: config.address.clone(), - port: config.port, - router: Router::new(), - default_catchers: catcher::defaults::get(), - catchers: catcher::defaults::get(), - } - } } diff --git a/lib/src/router/route.rs b/lib/src/router/route.rs index af3f5ee4..440f8f22 100644 --- a/lib/src/router/route.rs +++ b/lib/src/router/route.rs @@ -12,11 +12,17 @@ use request::Request; use http::{Method, ContentType}; use http::uri::{URI, URIBuf}; +/// A route: a method, its handler, path, rank, and format/content type. pub struct Route { + /// The method this route matches against. 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. pub path: URIBuf, - pub rank: isize, // Lower ranks have higher priorities. + /// The rank of this route. Lower ranks have higher priorities. + pub rank: isize, + /// The Content-Type this route matches against. pub content_type: ContentType, } @@ -27,18 +33,10 @@ fn default_rank(path: &str) -> isize { } impl Route { - pub fn ranked(rank: isize, m: Method, path: S, handler: Handler) -> Route - where S: AsRef - { - Route { - method: m, - path: URIBuf::from(path.as_ref()), - handler: handler, - rank: rank, - content_type: ContentType::any(), - } - } - + /// Creates a new route with the method, path, and handler. + /// + /// The rank of the route will be `0` if the path contains no dynamic + /// segments, and `1` if it does. pub fn new(m: Method, path: S, handler: Handler) -> Route where S: AsRef { @@ -51,6 +49,21 @@ impl Route { } } + /// Creates a new route with the given rank, method, path, and handler. + pub fn ranked(rank: isize, m: Method, path: S, handler: Handler) -> Route + where S: AsRef + { + Route { + method: m, + path: URIBuf::from(path.as_ref()), + handler: handler, + rank: rank, + content_type: ContentType::any(), + } + } + + /// 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 { @@ -60,6 +73,9 @@ impl Route { // FIXME: Decide whether a component has to be fully variable or not. That // is, whether you can have: /ab/ or even /:/ // TODO: Don't return a Vec...take in an &mut [&'a str] (no alloc!) + /// Given a URI, returns a vector of slices of that URI corresponding to the + /// dynamic segments in this route. + #[doc(hidden)] pub fn get_params<'a>(&self, uri: URI<'a>) -> Vec<&'a str> { let route_segs = self.path.as_uri().segments(); let uri_segs = uri.segments(); @@ -113,6 +129,7 @@ impl fmt::Debug for Route { } } +#[doc(hidden)] impl<'a> From<&'a StaticRouteInfo> for Route { fn from(info: &'a StaticRouteInfo) -> Route { let mut route = Route::new(info.method, info.path, info.handler);