Remove config global state. Use Responder::respond_to.

This commit includes two major changes to core:

  1. Configuration state is no longer global. The `config::active()`
     function has been removed. The active configuration can be
     retrieved via the `config` method on a `Rocket` instance.

  2. The `Responder` trait has changed. `Responder::respond(self)` has
     been removed in favor of `Responder::respond_to(self, &Request)`.
     This allows responders to dynamically adjust their response based
     on the incoming request.

Additionally, it includes the following changes to core and codegen:

  * The `Request::guard` method was added to allow for simple
    retrivial of request guards.
  * The `Request::limits` method was added to retrieve configured
    limits.
  * The `File` `Responder` implementation now uses a fixed size body
    instead of a chunked body.
  * The `Outcome::of<R: Responder>(R)` method was removed while
    `Outcome::from<R: Responder(&Request, R)` was added.
  * The unmounted and unmanaged limits are more cautious: they will only
    emit warnings when the `Rocket` receiver is known.

This commit includes one major change to contrib:

  1. To use contrib's templating, the fairing returned by
     `Template::fairing()` must be attached to the running Rocket
     instance.

Additionally, the `Display` implementation of `Template` was removed. To
directly render a template to a `String`, the new `Template::show`
method can be used.
This commit is contained in:
Sergio Benitez 2017-05-19 03:29:08 -07:00
parent 28a1ef0916
commit 9b955747e4
57 changed files with 803 additions and 735 deletions

View File

@ -8,8 +8,8 @@ use syntax::ext::base::{Annotatable, ExtCtxt};
use syntax::tokenstream::TokenTree;
use syntax::parse::token;
const ERR_PARAM: &'static str = "_error";
const REQ_PARAM: &'static str = "_request";
const ERR_PARAM: &'static str = "__err";
const REQ_PARAM: &'static str = "__req";
trait ErrorGenerateExt {
fn generate_fn_arguments(&self, &ExtCtxt, Ident, Ident) -> Vec<TokenTree>;
@ -71,7 +71,8 @@ pub fn error_decorator(ecx: &mut ExtCtxt,
$req_ident: &'_b ::rocket::Request)
-> ::rocket::response::Result<'_b> {
let user_response = $user_fn_name($fn_arguments);
let response = ::rocket::response::Responder::respond(user_response)?;
let response = ::rocket::response::Responder::respond_to(user_response,
$req_ident)?;
let status = ::rocket::http::Status::raw($code);
::rocket::response::Response::build().status(status).merge(response).ok()
}

View File

@ -78,7 +78,7 @@ impl RouteGenerateExt for RouteParams {
let mut items = ::rocket::request::FormItems::from($form_string);
let obj = match ::rocket::request::FromForm::from_form_items(items.by_ref()) {
Ok(v) => v,
Err(_) => return ::rocket::Outcome::Forward(_data)
Err(_) => return ::rocket::Outcome::Forward(__data)
};
if !items.exhaust() {
@ -106,7 +106,7 @@ impl RouteGenerateExt for RouteParams {
let ty = strip_ty_lifetimes(arg.ty.clone());
Some(quote_stmt!(ecx,
let $name: $ty =
match ::rocket::data::FromData::from_data(_req, _data) {
match ::rocket::data::FromData::from_data(__req, __data) {
::rocket::Outcome::Success(d) => d,
::rocket::Outcome::Forward(d) =>
return ::rocket::Outcome::Forward(d),
@ -120,9 +120,9 @@ impl RouteGenerateExt for RouteParams {
fn generate_query_statement(&self, ecx: &ExtCtxt) -> Option<Stmt> {
let param = self.query_param.as_ref();
let expr = quote_expr!(ecx,
match _req.uri().query() {
match __req.uri().query() {
Some(query) => query,
None => return ::rocket::Outcome::Forward(_data)
None => return ::rocket::Outcome::Forward(__data)
}
);
@ -149,13 +149,13 @@ impl RouteGenerateExt for RouteParams {
// Note: the `None` case shouldn't happen if a route is matched.
let ident = param.ident().prepend(PARAM_PREFIX);
let expr = match param {
Param::Single(_) => quote_expr!(ecx, match _req.get_param_str($i) {
Param::Single(_) => quote_expr!(ecx, match __req.get_param_str($i) {
Some(s) => <$ty as ::rocket::request::FromParam>::from_param(s),
None => return ::rocket::Outcome::Forward(_data)
None => return ::rocket::Outcome::Forward(__data)
}),
Param::Many(_) => quote_expr!(ecx, match _req.get_raw_segments($i) {
Param::Many(_) => quote_expr!(ecx, match __req.get_raw_segments($i) {
Some(s) => <$ty as ::rocket::request::FromSegments>::from_segments(s),
None => return ::rocket::Outcome::Forward(_data)
None => return ::rocket::Outcome::Forward(__data)
}),
};
@ -166,7 +166,7 @@ impl RouteGenerateExt for RouteParams {
Err(e) => {
println!(" => Failed to parse '{}': {:?}",
stringify!($original_ident), e);
return ::rocket::Outcome::Forward(_data)
return ::rocket::Outcome::Forward(__data)
}
};
).expect("declared param parsing statement"));
@ -195,10 +195,10 @@ impl RouteGenerateExt for RouteParams {
fn_param_statements.push(quote_stmt!(ecx,
#[allow(non_snake_case)]
let $ident: $ty = match
::rocket::request::FromRequest::from_request(_req) {
::rocket::request::FromRequest::from_request(__req) {
::rocket::outcome::Outcome::Success(v) => v,
::rocket::outcome::Outcome::Forward(_) =>
return ::rocket::Outcome::forward(_data),
return ::rocket::Outcome::forward(__data),
::rocket::outcome::Outcome::Failure((code, _)) => {
return ::rocket::Outcome::Failure(code)
},
@ -254,13 +254,13 @@ fn generic_route_decorator(known_method: Option<Spanned<Method>>,
// Allow the `unreachable_code` lint for those FromParam impls that have
// an `Error` associated type of !.
#[allow(unreachable_code)]
fn $route_fn_name<'_b>(_req: &'_b ::rocket::Request, _data: ::rocket::Data)
fn $route_fn_name<'_b>(__req: &'_b ::rocket::Request, __data: ::rocket::Data)
-> ::rocket::handler::Outcome<'_b> {
$param_statements
$query_statement
$data_statement
let responder = $user_fn_name($fn_arguments);
::rocket::handler::Outcome::of(responder)
::rocket::handler::Outcome::from(__req, responder)
}
).unwrap());

View File

@ -174,11 +174,11 @@ pub fn msg_and_help<'a, T: LintContext<'a>>(cx: &T,
note: &str,
help_sp: Option<Span>,
help: &str) {
let mut b = cx.struct_span_lint(lint, msg_sp, msg);
b.note(note);
// Be conservative. If we don't know the receiver, don't emit the warning.
if let Some(span) = help_sp {
b.span_help(span, help);
cx.struct_span_lint(lint, msg_sp, msg)
.note(note)
.span_help(span, help)
.emit()
}
b.emit();
}

View File

@ -18,8 +18,7 @@ tera_templates = ["tera", "templates"]
handlebars_templates = ["handlebars", "templates"]
# Internal use only.
templates = ["serde", "serde_json", "lazy_static_macro", "glob"]
lazy_static_macro = ["lazy_static"]
templates = ["serde", "serde_json", "glob"]
[dependencies]
rocket = { version = "0.2.6", path = "../lib/" }
@ -36,5 +35,4 @@ rmp-serde = { version = "^0.13", optional = true }
# Templating dependencies only.
handlebars = { version = "^0.26.1", optional = true }
glob = { version = "^0.2", optional = true }
lazy_static = { version = "^0.2", optional = true }
tera = { version = "^0.10", optional = true }

View File

@ -1,7 +1,6 @@
use std::ops::{Deref, DerefMut};
use std::io::Read;
use rocket::config;
use rocket::outcome::{Outcome, IntoOutcome};
use rocket::request::Request;
use rocket::data::{self, Data, FromData};
@ -80,6 +79,7 @@ impl<T> JSON<T> {
/// let my_json = JSON(string);
/// assert_eq!(my_json.into_inner(), "Hello".to_string());
/// ```
#[inline(always)]
pub fn into_inner(self) -> T {
self.0
}
@ -97,10 +97,7 @@ impl<T: DeserializeOwned> FromData for JSON<T> {
return Outcome::Forward(data);
}
let size_limit = config::active()
.and_then(|c| c.limits.get("json"))
.unwrap_or(LIMIT);
let size_limit = request.limits().get("json").unwrap_or(LIMIT);
serde_json::from_reader(data.open().take(size_limit))
.map(|val| JSON(val))
.map_err(|e| { error_!("Couldn't parse JSON body: {:?}", e); e })
@ -112,9 +109,9 @@ impl<T: DeserializeOwned> FromData for JSON<T> {
/// JSON and a fixed-size body with the serialized value. If serialization
/// fails, an `Err` of `Status::InternalServerError` is returned.
impl<T: Serialize> Responder<'static> for JSON<T> {
fn respond(self) -> response::Result<'static> {
fn respond_to(self, req: &Request) -> response::Result<'static> {
serde_json::to_string(&self.0).map(|string| {
content::JSON(string).respond().unwrap()
content::JSON(string).respond_to(req).unwrap()
}).map_err(|e| {
error_!("JSON failed to serialize: {:?}", e);
Status::InternalServerError
@ -125,12 +122,14 @@ impl<T: Serialize> Responder<'static> for JSON<T> {
impl<T> Deref for JSON<T> {
type Target = T;
#[inline(always)]
fn deref<'a>(&'a self) -> &'a T {
&self.0
}
}
impl<T> DerefMut for JSON<T> {
#[inline(always)]
fn deref_mut<'a>(&'a mut self) -> &'a mut T {
&mut self.0
}

View File

@ -1,4 +1,7 @@
#![feature(drop_types_in_const, macro_reexport)]
#![cfg_attr(feature = "templates", feature(conservative_impl_trait))]
#![cfg_attr(feature = "templates", feature(associated_consts))]
#![cfg_attr(feature = "templates", feature(struct_field_attributes))]
//! This crate contains officially sanctioned contributor libraries that provide
//! functionality commonly used by Rocket applications.
@ -37,10 +40,6 @@
#[macro_use] extern crate log;
#[macro_use] extern crate rocket;
#[cfg_attr(feature = "lazy_static_macro", macro_use)]
#[cfg(feature = "lazy_static_macro")]
extern crate lazy_static;
#[cfg(feature = "serde")]
extern crate serde;

View File

@ -3,7 +3,6 @@ extern crate rmp_serde;
use std::ops::{Deref, DerefMut};
use std::io::{Cursor, Read};
use rocket::config;
use rocket::outcome::{Outcome, IntoOutcome};
use rocket::request::Request;
use rocket::data::{self, Data, FromData};
@ -111,11 +110,8 @@ impl<T: DeserializeOwned> FromData for MsgPack<T> {
return Outcome::Forward(data);
}
let size_limit = config::active()
.and_then(|c| c.limits.get("msgpack"))
.unwrap_or(LIMIT);
let mut buf = Vec::new();
let size_limit = request.limits().get("msgpack").unwrap_or(LIMIT);
if let Err(e) = data.open().take(size_limit).read_to_end(&mut buf) {
let e = MsgPackError::InvalidDataRead(e);
error_!("Couldn't read request data: {:?}", e);
@ -132,7 +128,7 @@ impl<T: DeserializeOwned> FromData for MsgPack<T> {
/// Content-Type `MsgPack` and a fixed-size body with the serialization. If
/// serialization fails, an `Err` of `Status::InternalServerError` is returned.
impl<T: Serialize> Responder<'static> for MsgPack<T> {
fn respond(self) -> response::Result<'static> {
fn respond_to(self, _: &Request) -> response::Result<'static> {
rmp_serde::to_vec(&self.0).map_err(|e| {
error_!("MsgPack failed to serialize: {:?}", e);
Status::InternalServerError

View File

@ -0,0 +1,131 @@
use std::path::{Path, PathBuf};
use std::collections::HashMap;
use super::{Engines, TemplateInfo};
use super::glob;
use rocket::http::ContentType;
pub struct Context {
/// The root of the template directory.
pub root: PathBuf,
/// Mapping from template name to its information.
pub templates: HashMap<String, TemplateInfo>,
/// Mapping from template name to its information.
pub engines: Engines
}
impl Context {
pub fn initialize(root: PathBuf) -> Option<Context> {
let mut templates: HashMap<String, TemplateInfo> = HashMap::new();
for ext in Engines::ENABLED_EXTENSIONS {
let mut glob_path = root.join("**").join("*");
glob_path.set_extension(ext);
let glob_path = glob_path.to_str().expect("valid glob path string");
for path in glob(glob_path).unwrap().filter_map(Result::ok) {
let (name, data_type_str) = split_path(&root, &path);
if let Some(info) = templates.get(&*name) {
warn_!("Template name '{}' does not have a unique path.", name);
info_!("Existing path: {:?}", info.path);
info_!("Additional path: {:?}", path);
warn_!("Using existing path for template '{}'.", name);
continue;
}
let data_type = data_type_str.as_ref()
.map(|ext| ContentType::from_extension(ext))
.unwrap_or(ContentType::HTML);
templates.insert(name, TemplateInfo {
path: path.to_path_buf(),
extension: ext.to_string(),
data_type: data_type,
});
}
}
Engines::init(&templates).map(|engines| {
Context { root, templates, engines }
})
}
}
/// Removes the file path's extension or does nothing if there is none.
fn remove_extension<P: AsRef<Path>>(path: P) -> PathBuf {
let path = path.as_ref();
let stem = match path.file_stem() {
Some(stem) => stem,
None => return path.to_path_buf()
};
match path.parent() {
Some(parent) => parent.join(stem),
None => PathBuf::from(stem)
}
}
/// Splits a path into a name that may be used to identify the template, and the
/// template's data type, if any.
fn split_path(root: &Path, path: &Path) -> (String, Option<String>) {
let rel_path = path.strip_prefix(root).unwrap().to_path_buf();
let path_no_ext = remove_extension(&rel_path);
let data_type = path_no_ext.extension();
let mut name = remove_extension(&path_no_ext).to_string_lossy().into_owned();
// Ensure template name consistency on Windows systems
if cfg!(windows) {
name = name.replace("\\", "/");
}
(name, data_type.map(|d| d.to_string_lossy().into_owned()))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn template_path_index_html() {
for root in &["/", "/a/b/c/", "/a/b/c/d/", "/a/"] {
for filename in &["index.html.hbs", "index.html.tera"] {
let path = Path::new(root).join(filename);
let (name, data_type) = split_path(Path::new(root), &path);
assert_eq!(name, "index");
assert_eq!(data_type, Some("html".into()));
}
}
}
#[test]
fn template_path_subdir_index_html() {
for root in &["/", "/a/b/c/", "/a/b/c/d/", "/a/"] {
for sub in &["a/", "a/b/", "a/b/c/", "a/b/c/d/"] {
for filename in &["index.html.hbs", "index.html.tera"] {
let path = Path::new(root).join(sub).join(filename);
let (name, data_type) = split_path(Path::new(root), &path);
let expected_name = format!("{}index", sub);
assert_eq!(name, expected_name.as_str());
assert_eq!(data_type, Some("html".into()));
}
}
}
}
#[test]
fn template_path_doc_examples() {
fn name_for(path: &str) -> String {
split_path(Path::new("templates/"), &Path::new("templates/").join(path)).0
}
assert_eq!(name_for("index.html.hbs"), "index");
assert_eq!(name_for("index.tera"), "index");
assert_eq!(name_for("index.hbs"), "index");
assert_eq!(name_for("dir/index.hbs"), "dir/index");
assert_eq!(name_for("dir/index.html.tera"), "dir/index");
assert_eq!(name_for("index.template.html.hbs"), "index.template");
assert_eq!(name_for("subdir/index.template.html.hbs"), "subdir/index.template");
}
}

View File

@ -0,0 +1,72 @@
use std::collections::HashMap;
use super::serde::Serialize;
use super::TemplateInfo;
#[cfg(feature = "tera_templates")] use super::tera_templates::Tera;
#[cfg(feature = "handlebars_templates")] use super::handlebars_templates::Handlebars;
pub trait Engine: Send + Sync + 'static {
const EXT: &'static str;
fn init(templates: &[(&str, &TemplateInfo)]) -> Option<Self> where Self: Sized;
fn render<C: Serialize>(&self, name: &str, context: C) -> Option<String>;
}
pub struct Engines {
#[cfg(feature = "tera_templates")]
tera: Tera,
#[cfg(feature = "handlebars_templates")]
handlebars: Handlebars,
}
impl Engines {
pub const ENABLED_EXTENSIONS: &'static [&'static str] = &[
#[cfg(feature = "tera_templates")] Tera::EXT,
#[cfg(feature = "handlebars_templates")] Handlebars::EXT,
];
pub fn init(templates: &HashMap<String, TemplateInfo>) -> Option<Engines> {
fn inner<E: Engine>(templates: &HashMap<String, TemplateInfo>) -> Option<E> {
let named_templates = templates.iter()
.filter(|&(_, i)| i.extension == E::EXT)
.map(|(k, i)| (k.as_str(), i))
.collect::<Vec<_>>();
E::init(&*named_templates)
}
Some(Engines {
#[cfg(feature = "tera_templates")]
tera: match inner::<Tera>(templates) {
Some(tera) => tera,
None => return None
},
#[cfg(feature = "handlebars_templates")]
handlebars: match inner::<Handlebars>(templates) {
Some(hb) => hb,
None => return None
},
})
}
pub fn render<C>(&self, name: &str, info: &TemplateInfo, c: C) -> Option<String>
where C: Serialize
{
#[cfg(feature = "tera_templates")]
{
if info.extension == Tera::EXT {
return Engine::render(&self.tera, name, c);
}
}
#[cfg(feature = "handlebars_templates")]
{
if info.extension == Handlebars::EXT {
return Engine::render(&self.handlebars, name, c);
}
}
None
}
}

View File

@ -1,57 +1,40 @@
extern crate handlebars;
use super::serde::Serialize;
use super::TemplateInfo;
use super::{Engine, TemplateInfo};
use self::handlebars::Handlebars;
pub use self::handlebars::Handlebars;
static mut HANDLEBARS: Option<Handlebars> = None;
impl Engine for Handlebars {
const EXT: &'static str = "hbs";
pub const EXT: &'static str = "hbs";
// This function must be called a SINGLE TIME from A SINGLE THREAD for safety to
// hold here and in `render`.
pub unsafe fn register(templates: &[(&str, &TemplateInfo)]) -> bool {
if HANDLEBARS.is_some() {
error_!("Internal error: reregistering handlebars!");
return false;
}
let mut hb = Handlebars::new();
let mut success = true;
for &(name, info) in templates {
let path = &info.full_path;
if let Err(e) = hb.register_template_file(name, path) {
error_!("Handlebars template '{}' failed registry: {:?}", name, e);
success = false;
fn init(templates: &[(&str, &TemplateInfo)]) -> Option<Handlebars> {
let mut hb = Handlebars::new();
for &(name, info) in templates {
let path = &info.path;
if let Err(e) = hb.register_template_file(name, path) {
error!("Error in Handlebars template '{}'.", name);
info_!("{}", e);
info_!("Template path: '{}'.", path.to_string_lossy());
return None;
}
}
Some(hb)
}
HANDLEBARS = Some(hb);
success
}
pub fn render<T>(name: &str, _info: &TemplateInfo, context: &T) -> Option<String>
where T: Serialize
{
let hb = match unsafe { HANDLEBARS.as_ref() } {
Some(hb) => hb,
None => {
error_!("Internal error: `render` called before handlebars init.");
fn render<C: Serialize>(&self, name: &str, context: C) -> Option<String> {
if self.get_template(name).is_none() {
error_!("Handlebars template '{}' does not exist.", name);
return None;
}
};
if hb.get_template(name).is_none() {
error_!("Handlebars template '{}' does not exist.", name);
return None;
}
match hb.render(name, context) {
Ok(string) => Some(string),
Err(e) => {
error_!("Error rendering Handlebars template '{}': {}", name, e);
None
match self.render(name, &context) {
Ok(string) => Some(string),
Err(e) => {
error_!("Error rendering Handlebars template '{}': {}", name, e);
None
}
}
}
}

View File

@ -1,53 +0,0 @@
/// Returns a hashset with the extensions of all of the enabled template
/// engines from the set of template engined passed in.
macro_rules! engine_set {
($($feature:expr => $engine:ident),+,) => ({
type RegisterFn = for<'a, 'b> unsafe fn(&'a [(&'b str, &TemplateInfo)]) -> bool;
let mut set = Vec::new();
$(
#[cfg(feature = $feature)]
fn $engine(set: &mut Vec<(&'static str, RegisterFn)>) {
set.push(($engine::EXT, $engine::register));
}
#[cfg(not(feature = $feature))]
fn $engine(_: &mut Vec<(&'static str, RegisterFn)>) { }
$engine(&mut set);
)+
set
});
}
/// Renders the template named `name` with the given template info `info` and
/// context `ctxt` using one of the templates in the template set passed in. It
/// does this by checking if the template's extension matches the engine's
/// extension, and if so, calls the engine's `render` method. All of this only
/// happens for engine's that have been enabled as features by the user.
macro_rules! render_set {
($name:expr, $info:expr, $ctxt:expr, $($feature:expr => $engine:ident),+,) => ({
$(
#[cfg(feature = $feature)]
fn $engine<T: Serialize>(name: &str, info: &TemplateInfo, c: &T)
-> Option<Template> {
if info.extension == $engine::EXT {
let rendered = $engine::render(name, info, c);
return Some(Template(rendered, info.data_type.clone()));
}
None
}
#[cfg(not(feature = $feature))]
fn $engine<T: Serialize>(_: &str, _: &TemplateInfo, _: &T)
-> Option<Template> { None }
if let Some(template) = $engine($name, &$info, $ctxt) {
return template
}
)+
});
}

View File

@ -2,25 +2,28 @@ extern crate serde;
extern crate serde_json;
extern crate glob;
#[cfg(feature = "tera_templates")]
mod tera_templates;
#[cfg(feature = "handlebars_templates")]
mod handlebars_templates;
#[macro_use] mod macros;
#[cfg(feature = "tera_templates")] mod tera_templates;
#[cfg(feature = "handlebars_templates")] mod handlebars_templates;
mod engine;
mod context;
use self::engine::{Engine, Engines};
use self::context::Context;
use self::serde::Serialize;
use self::serde_json::{Value, to_value};
use self::glob::glob;
use std::borrow::Cow;
use std::path::{Path, PathBuf};
use std::collections::HashMap;
use std::fmt;
use rocket::config::{self, ConfigError};
use rocket::State;
use rocket::request::Request;
use rocket::fairing::{Fairing, AdHoc};
use rocket::response::{self, Content, Responder};
use rocket::http::{ContentType, Status};
const DEFAULT_TEMPLATE_DIR: &'static str = "templates";
/// The Template type implements generic support for template rendering in
/// Rocket.
///
@ -28,8 +31,9 @@ use rocket::http::{ContentType, Status};
/// the template directory. The template directory is configurable via the
/// `template_dir` configuration parameter and defaults to `templates/`. The
/// path set in `template_dir` should be relative to the Rocket configuration
/// file. See the [configuration chapter](https://rocket.rs/guide/overview/#configuration)
/// of the guide for more information on configuration.
/// file. See the [configuration
/// chapter](https://rocket.rs/guide/overview/#configuration) of the guide for
/// more information on configuration.
///
/// Templates are discovered according to their extension. At present, this
/// library supports the following templates and extensions:
@ -55,6 +59,13 @@ use rocket::http::{ContentType, Status};
/// type, and one for the template extension. This means that template
/// extensions should look like: `.html.hbs`, `.html.tera`, `.xml.hbs`, etc.
///
/// Template discovery is actualized by the template fairing, which itself is
/// created via the
/// [`Template::fairing()`](/rocket_contrib/struct.Template.html#method.fairing)
/// method. In order for _any_ templates to be rendered, the template fairing
/// must be [attached](/rocket/struct.Rocket.html#method.attach) to the running
/// Rocket instance.
///
/// Templates are rendered with the `render` method. The method takes in the
/// name of a template and a context to render the template with. The context
/// can be any type that implements `Serialize` from
@ -74,7 +85,25 @@ use rocket::http::{ContentType, Status};
/// features = ["handlebars_templates", "tera_templates"]
/// ```
///
/// The Template type implements Rocket's `Responder` trait, so it can be
/// Then, ensure that the template [fairing](/rocket/fairing/) is attached to
/// your Rocket application:
///
/// ```rust
/// extern crate rocket;
/// extern crate rocket_contrib;
///
/// use rocket_contrib::Template;
///
/// fn main() {
/// rocket::ignite()
/// // ...
/// .attach(Template::fairing())
/// // ...
/// # ;
/// }
/// ```
///
/// The `Template` type implements Rocket's `Responder` trait, so it can be
/// returned from a request handler directly:
///
/// ```rust,ignore
@ -84,49 +113,68 @@ use rocket::http::{ContentType, Status};
/// Template::render("index", &context)
/// }
/// ```
// Fields are: (optionally rendered template, template extension)
#[derive(Debug)]
pub struct Template(Option<String>, Option<String>);
pub struct Template {
name: Cow<'static, str>,
value: Option<Value>
}
#[derive(Debug)]
pub struct TemplateInfo {
/// The complete path, including `template_dir`, to this template.
full_path: PathBuf,
/// The complete path, without `template_dir`, to this template.
path: PathBuf,
/// The extension for the engine of this template.
extension: String,
/// The extension before the engine extension in the template, if any.
data_type: Option<String>
}
const DEFAULT_TEMPLATE_DIR: &'static str = "templates";
lazy_static! {
static ref TEMPLATES: HashMap<String, TemplateInfo> = discover_templates();
static ref TEMPLATE_DIR: PathBuf = {
let default_dir_path = config::active().ok_or(ConfigError::NotFound)
.map(|config| config.root().join(DEFAULT_TEMPLATE_DIR))
.map_err(|_| {
warn_!("No configuration is active!");
warn_!("Using default template directory: {:?}", DEFAULT_TEMPLATE_DIR);
})
.unwrap_or(PathBuf::from(DEFAULT_TEMPLATE_DIR));
config::active().ok_or(ConfigError::NotFound)
.and_then(|config| config.get_str("template_dir"))
.map(|user_dir| PathBuf::from(user_dir))
.map_err(|e| {
if !e.is_not_found() {
e.pretty_print();
warn_!("Using default directory '{:?}'", default_dir_path);
}
})
.unwrap_or(default_dir_path)
};
data_type: ContentType
}
impl Template {
/// Returns a fairing that intializes and maintains templating state.
///
/// This fairing _must_ be attached to any `Rocket` instance that wishes to
/// render templates. Failure to attach this fairing will result in a
/// "Uninitialized template context: missing fairing." error message when a
/// template is attempted to be rendered.
///
/// # Example
///
/// To attach this fairing, simple call `attach` on the application's
/// `Rocket` instance with `Template::fairing()`:
///
/// ```rust
/// extern crate rocket;
/// extern crate rocket_contrib;
///
/// use rocket_contrib::Template;
///
/// fn main() {
/// rocket::ignite()
/// // ...
/// .attach(Template::fairing())
/// // ...
/// # ;
/// }
/// ```
pub fn fairing() -> impl Fairing {
AdHoc::on_attach(|rocket| {
let mut template_root = rocket.config().root().join(DEFAULT_TEMPLATE_DIR);
match rocket.config().get_str("template_dir") {
Ok(dir) => template_root = rocket.config().root().join(dir),
Err(ref e) if !e.is_not_found() => {
e.pretty_print();
warn_!("Using default directory '{:?}'", template_root);
}
Err(_) => { }
};
match Context::initialize(template_root) {
Some(ctxt) => Ok(rocket.manage(ctxt)),
None => Err(rocket)
}
})
}
/// Render the template named `name` with the context `context`. The
/// `context` can be of any type that implements `Serialize`. This is
/// typically a `HashMap` or a custom `struct`.
@ -141,30 +189,73 @@ impl Template {
/// let mut context = HashMap::new();
///
/// # context.insert("test", "test");
/// let template = Template::render("index", &context);
/// # assert_eq!(template.to_string(), "");
pub fn render<S, T>(name: S, context: &T) -> Template
where S: AsRef<str>, T: Serialize
/// # #[allow(unused_variables)]
/// let template = Template::render("index", context);
#[inline]
pub fn render<S, C>(name: S, context: C) -> Template
where S: Into<Cow<'static, str>>, C: Serialize
{
let name = name.as_ref();
let template = TEMPLATES.get(name);
if template.is_none() {
let names: Vec<_> = TEMPLATES.keys().map(|s| s.as_str()).collect();
Template { name: name.into(), value: to_value(context).ok() }
}
/// Render the template named `name` located at the path `root` with the
/// context `context` into a `String`. This method is _very slow_ and should
/// **not** be used in any running Rocket application. This method should
/// only be used during testing to validate `Template` responses. For other
/// uses, use [`render`](#method.render) instead.
///
/// The `context` can be of any type that implements `Serialize`. This is
/// typically a `HashMap` or a custom `struct`. The path `root` can be
/// relative, in which case it is relative to the current working directory,
/// or absolute.
///
/// Returns `Some` if the template could be rendered. Otherwise, returns
/// `None`. If rendering fails, error output is printed to the console.
///
/// # Example
///
/// ```rust
/// use std::collections::HashMap;
/// use rocket_contrib::Template;
///
/// // Create a `context`. Here, just an empty `HashMap`.
/// let mut context = HashMap::new();
///
/// # context.insert("test", "test");
/// # #[allow(unused_variables)]
/// let template = Template::show("templates/", "index", context);
#[inline]
pub fn show<P, S, C>(root: P, name: S, context: C) -> Option<String>
where P: AsRef<Path>, S: Into<Cow<'static, str>>, C: Serialize
{
let root = root.as_ref().to_path_buf();
Context::initialize(root).and_then(|ctxt| {
Template::render(name, context).finalize(&ctxt).ok().map(|v| v.0)
})
}
#[inline(always)]
fn finalize(self, ctxt: &Context) -> Result<(String, ContentType), Status> {
let name = &*self.name;
let info = ctxt.templates.get(name).ok_or_else(|| {
let ts: Vec<_> = ctxt.templates.keys().map(|s| s.as_str()).collect();
error_!("Template '{}' does not exist.", name);
info_!("Known templates: {}", names.join(","));
info_!("Searched in '{:?}'.", *TEMPLATE_DIR);
return Template(None, None);
}
info_!("Known templates: {}", ts.join(","));
info_!("Searched in '{:?}'.", ctxt.root);
Status::InternalServerError
})?;
// Keep this set in-sync with the `engine_set` invocation. The macro
// `return`s a `Template` if the extenion in `template` matches an
// engine in the set. Otherwise, control will fall through.
render_set!(name, template.unwrap(), context,
"tera_templates" => tera_templates,
"handlebars_templates" => handlebars_templates,
);
let value = self.value.ok_or_else(|| {
error_!("The provided template context failed to serialize.");
Status::InternalServerError
})?;
unreachable!("A template extension was discovered but not rendered.")
let string = ctxt.engines.render(name, &info, value).ok_or_else(|| {
error_!("Template '{}' failed to render.", name);
Status::InternalServerError
})?;
Ok((string, info.data_type.clone()))
}
}
@ -172,155 +263,15 @@ impl Template {
/// extension and a fixed-size body containing the rendered template. If
/// rendering fails, an `Err` of `Status::InternalServerError` is returned.
impl Responder<'static> for Template {
fn respond(self) -> response::Result<'static> {
let content_type = match self.1 {
Some(ref ext) => ContentType::from_extension(ext),
None => ContentType::HTML
};
fn respond_to(self, req: &Request) -> response::Result<'static> {
let ctxt = req.guard::<State<Context>>().succeeded().ok_or_else(|| {
error_!("Uninitialized template context: missing fairing.");
info_!("To use templates, you must attach `Template::fairing()`.");
info_!("See the `Template` documentation for more information.");
Status::InternalServerError
})?;
match self.0 {
Some(render) => Content(content_type, render).respond(),
None => Err(Status::InternalServerError)
}
}
}
/// Renders `self`. If the template cannot be rendered, nothing is written.
impl fmt::Display for Template {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self.0 {
Some(ref render) => render.fmt(f),
None => Ok(())
}
}
}
/// Removes the file path's extension or does nothing if there is none.
fn remove_extension<P: AsRef<Path>>(path: P) -> PathBuf {
let path = path.as_ref();
let stem = match path.file_stem() {
Some(stem) => stem,
None => return path.to_path_buf()
};
match path.parent() {
Some(parent) => parent.join(stem),
None => PathBuf::from(stem)
}
}
/// Splits a path into a relative path from TEMPLATE_DIR, a name that
/// may be used to identify the template, and the template's data type.
fn split_path(path: &Path) -> (PathBuf, String, Option<String>) {
let rel_path = path.strip_prefix(&*TEMPLATE_DIR).unwrap().to_path_buf();
let path_no_ext = remove_extension(&rel_path);
let data_type = path_no_ext.extension();
let mut name = remove_extension(&path_no_ext).to_string_lossy().into_owned();
// Ensure template name consistency on Windows systems
if cfg!(windows) {
name = name.replace("\\", "/");
}
(rel_path, name, data_type.map(|d| d.to_string_lossy().into_owned()))
}
/// Returns a HashMap of `TemplateInfo`'s for all of the templates in
/// `TEMPLATE_DIR`. Templates are all files that match one of the extensions for
/// engine's in `engine_set`.
///
/// **WARNING:** This function should be called ONCE from a SINGLE THREAD.
fn discover_templates() -> HashMap<String, TemplateInfo> {
// Keep this set in-sync with the `render_set` invocation.
let engines = engine_set![
"tera_templates" => tera_templates,
"handlebars_templates" => handlebars_templates,
];
let mut templates: HashMap<String, TemplateInfo> = HashMap::new();
for &(ext, _) in &engines {
let mut glob_path: PathBuf = TEMPLATE_DIR.join("**").join("*");
glob_path.set_extension(ext);
for path in glob(glob_path.to_str().unwrap()).unwrap().filter_map(Result::ok) {
let (rel_path, name, data_type) = split_path(&path);
if let Some(info) = templates.get(&*name) {
warn_!("Template name '{}' does not have a unique path.", name);
info_!("Existing path: {:?}", info.full_path);
info_!("Additional path: {:?}", path);
warn_!("Using existing path for template '{}'.", name);
continue;
}
templates.insert(name, TemplateInfo {
full_path: path.to_path_buf(),
path: rel_path,
extension: ext.to_string(),
data_type: data_type,
});
}
}
for &(ext, register_fn) in &engines {
let named_templates = templates.iter()
.filter(|&(_, i)| i.extension == ext)
.map(|(k, i)| (k.as_str(), i))
.collect::<Vec<_>>();
unsafe { register_fn(&*named_templates); }
};
templates
}
#[cfg(test)]
mod tests {
use super::*;
/// Combines a `relative_path` and the `TEMPLATE_DIR` path into a full path.
fn template_path(relative_path: &str) -> PathBuf {
let mut path = PathBuf::from(&*TEMPLATE_DIR);
path.push(relative_path);
path
}
/// Returns the template system name, given a relative path to a file.
fn relative_path_to_name(relative_path: &str) -> String {
let path = template_path(relative_path);
let (_, name, _) = split_path(&path);
name
}
#[test]
fn template_path_index_html() {
let path = template_path("index.html.hbs");
let (rel_path, name, data_type) = split_path(&path);
assert_eq!(rel_path.to_string_lossy(), "index.html.hbs");
assert_eq!(name, "index");
assert_eq!(data_type, Some("html".to_owned()));
}
#[test]
fn template_path_subdir_index_html() {
let path = template_path("subdir/index.html.hbs");
let (rel_path, name, data_type) = split_path(&path);
assert_eq!(rel_path.to_string_lossy(), "subdir/index.html.hbs");
assert_eq!(name, "subdir/index");
assert_eq!(data_type, Some("html".to_owned()));
}
#[test]
fn template_path_doc_examples() {
assert_eq!(relative_path_to_name("index.html.hbs"), "index");
assert_eq!(relative_path_to_name("index.tera"), "index");
assert_eq!(relative_path_to_name("index.hbs"), "index");
assert_eq!(relative_path_to_name("dir/index.hbs"), "dir/index");
assert_eq!(relative_path_to_name("dir/index.html.tera"), "dir/index");
assert_eq!(relative_path_to_name("index.template.html.hbs"),
"index.template");
assert_eq!(relative_path_to_name("subdir/index.template.html.hbs"),
"subdir/index.template");
let (render, content_type) = self.finalize(&ctxt)?;
Content(content_type, render).respond_to(req)
}
}

View File

@ -1,67 +1,52 @@
extern crate tera;
use super::serde::Serialize;
use super::TemplateInfo;
use super::{Engine, TemplateInfo};
use self::tera::Tera;
pub use self::tera::Tera;
static mut TERA: Option<Tera> = None;
impl Engine for Tera {
const EXT: &'static str = "tera";
pub const EXT: &'static str = "tera";
fn init(templates: &[(&str, &TemplateInfo)]) -> Option<Tera> {
// Create the Tera instance.
let mut tera = Tera::default();
let ext = [".html.tera", ".htm.tera", ".xml.tera", ".html", ".htm", ".xml"];
tera.autoescape_on(ext.to_vec());
// This function must be called a SINGLE TIME from A SINGLE THREAD for safety to
// hold here and in `render`.
pub unsafe fn register(templates: &[(&str, &TemplateInfo)]) -> bool {
if TERA.is_some() {
error_!("Internal error: reregistering Tera!");
return false;
}
// Collect into a tuple of (name, path) for Tera.
let tera_templates = templates.iter()
.map(|&(name, info)| (&info.path, Some(name)))
.collect::<Vec<_>>();
let mut tera = Tera::default();
let ext = [".html.tera", ".htm.tera", ".xml.tera", ".html", ".htm", ".xml"];
tera.autoescape_on(ext.to_vec());
// Collect into a tuple of (name, path) for Tera.
let tera_templates = templates.iter()
.map(|&(name, info)| (&info.full_path, Some(name)))
.collect::<Vec<_>>();
// Finally try to tell Tera about all of the templates.
let mut success = true;
if let Err(e) = tera.add_template_files(tera_templates) {
error_!("Failed to initialize Tera templates: {:?}", e);
success = false;
}
TERA = Some(tera);
success
}
pub fn render<T>(name: &str, _: &TemplateInfo, context: &T) -> Option<String>
where T: Serialize
{
let tera = match unsafe { TERA.as_ref() } {
Some(tera) => tera,
None => {
error_!("Internal error: `render` called before Tera init.");
return None;
}
};
if tera.get_template(name).is_err() {
error_!("Tera template '{}' does not exist.", name);
return None;
};
match tera.render(name, context) {
Ok(string) => Some(string),
Err(e) => {
error_!("Error rendering Tera template '{}'.", name);
// Finally try to tell Tera about all of the templates.
if let Err(e) = tera.add_template_files(tera_templates) {
error!("Failed to initialize Tera templating.");
for error in e.iter().skip(1) {
error_!("{}.", error);
info_!("{}.", error);
}
return None
}
None
Some(tera)
}
fn render<C: Serialize>(&self, name: &str, context: C) -> Option<String> {
if self.get_template(name).is_err() {
error_!("Tera template '{}' does not exist.", name);
return None;
};
match self.render(name, &context) {
Ok(string) => Some(string),
Err(e) => {
error_!("Error rendering Tera template '{}'.", name);
for error in e.iter().skip(1) {
error_!("{}.", error);
}
None
}
}
}
}

View File

@ -2,18 +2,13 @@ extern crate rocket;
extern crate rocket_contrib;
use std::env;
use rocket::config::Config;
use rocket::config::Environment::*;
use std::path::PathBuf;
fn init() {
fn template_root() -> PathBuf {
let cwd = env::current_dir().expect("current working directory");
let tests_dir = cwd.join("tests");
let config = Config::build(Development).root(tests_dir).unwrap();
rocket::custom(config, true);
cwd.join("tests").join("templates")
}
// FIXME: Do something about overlapping configs.
#[cfg(feature = "tera_templates")]
mod tera_tests {
use super::*;
@ -27,19 +22,17 @@ mod tera_tests {
#[test]
fn test_tera_templates() {
init();
let mut map = HashMap::new();
map.insert("title", "_test_");
map.insert("content", "<script />");
// Test with a txt file, which shouldn't escape.
let template = Template::render("tera/txt_test", &map);
assert_eq!(&template.to_string(), UNESCAPED_EXPECTED);
let template = Template::show(template_root(), "tera/txt_test", &map);
assert_eq!(template, Some(UNESCAPED_EXPECTED.into()));
// Now with an HTML file, which should.
let template = Template::render("tera/html_test", &map);
assert_eq!(&template.to_string(), ESCAPED_EXPECTED);
let template = Template::show(template_root(), "tera/html_test", &map);
assert_eq!(template, Some(ESCAPED_EXPECTED.into()));
}
}
@ -54,15 +47,13 @@ mod handlebars_tests {
#[test]
fn test_handlebars_templates() {
init();
let mut map = HashMap::new();
map.insert("title", "_test_");
map.insert("content", "<script /> hi");
// Test with a txt file, which shouldn't escape.
let template = Template::render("hbs/test", &map);
assert_eq!(&template.to_string(), EXPECTED);
let template = Template::show(template_root(), "hbs/test", &map);
assert_eq!(template, Some(EXPECTED.into()));
}
}

View File

@ -1,10 +0,0 @@
#![feature(plugin)]
#![plugin(rocket_codegen)]
extern crate rocket;
#[get("/")]
#[allow(unmounted_route)]
pub fn hello() -> &'static str {
"Hello, world!"
}

View File

@ -2,9 +2,8 @@
#![plugin(rocket_codegen)]
extern crate rocket;
extern crate config;
// This example's illustration is the Rocket.toml file.
fn main() {
rocket::ignite().mount("/hello", routes![config::hello]).launch();
rocket::ignite().launch();
}

View File

@ -1,21 +1,22 @@
extern crate rocket;
extern crate config as lib;
use rocket::config::{self, Environment};
use rocket::http::Method;
use rocket::{self, State};
use rocket::fairing::AdHoc;
use rocket::config::{self, Config, Environment};
use rocket::http::{Method, Status};
use rocket::LoggingLevel;
use rocket::testing::MockRequest;
pub fn test_config(environment: Environment) {
// Manually set the config environment variable. Rocket will initialize the
// environment in `ignite()`.
::std::env::set_var("ROCKET_ENV", environment.to_string());
rocket::ignite().mount("/hello", routes![lib::hello]);
struct LocalConfig(Config);
// Get the active environment and ensure that it matches our expectations.
let config = config::active().unwrap();
match environment {
Environment::Development => {
#[get("/check_config")]
fn check_config(config: State<LocalConfig>) -> Option<()> {
let environment = match ::std::env::var("ROCKET_ENV") {
Ok(name) => name,
Err(_) => return None
};
let config = &config.0;
match &*environment {
"development" => {
assert_eq!(config.address, "localhost".to_string());
assert_eq!(config.port, 8000);
assert_eq!(config.workers, 1);
@ -25,7 +26,7 @@ pub fn test_config(environment: Environment) {
assert_eq!(config.get_str("hi"), Ok("Hello!"));
assert_eq!(config.get_bool("is_extra"), Ok(true));
}
Environment::Staging => {
"staging" => {
assert_eq!(config.address, "0.0.0.0".to_string());
assert_eq!(config.port, 80);
assert_eq!(config.workers, 8);
@ -33,7 +34,7 @@ pub fn test_config(environment: Environment) {
assert_eq!(config.environment, config::Environment::Staging);
assert_eq!(config.extras().count(), 0);
}
Environment::Production => {
"production" => {
assert_eq!(config.address, "0.0.0.0".to_string());
assert_eq!(config.port, 80);
assert_eq!(config.workers, 12);
@ -41,13 +42,30 @@ pub fn test_config(environment: Environment) {
assert_eq!(config.environment, config::Environment::Production);
assert_eq!(config.extras().count(), 0);
}
_ => {
panic!("Unknown environment in envvar: {}", environment);
}
}
Some(())
}
pub fn test_hello() {
let rocket = rocket::ignite().mount("/hello", routes![lib::hello]);
let mut request = MockRequest::new(Method::Get, "/hello");
let mut response = request.dispatch_with(&rocket);
pub fn test_config(environment: Environment) {
// Manually set the config environment variable. Rocket will initialize the
// environment in `ignite()`. We'll read this back in the handler to config.
::std::env::set_var("ROCKET_ENV", environment.to_string());
assert_eq!(response.body_string(), Some("Hello, world!".into()));
// FIXME: launch fairings aren't run during tests since...the Rocket isn't
// being launch
let rocket = rocket::ignite()
.attach(AdHoc::on_attach(|rocket| {
println!("Attaching local config.");
let config = rocket.config().clone();
Ok(rocket.manage(LocalConfig(config)))
}))
.mount("/", routes![check_config]);
let mut request = MockRequest::new(Method::Get, "/check_config");
let response = request.dispatch_with(&rocket);
assert_eq!(response.status(), Status::Ok);
}

View File

@ -8,5 +8,4 @@ mod common;
#[test]
fn test_development_config() {
common::test_config(rocket::config::Environment::Development);
common::test_hello();
}

View File

@ -8,5 +8,4 @@ mod common;
#[test]
fn test_production_config() {
common::test_config(rocket::config::Environment::Production);
common::test_hello();
}

View File

@ -8,5 +8,4 @@ mod common;
#[test]
fn test_staging_config() {
common::test_config(rocket::config::Environment::Staging);
common::test_hello();
}

View File

@ -36,6 +36,10 @@ fn index(cookies: Cookies) -> Template {
Template::render("index", &context)
}
fn main() {
rocket::ignite().mount("/", routes![submit, index]).launch();
fn rocket() -> rocket::Rocket {
rocket::ignite().mount("/", routes![submit, index]).attach(Template::fairing())
}
fn main() {
rocket().launch();
}

View File

@ -5,9 +5,11 @@ use rocket::testing::MockRequest;
use rocket::http::*;
use rocket_contrib::Template;
const TEMPLATE_ROOT: &'static str = "templates/";
#[test]
fn test_submit() {
let rocket = rocket::ignite().mount("/", routes![super::index, super::submit]);
let rocket = rocket();
let mut request = MockRequest::new(Method::Post, "/submit")
.header(ContentType::Form)
.body("message=Hello from Rocket!");
@ -21,7 +23,7 @@ fn test_submit() {
}
fn test_body(optional_cookie: Option<Cookie<'static>>, expected_body: String) {
let rocket = rocket::ignite().mount("/", routes![super::index, super::submit]);
let rocket = rocket();
let mut request = MockRequest::new(Method::Get, "/");
// Attach a cookie if one is given.
@ -38,16 +40,16 @@ fn test_body(optional_cookie: Option<Cookie<'static>>, expected_body: String) {
fn test_index() {
// Render the template with an empty context to test against.
let mut context: HashMap<&str, &str> = HashMap::new();
let template = Template::render("index", &context);
let template = Template::show(TEMPLATE_ROOT, "index", &context).unwrap();
// Test the route without sending the "message" cookie.
test_body(None, template.to_string());
test_body(None, template);
// Render the template with a context that contains the message.
context.insert("message", "Hello from Rocket!");
// Test the route with the "message" cookie.
let cookie = Cookie::new("message", "Hello from Rocket!");
let template = Template::render("index", &context);
test_body(Some(cookie), template.to_string());
let template = Template::show(TEMPLATE_ROOT, "index", &context).unwrap();
test_body(Some(cookie), template);
}

View File

@ -72,7 +72,7 @@ fn rocket() -> rocket::Rocket {
let token_val = rocket.config().get_int("token").unwrap_or(-1);
Ok(rocket.manage(Token(token_val)))
}))
.attach(AdHoc::on_launch(|rocket| {
.attach(AdHoc::on_launch(|_| {
println!("Rocket is about to launch!");
}))
.attach(AdHoc::on_request(|req, _| {

View File

@ -40,9 +40,13 @@ fn not_found(req: &Request) -> Template {
Template::render("error/404", &map)
}
fn main() {
fn rocket() -> rocket::Rocket {
rocket::ignite()
.mount("/", routes![index, get])
.attach(Template::fairing())
.catch(errors![not_found])
.launch();
}
fn main() {
rocket().launch();
}

View File

@ -1,16 +1,15 @@
use rocket;
use super::rocket;
use rocket::testing::MockRequest;
use rocket::http::Method::*;
use rocket::http::Status;
use rocket::Response;
use rocket_contrib::Template;
const TEMPLATE_ROOT: &'static str = "templates/";
macro_rules! run_test {
($req:expr, $test_fn:expr) => ({
let rocket = rocket::ignite()
.mount("/", routes![super::index, super::get])
.catch(errors![super::not_found]);
let rocket = rocket();
let mut req = $req;
$test_fn(req.dispatch_with(&rocket));
})
@ -25,8 +24,8 @@ fn test_root() {
assert_eq!(response.status(), Status::SeeOther);
assert!(response.body().is_none());
let location_headers: Vec<_> = response.headers().get("Location").collect();
assert_eq!(location_headers, vec!["/hello/Unknown"]);
let location: Vec<_> = response.headers().get("Location").collect();
assert_eq!(location, vec!["/hello/Unknown"]);
});
}
@ -36,10 +35,10 @@ fn test_root() {
run_test!(req, |mut response: Response| {
let mut map = ::std::collections::HashMap::new();
map.insert("path", "/");
let expected_body = Template::render("error/404", &map).to_string();
let expected = Template::show(TEMPLATE_ROOT, "error/404", &map).unwrap();
assert_eq!(response.status(), Status::NotFound);
assert_eq!(response.body_string(), Some(expected_body));
assert_eq!(response.body_string(), Some(expected));
});
}
}
@ -56,7 +55,7 @@ fn test_name() {
items: vec!["One", "Two", "Three"].iter().map(|s| s.to_string()).collect()
};
let expected = Template::render("index", &context).to_string();
let expected = Template::show(TEMPLATE_ROOT, "index", &context).unwrap();
assert_eq!(response.body_string(), Some(expected));
});
}
@ -70,7 +69,8 @@ fn test_404() {
let mut map = ::std::collections::HashMap::new();
map.insert("path", "/hello/");
let expected = Template::render("error/404", &map).to_string();
let expected = Template::show(TEMPLATE_ROOT, "error/404", &map).unwrap();
assert_eq!(response.body_string(), Some(expected));
});
}

View File

@ -9,7 +9,7 @@
<h3>Here are your items:</h3>
<ul>
{{#each items}}
<li>{{this}}</li>
<li>{{this}}</li>
{{/each}}
</ul>

View File

@ -17,13 +17,13 @@ fn forward(_req: &Request, data: Data) -> Outcome<'static> {
Outcome::forward(data)
}
fn hi(_req: &Request, _: Data) -> Outcome<'static> {
Outcome::of("Hello!")
fn hi(req: &Request, _: Data) -> Outcome<'static> {
Outcome::from(req, "Hello!")
}
fn name<'a>(req: &'a Request, _: Data) -> Outcome<'a> {
let param = req.get_param::<&'a RawStr>(0);
Outcome::of(param.map(|r| r.as_str()).unwrap_or("unnamed"))
Outcome::from(req, param.map(|r| r.as_str()).unwrap_or("unnamed"))
}
fn echo_url(req: &Request, _: Data) -> Outcome<'static> {
@ -32,7 +32,7 @@ fn echo_url(req: &Request, _: Data) -> Outcome<'static> {
.split_at(6)
.1;
Outcome::of(RawStr::from_str(param).url_decode())
Outcome::from(req, RawStr::from_str(param).url_decode())
}
fn upload<'r>(req: &'r Request, data: Data) -> Outcome<'r> {
@ -44,7 +44,7 @@ fn upload<'r>(req: &'r Request, data: Data) -> Outcome<'r> {
let file = File::create("/tmp/upload.txt");
if let Ok(mut file) = file {
if let Ok(n) = io::copy(&mut data.open(), &mut file) {
return Outcome::of(format!("OK: {} bytes uploaded.", n));
return Outcome::from(req, format!("OK: {} bytes uploaded.", n));
}
println!(" => Failed copying.");
@ -55,12 +55,13 @@ fn upload<'r>(req: &'r Request, data: Data) -> Outcome<'r> {
}
}
fn get_upload(_: &Request, _: Data) -> Outcome<'static> {
Outcome::of(File::open("/tmp/upload.txt").ok())
fn get_upload(req: &Request, _: Data) -> Outcome<'static> {
Outcome::from(req, File::open("/tmp/upload.txt").ok())
}
fn not_found_handler<'r>(_: Error, req: &'r Request) -> response::Result<'r> {
Custom(Status::NotFound, format!("Couldn't find: {}", req.uri())).respond()
let res = Custom(Status::NotFound, format!("Couldn't find: {}", req.uri()));
res.respond_to(req)
}
fn rocket() -> rocket::Rocket {

View File

@ -32,7 +32,7 @@ impl<'a, 'r> FromRequest<'a, 'r> for Conn {
type Error = ();
fn from_request(request: &'a Request<'r>) -> request::Outcome<Conn, ()> {
let pool = match <State<Pool> as FromRequest>::from_request(request) {
let pool = match request.guard::<State<Pool>>() {
Outcome::Success(pool) => pool,
Outcome::Failure(e) => return Outcome::Failure(e),
Outcome::Forward(_) => return Outcome::Forward(()),

View File

@ -76,5 +76,6 @@ fn main() {
.manage(db::init_pool())
.mount("/", routes![index, static_files::all])
.mount("/todo/", routes![new, toggle, delete])
.attach(Template::fairing())
.launch();
}

View File

@ -26,7 +26,7 @@
class="u-full-width {% if msg %}field-{{msg.0}}{% endif %}" />
{% if msg %}
<small class="field-{{msg.0}}-msg">
{{ msg.1 }}
{{ msg.1 }}
</small>
{% endif %}
</div>

View File

@ -83,11 +83,12 @@ impl Catcher {
/// use rocket::http::Status;
///
/// fn handle_404<'r>(_: Error, req: &'r Request) -> Result<'r> {
/// Custom(Status::NotFound, format!("Couldn't find: {}", req.uri())).respond()
/// let res = Custom(Status::NotFound, format!("404: {}", req.uri()));
/// res.respond_to(req)
/// }
///
/// fn handle_500<'r>(_: Error, _: &'r Request) -> Result<'r> {
/// "Whoops, we messed up!".respond()
/// fn handle_500<'r>(_: Error, req: &'r Request) -> Result<'r> {
/// "Whoops, we messed up!".respond_to(req)
/// }
///
/// let not_found_catcher = Catcher::new(404, handle_404);
@ -156,10 +157,10 @@ macro_rules! default_errors {
let mut map = HashMap::new();
$(
fn $fn_name<'r>(_: Error, _r: &'r Request) -> response::Result<'r> {
fn $fn_name<'r>(_: Error, req: &'r Request) -> response::Result<'r> {
status::Custom(Status::from_code($code).unwrap(),
content::HTML(error_page_template!($code, $name, $description))
).respond()
).respond_to(req)
}
map.insert($code, Catcher::new_default($code, $fn_name));

View File

@ -57,7 +57,7 @@ macro_rules! config_from_raw {
$($key:ident => ($type:ident, $set:ident, $map:expr)),+ | _ => $rest:expr) => (
match $name {
$(stringify!($key) => {
concat_idents!(value_as_, $type)($config, $name, $value)
super::custom_values::$type($config, $name, $value)
.and_then(|parsed| $map($config.$set(parsed)))
})+
_ => $rest

View File

@ -50,7 +50,8 @@ pub struct Limits {
impl Default for Limits {
fn default() -> Limits {
Limits { forms: 1024 * 32, extra: Vec::new() }
/// Default limit for forms is 32KiB.
Limits { forms: 32 * 1024, extra: Vec::new() }
}
}
@ -103,33 +104,33 @@ impl fmt::Display for Limits {
}
}
pub fn value_as_str<'a>(conf: &Config, name: &str, v: &'a Value) -> Result<&'a str> {
pub fn str<'a>(conf: &Config, name: &str, v: &'a Value) -> Result<&'a str> {
v.as_str().ok_or(conf.bad_type(name, v.type_str(), "a string"))
}
pub fn value_as_u64(conf: &Config, name: &str, value: &Value) -> Result<u64> {
pub fn u64(conf: &Config, name: &str, value: &Value) -> Result<u64> {
match value.as_integer() {
Some(x) if x >= 0 => Ok(x as u64),
_ => Err(conf.bad_type(name, value.type_str(), "an unsigned integer"))
}
}
pub fn value_as_u16(conf: &Config, name: &str, value: &Value) -> Result<u16> {
pub fn u16(conf: &Config, name: &str, value: &Value) -> Result<u16> {
match value.as_integer() {
Some(x) if x >= 0 && x <= (u16::max_value() as i64) => Ok(x as u16),
_ => Err(conf.bad_type(name, value.type_str(), "a 16-bit unsigned integer"))
}
}
pub fn value_as_log_level(conf: &Config,
pub fn log_level(conf: &Config,
name: &str,
value: &Value
) -> Result<LoggingLevel> {
value_as_str(conf, name, value)
str(conf, name, value)
.and_then(|s| s.parse().map_err(|e| conf.bad_type(name, value.type_str(), e)))
}
pub fn value_as_tls_config<'v>(conf: &Config,
pub fn tls_config<'v>(conf: &Config,
name: &str,
value: &'v Value,
) -> Result<(&'v str, &'v str)> {
@ -140,8 +141,8 @@ pub fn value_as_tls_config<'v>(conf: &Config,
let env = conf.environment;
for (key, value) in table {
match key.as_str() {
"certs" => certs_path = Some(value_as_str(conf, "tls.certs", value)?),
"key" => key_path = Some(value_as_str(conf, "tls.key", value)?),
"certs" => certs_path = Some(str(conf, "tls.certs", value)?),
"key" => key_path = Some(str(conf, "tls.key", value)?),
_ => return Err(ConfigError::UnknownKey(format!("{}.tls.{}", env, key)))
}
}
@ -154,13 +155,13 @@ pub fn value_as_tls_config<'v>(conf: &Config,
}
}
pub fn value_as_limits(conf: &Config, name: &str, value: &Value) -> Result<Limits> {
pub fn limits(conf: &Config, name: &str, value: &Value) -> Result<Limits> {
let table = value.as_table()
.ok_or_else(|| conf.bad_type(name, value.type_str(), "a table"))?;
let mut limits = Limits::default();
for (key, val) in table {
let val = value_as_u64(conf, &format!("limits.{}", key), val)?;
let val = u64(conf, &format!("limits.{}", key), val)?;
limits = limits.add(key.as_str(), val);
}

View File

@ -158,28 +158,33 @@
//! ## Retrieving Configuration Parameters
//!
//! Configuration parameters for the currently active configuration environment
//! can be retrieved via the [active](fn.active.html) function and methods on
//! the [Config](struct.Config.html) structure. The general structure is to call
//! `active` and then one of the `get_` methods on the returned `Config`
//! structure.
//! can be retrieved via the [config](/rocket/struct.Rocket.html#method.config)
//! method on an instance of `Rocket` and `get_` methods on the
//! [Config](struct.Config.html) structure.
//!
//! As an example, consider the following code used by the `Template` type to
//! retrieve the value of the `template_dir` configuration parameter. If the
//! value isn't present or isn't a string, a default value is used.
//! The retrivial of configuration parameters usually occurs at launch time via
//! a [launch fairing](/rocket/fairing/trait.Fairing.html). If information about
//! the configuraiton is needed later in the program, an attach fairing can be
//! used to store the information as managed state. As an example of the latter,
//! consider the following short program which reads the `token` configuration
//! parameter and stores the value or a default in a `Token` managed state
//! value:
//!
//! ```rust
//! use std::path::PathBuf;
//! use rocket::config::{self, ConfigError};
//! use rocket::fairing::AdHoc;
//!
//! const DEFAULT_TEMPLATE_DIR: &'static str = "templates";
//! struct Token(i64);
//!
//! # #[allow(unused_variables)]
//! let template_dir = config::active().ok_or(ConfigError::NotFound)
//! .map(|config| config.root().join(DEFAULT_TEMPLATE_DIR))
//! .unwrap_or_else(|_| PathBuf::from(DEFAULT_TEMPLATE_DIR));
//! fn main() {
//! rocket::ignite()
//! .attach(AdHoc::on_attach(|rocket| {
//! println!("Adding token managed state from config...");
//! let token_val = rocket.config().get_int("token").unwrap_or(-1);
//! Ok(rocket.manage(Token(token_val)))
//! }))
//! # ;
//! }
//! ```
//!
//! Libraries should always use a default if a parameter is not defined.
mod error;
mod environment;
@ -188,7 +193,6 @@ mod builder;
mod toml_ext;
mod custom_values;
use std::sync::{Once, ONCE_INIT};
use std::fs::{self, File};
use std::collections::HashMap;
use std::io::Read;
@ -213,9 +217,6 @@ use self::toml_ext::parse_simple_toml_value;
use logger::{self, LoggingLevel};
use http::uncased::uncased_eq;
static INIT: Once = ONCE_INIT;
static mut CONFIG: Option<RocketConfig> = None;
const CONFIG_FILENAME: &'static str = "Rocket.toml";
const GLOBAL_ENV_NAME: &'static str = "global";
const ENV_VAR_PREFIX: &'static str = "ROCKET_";
@ -466,32 +467,7 @@ impl RocketConfig {
/// # Panics
///
/// If there is a problem, prints a nice error message and bails.
pub(crate) fn init() -> (&'static Config, bool) {
let mut this_init = false;
unsafe {
INIT.call_once(|| {
private_init();
this_init = true;
});
(CONFIG.as_ref().unwrap().active(), this_init)
}
}
pub(crate) fn custom_init(config: Config) -> (&'static Config, bool) {
let mut this_init = false;
unsafe {
INIT.call_once(|| {
CONFIG = Some(RocketConfig::new(config));
this_init = true;
});
(CONFIG.as_ref().unwrap().active(), this_init)
}
}
unsafe fn private_init() {
pub(crate) fn init() -> Config {
let bail = |e: ConfigError| -> ! {
logger::init(LoggingLevel::Debug);
e.pretty_print();
@ -515,16 +491,8 @@ unsafe fn private_init() {
RocketConfig::active_default(&default_path).unwrap_or_else(|e| bail(e))
});
CONFIG = Some(config);
}
/// Retrieve the active configuration, if there is one.
///
/// This function is guaranteed to return `Some` once a Rocket application has
/// started. Before a Rocket application has started, or when there is no active
/// Rocket application (such as during testing), this function will return None.
pub fn active() -> Option<&'static Config> {
unsafe { CONFIG.as_ref().map(|c| c.active()) }
// FIXME: Should probably store all of the config.
config.active().clone()
}
#[cfg(test)]

View File

@ -1,3 +1,5 @@
//! Types and traits for handling incoming body data.
mod data;
mod data_stream;
mod net_stream;

View File

@ -24,8 +24,8 @@ pub enum Error {
///
/// In almost every instance, a launch error occurs because of an I/O error;
/// this is represented by the `Io` variant. A launch error may also occur
/// because of ill-defined routes that lead to collisions or because a launch
/// fairing encounted an error; these are represented by the `Collision` and
/// because of ill-defined routes that lead to collisions or because a fairing
/// encountered an error; these are represented by the `Collision` and
/// `FailedFairing` variants, respectively. The `Unknown` variant captures all
/// other kinds of launch errors.
#[derive(Debug)]
@ -196,7 +196,7 @@ impl Drop for LaunchError {
panic!("route collisions detected");
}
LaunchErrorKind::FailedFairing => {
error!("Rocket failed to launch due to a failing launch fairing.");
error!("Rocket failed to launch due to a failing fairing.");
panic!("launch fairing failure");
}
LaunchErrorKind::Unknown(ref e) => {

View File

@ -85,8 +85,8 @@ impl Fairings {
use term_painter::ToStyle;
use term_painter::Color::{White, Magenta};
if self.all_fairings.len() > 0 {
info!("📡 {}:", Magenta.paint("Fairings"));
if self.all_fairings.is_empty() {
return
}
fn info_if_nonempty(kind: &str, fairings: &[&Fairing]) {
@ -97,6 +97,7 @@ impl Fairings {
White.paint(names.join(", ")));
}
info!("📡 {}:", Magenta.paint("Fairings"));
info_if_nonempty("launch", &self.launch);
info_if_nonempty("request", &self.request);
info_if_nonempty("response", &self.response);

View File

@ -12,8 +12,8 @@ pub type Outcome<'r> = outcome::Outcome<Response<'r>, Status, Data>;
impl<'r> Outcome<'r> {
#[inline]
pub fn of<T: Responder<'r>>(responder: T) -> Outcome<'r> {
match responder.respond() {
pub fn from<T: Responder<'r>>(req: &Request, responder: T) -> Outcome<'r> {
match responder.respond_to(req) {
Ok(response) => outcome::Outcome::Success(response),
Err(status) => outcome::Outcome::Failure(status)
}

View File

@ -7,7 +7,6 @@
#![feature(lookup_host)]
#![feature(plugin)]
#![feature(never_type)]
#![feature(concat_idents)]
#![feature(more_io_inner_methods)]
#![plugin(pear_codegen)]

View File

@ -141,13 +141,20 @@ impl Log for RocketLogger {
}
#[doc(hidden)]
pub fn init(level: LoggingLevel) {
pub fn try_init(level: LoggingLevel, verbose: bool) {
let result = log::set_logger(|max_log_level| {
max_log_level.set(level.max_log_level().to_log_level_filter());
Box::new(RocketLogger(level))
});
if let Err(err) = result {
println!("Logger failed to initialize: {}", err);
if verbose {
println!("Logger failed to initialize: {}", err);
}
}
}
#[doc(hidden)]
pub fn init(level: LoggingLevel) {
try_init(level, true)
}

View File

@ -28,7 +28,6 @@ use std::marker::PhantomData;
use std::fmt::{self, Debug};
use std::io::Read;
use config;
use http::Status;
use request::Request;
use data::{self, Data, FromData};
@ -270,9 +269,6 @@ impl<'f, T: FromForm<'f> + Debug + 'f> Debug for Form<'f, T> {
}
}
/// Default limit for forms is 32KiB.
const LIMIT: u64 = 32 * (1 << 10);
/// Parses a `Form` from incoming form data.
///
/// If the content type of the request data is not
@ -297,7 +293,7 @@ impl<'f, T: FromForm<'f>> FromData for Form<'f, T> where T::Error: Debug {
}
let mut form_string = String::with_capacity(4096);
let limit = config::active().map(|c| c.limits.forms).unwrap_or(LIMIT);
let limit = request.limits().forms;
let mut stream = data.open().take(limit);
if let Err(e) = stream.read_to_string(&mut form_string) {
error_!("IO Error: {:?}", e);

View File

@ -1,8 +1,8 @@
use std::fmt::Debug;
use std::net::SocketAddr;
use outcome::{self, IntoOutcome};
use request::Request;
use outcome::{self, IntoOutcome};
use outcome::Outcome::*;
use http::{Status, ContentType, Accept, Method, Cookies, Session};

View File

@ -9,17 +9,20 @@ use term_painter::ToStyle;
use state::{Container, Storage};
use error::Error;
use super::{FromParam, FromSegments};
use super::{FromParam, FromSegments, FromRequest, Outcome};
use router::Route;
use config::{Config, Limits};
use http::uri::{URI, Segments};
use http::{Method, Header, HeaderMap, Cookies, Session, CookieJar, Key};
use http::{Method, Header, HeaderMap, Cookies, Session, CookieJar};
use http::{RawStr, ContentType, Accept, MediaType};
use http::hyper;
struct PresetState<'r> {
key: &'r Key,
managed_state: &'r Container,
// The running Rocket instances configuration.
config: &'r Config,
// The managed state of the running Rocket instance.
state: &'r Container,
}
struct RequestState<'r> {
@ -286,30 +289,19 @@ impl<'r> Request<'r> {
#[inline]
pub fn session(&self) -> Session {
let key = self.preset().config.secret_key();
match self.extra.session.try_borrow_mut() {
Ok(jar) => Session::new(jar, self.preset().key),
Ok(jar) => Session::new(jar, key),
Err(_) => {
error_!("Multiple `Session` instances are active at once.");
info_!("An instance of `Session` must be dropped before another \
can be retrieved.");
warn_!("The retrieved `Session` instance will be empty.");
Session::empty(self.preset().key)
Session::empty(key)
}
}
}
/// Replace all of the cookies in `self` with those in `jar`.
#[inline]
pub(crate) fn set_cookies(&mut self, jar: CookieJar) {
self.extra.cookies = RefCell::new(jar);
}
/// Replace all of the session cookie in `self` with those in `jar`.
#[inline]
pub(crate) fn set_session(&mut self, jar: CookieJar) {
self.extra.session = RefCell::new(jar);
}
/// Returns the Content-Type header of `self`. If the header is not present,
/// returns `None`. The Content-Type header is cached after the first call
/// to this function. As a result, subsequent calls will always return the
@ -352,7 +344,6 @@ impl<'r> Request<'r> {
self.accept().and_then(|accept| accept.first()).map(|wmt| wmt.media_type())
}
#[inline(always)]
pub fn format(&self) -> Option<&MediaType> {
static ANY: MediaType = MediaType::Any;
if self.method.supports_payload() {
@ -386,7 +377,7 @@ impl<'r> Request<'r> {
///
/// # #[allow(dead_code)]
/// fn name<'a>(req: &'a Request, _: Data) -> Outcome<'a> {
/// Outcome::of(req.get_param::<String>(0).unwrap_or("unnamed".into()))
/// Outcome::from(req, req.get_param::<String>(0).unwrap_or("unnamed".into()))
/// }
/// ```
pub fn get_param<'a, T: FromParam<'a>>(&'a self, n: usize) -> Result<T, Error> {
@ -394,15 +385,6 @@ impl<'r> Request<'r> {
T::from_param(param).map_err(|_| Error::BadParse)
}
/// Set `self`'s parameters given that the route used to reach this request
/// was `route`. This should only be used internally by `Rocket` as improper
/// 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) {
*self.extra.params.borrow_mut() = route.get_param_indexes(self.uri());
}
/// Get the `n`th path parameter as a string, if it exists. This is used by
/// codegen.
#[doc(hidden)]
@ -468,10 +450,9 @@ impl<'r> Request<'r> {
Some(Segments(&path[i..j]))
}
/// Get the managed state container, if it exists. For internal use only!
#[inline(always)]
pub fn get_state<T: Send + Sync + 'static>(&self) -> Option<&'r T> {
self.preset().managed_state.try_get()
/// Get the limits.
pub fn limits(&self) -> &'r Limits {
&self.preset().config.limits
}
#[inline(always)]
@ -485,10 +466,43 @@ impl<'r> Request<'r> {
}
}
/// Set `self`'s parameters given that the route used to reach this request
/// was `route`. This should only be used internally by `Rocket` as improper
/// 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) {
*self.extra.params.borrow_mut() = route.get_param_indexes(self.uri());
}
/// Replace all of the cookies in `self` with those in `jar`.
#[inline]
pub(crate) fn set_cookies(&mut self, jar: CookieJar) {
self.extra.cookies = RefCell::new(jar);
}
/// Replace all of the session cookie in `self` with those in `jar`.
#[inline]
pub(crate) fn set_session(&mut self, jar: CookieJar) {
self.extra.session = RefCell::new(jar);
}
/// Try to derive some guarded value from `self`.
#[inline(always)]
pub fn guard<'a, T: FromRequest<'a, 'r>>(&'a self) -> Outcome<T, T::Error> {
T::from_request(self)
}
/// Get the managed state T, if it exists. For internal use only!
#[inline(always)]
pub(crate) fn get_state<T: Send + Sync + 'static>(&self) -> Option<&'r T> {
self.preset().state.try_get()
}
/// Set the precomputed state. For internal use only!
#[inline(always)]
pub(crate) fn set_preset_state(&mut self, key: &'r Key, state: &'r Container) {
self.extra.preset = Some(PresetState { key, managed_state: state });
pub(crate) fn set_preset(&mut self, config: &'r Config, state: &'r Container) {
self.extra.preset = Some(PresetState { config, state });
}
/// Convert from Hyper types into a Rocket Request.

View File

@ -22,6 +22,7 @@
//! let response = content::HTML("<h1>Hello, world!</h1>");
//! ```
use request::Request;
use response::{Response, Responder};
use http::{Status, ContentType};
@ -47,9 +48,9 @@ pub struct Content<R>(pub ContentType, pub R);
/// delegates the remainder of the response to the wrapped responder.
impl<'r, R: Responder<'r>> Responder<'r> for Content<R> {
#[inline(always)]
fn respond(self) -> Result<Response<'r>, Status> {
fn respond_to(self, req: &Request) -> Result<Response<'r>, Status> {
Response::build()
.merge(self.1.respond()?)
.merge(self.1.respond_to(req)?)
.header(self.0)
.ok()
}
@ -71,8 +72,8 @@ macro_rules! ctrs {
/// Sets the Content-Type of the response then delegates the
/// remainder of the response to the wrapped responder.
impl<'r, R: Responder<'r>> Responder<'r> for $name<R> {
fn respond(self) -> Result<Response<'r>, Status> {
Content(ContentType::$name, self.0).respond()
fn respond_to(self, req: &Request) -> Result<Response<'r>, Status> {
Content(ContentType::$name, self.0).respond_to(req)
}
}
)+

View File

@ -1,3 +1,4 @@
use request::Request;
use response::{Response, Responder};
use http::Status;
@ -6,8 +7,8 @@ use http::Status;
#[derive(Debug)]
pub struct Failure(pub Status);
impl<'r> Responder<'r> for Failure {
fn respond(self) -> Result<Response<'r>, Status> {
impl Responder<'static> for Failure {
fn respond_to(self, _: &Request) -> Result<Response<'static>, Status> {
Err(self.0)
}
}

View File

@ -181,10 +181,10 @@ impl<'r, R: Responder<'r>> Flash<R> {
/// response handling to the wrapped responder. As a result, the `Outcome` of
/// the response is the `Outcome` of the wrapped `Responder`.
impl<'r, R: Responder<'r>> Responder<'r> for Flash<R> {
fn respond(self) -> Result<Response<'r>, Status> {
fn respond_to(self, req: &Request) -> Result<Response<'r>, Status> {
trace_!("Flash: setting message: {}:{}", self.name, self.message);
let cookie = self.cookie();
Response::build_from(self.responder.respond()?)
Response::build_from(self.responder.respond_to(req)?)
.header_adjoin(&cookie)
.ok()
}

View File

@ -3,6 +3,7 @@ use std::path::{Path, PathBuf};
use std::io;
use std::ops::{Deref, DerefMut};
use request::Request;
use response::{Response, Responder};
use http::{Status, ContentType};
@ -78,8 +79,8 @@ impl NamedFile {
/// [ContentType::from_extension](/rocket/http/struct.ContentType.html#method.from_extension)
/// for more information. If you would like to stream a file with a different
/// Content-Type than that implied by its extension, use a `File` directly.
impl<'r> Responder<'r> for NamedFile {
fn respond(self) -> Result<Response<'r>, Status> {
impl Responder<'static> for NamedFile {
fn respond_to(self, _: &Request) -> Result<Response<'static>, Status> {
let mut response = Response::new();
if let Some(ext) = self.path().extension() {
// TODO: Use Cow for lowercase.

View File

@ -1,3 +1,4 @@
use request::Request;
use response::{Response, Responder};
use http::hyper::header;
use http::Status;
@ -104,7 +105,7 @@ impl Redirect {
/// the `Location` header field. The body of the response is empty. This
/// responder does not fail.
impl Responder<'static> for Redirect {
fn respond(self) -> Result<Response<'static>, Status> {
fn respond_to(self, _: &Request) -> Result<Response<'static>, Status> {
Response::build()
.status(self.0)
.header(header::Location(self.1))

View File

@ -3,7 +3,8 @@ use std::io::Cursor;
use std::fmt;
use http::{Status, ContentType};
use response::{Response, Stream};
use response::Response;
use request::Request;
/// Trait implemented by types that generate responses for clients.
///
@ -49,12 +50,14 @@ use response::{Response, Stream};
///
/// * **File**
///
/// Streams the `File` to the client. This is essentially an alias to
/// `Stream::from(file)`.
/// Responds with a sized body containing the data in the `File`. No
/// `Content-Type` is set. To automatically have a `Content-Type` set based
/// on the file's extension, use
/// [`NamedFile`](/rocket/response/struct.NamedFile.html).
///
/// * **()**
///
/// Responds with an empty body. No Content-Type is set.
/// Responds with an empty body. No `Content-Type` is set.
///
/// * **Option&lt;T>**
///
@ -95,6 +98,16 @@ use response::{Response, Stream};
/// or `ResponseBuilder` struct. Ensure that you document the merging or joining
/// behavior appropriately.
///
/// ## Inspecting Requests
///
/// A `Responder` has access to the request it is responding to. Even so, you
/// should avoid using the `Request` value as much as possible. This is because
/// using the `Request` object makes your responder _inpure_, and so the use of
/// the type as a `Responder` has less intrinsic meaning associated with it. If
/// the `Responder` were pure, however, it always respond in the same manner,
/// regardless of the incoming request. Thus, knowing the type is sufficient to
/// fully determine its functionality.
///
/// # Example
///
/// Say that you have a custom type, `Person`:
@ -133,11 +146,12 @@ use response::{Response, Stream};
/// #
/// use std::io::Cursor;
///
/// use rocket::request::Request;
/// use rocket::response::{self, Response, Responder};
/// use rocket::http::ContentType;
///
/// impl<'r> Responder<'r> for Person {
/// fn respond(self) -> response::Result<'r> {
/// impl Responder<'static> for Person {
/// fn respond_to(self, _: &Request) -> response::Result<'static> {
/// Response::build()
/// .sized_body(Cursor::new(format!("{}:{}", self.name, self.age)))
/// .raw_header("X-Person-Name", self.name)
@ -155,32 +169,21 @@ pub trait Responder<'r> {
/// Returns `Ok` if a `Response` could be generated successfully. Otherwise,
/// returns an `Err` with a failing `Status`.
///
/// The `request` parameter is the `Request` that this `Responder` is
/// responding to.
///
/// When using Rocket's code generation, if an `Ok(Response)` is returned,
/// the response will be written out to the client. If an `Err(Status)` is
/// returned, the error catcher for the given status is retrieved and called
/// to generate a final error response, which is then written out to the
/// client.
fn respond(self) -> Result<Response<'r>, Status>;
fn respond_to(self, request: &Request) -> Result<Response<'r>, Status>;
}
/// Returns a response with Content-Type `text/plain` and a fixed-size body
/// containing the string `self`. Always returns `Ok`.
///
/// # Example
///
/// ```rust
/// use rocket::response::Responder;
/// use rocket::http::ContentType;
///
/// let mut response = "Hello".respond().unwrap();
/// assert_eq!(response.body_string(), Some("Hello".into()));
///
/// let content_type: Vec<_> = response.headers().get("Content-Type").collect();
/// assert_eq!(content_type.len(), 1);
/// assert_eq!(content_type[0], ContentType::Plain.to_string());
/// ```
impl<'r> Responder<'r> for &'r str {
fn respond(self) -> Result<Response<'r>, Status> {
fn respond_to(self, _: &Request) -> Result<Response<'r>, Status> {
Response::build()
.header(ContentType::Plain)
.sized_body(Cursor::new(self))
@ -191,7 +194,7 @@ impl<'r> Responder<'r> for &'r str {
/// Returns a response with Content-Type `text/plain` and a fixed-size body
/// containing the string `self`. Always returns `Ok`.
impl Responder<'static> for String {
fn respond(self) -> Result<Response<'static>, Status> {
fn respond_to(self, _: &Request) -> Result<Response<'static>, Status> {
Response::build()
.header(ContentType::Plain)
.sized_body(Cursor::new(self))
@ -199,16 +202,16 @@ impl Responder<'static> for String {
}
}
/// Aliases Stream<File>: `Stream::from(self)`.
/// Returns a response with a sized body for the file. Always returns `Ok`.
impl Responder<'static> for File {
fn respond(self) -> Result<Response<'static>, Status> {
Stream::from(self).respond()
fn respond_to(self, _: &Request) -> Result<Response<'static>, Status> {
Response::build().sized_body(self).ok()
}
}
/// Returns an empty, default `Response`. Always returns `Ok`.
impl Responder<'static> for () {
fn respond(self) -> Result<Response<'static>, Status> {
fn respond_to(self, _: &Request) -> Result<Response<'static>, Status> {
Ok(Response::new())
}
}
@ -216,11 +219,11 @@ impl Responder<'static> for () {
/// If `self` is `Some`, responds with the wrapped `Responder`. Otherwise prints
/// a warning message and returns an `Err` of `Status::NotFound`.
impl<'r, R: Responder<'r>> Responder<'r> for Option<R> {
fn respond(self) -> Result<Response<'r>, Status> {
fn respond_to(self, req: &Request) -> Result<Response<'r>, Status> {
self.map_or_else(|| {
warn_!("Response was `None`.");
Err(Status::NotFound)
}, |r| r.respond())
}, |r| r.respond_to(req))
}
}
@ -228,8 +231,8 @@ impl<'r, R: Responder<'r>> Responder<'r> for Option<R> {
/// an error message with the `Err` value returns an `Err` of
/// `Status::InternalServerError`.
impl<'r, R: Responder<'r>, E: fmt::Debug> Responder<'r> for Result<R, E> {
default fn respond(self) -> Result<Response<'r>, Status> {
self.map(|r| r.respond()).unwrap_or_else(|e| {
default fn respond_to(self, req: &Request) -> Result<Response<'r>, Status> {
self.map(|r| r.respond_to(req)).unwrap_or_else(|e| {
error_!("Response was `Err`: {:?}.", e);
Err(Status::InternalServerError)
})
@ -239,10 +242,10 @@ impl<'r, R: Responder<'r>, E: fmt::Debug> Responder<'r> for Result<R, E> {
/// Responds with the wrapped `Responder` in `self`, whether it is `Ok` or
/// `Err`.
impl<'r, R: Responder<'r>, E: Responder<'r> + fmt::Debug> Responder<'r> for Result<R, E> {
fn respond(self) -> Result<Response<'r>, Status> {
fn respond_to(self, req: &Request) -> Result<Response<'r>, Status> {
match self {
Ok(responder) => responder.respond(),
Err(responder) => responder.respond(),
Ok(responder) => responder.respond_to(req),
Err(responder) => responder.respond_to(req),
}
}
}

View File

@ -1129,9 +1129,11 @@ impl<'r> fmt::Debug for Response<'r> {
}
}
use request::Request;
impl<'r> Responder<'r> for Response<'r> {
/// This is the identity implementation. It simply returns `Ok(self)`.
fn respond(self) -> Result<Response<'r>, Status> {
fn respond_to(self, _: &Request) -> Result<Response<'r>, Status> {
Ok(self)
}
}

View File

@ -10,6 +10,7 @@
use std::hash::{Hash, Hasher};
use std::collections::hash_map::DefaultHasher;
use request::Request;
use response::{Responder, Response};
use http::hyper::header;
use http::Status;
@ -40,10 +41,10 @@ pub struct Created<R>(pub String, pub Option<R>);
/// information about the created resource. If no responder is provided, the
/// response body will be empty.
impl<'r, R: Responder<'r>> Responder<'r> for Created<R> {
default fn respond(self) -> Result<Response<'r>, Status> {
default fn respond_to(self, req: &Request) -> Result<Response<'r>, Status> {
let mut build = Response::build();
if let Some(responder) = self.1 {
build.merge(responder.respond()?);
build.merge(responder.respond_to(req)?);
}
build.status(Status::Created).header(header::Location(self.0)).ok()
@ -55,14 +56,14 @@ impl<'r, R: Responder<'r>> Responder<'r> for Created<R> {
/// a `Responder` is provided that implements `Hash`. The `ETag` header is set
/// to a hash value of the responder.
impl<'r, R: Responder<'r> + Hash> Responder<'r> for Created<R> {
fn respond(self) -> Result<Response<'r>, Status> {
fn respond_to(self, req: &Request) -> Result<Response<'r>, Status> {
let mut hasher = DefaultHasher::default();
let mut build = Response::build();
if let Some(responder) = self.1 {
responder.hash(&mut hasher);
let hash = hasher.finish().to_string();
build.merge(responder.respond()?);
build.merge(responder.respond_to(req)?);
build.header(header::ETag(header::EntityTag::strong(hash)));
}
@ -100,10 +101,10 @@ pub struct Accepted<R>(pub Option<R>);
/// Sets the status code of the response to 202 Accepted. If the responder is
/// `Some`, it is used to finalize the response.
impl<'r, R: Responder<'r>> Responder<'r> for Accepted<R> {
fn respond(self) -> Result<Response<'r>, Status> {
fn respond_to(self, req: &Request) -> Result<Response<'r>, Status> {
let mut build = Response::build();
if let Some(responder) = self.0 {
build.merge(responder.respond()?);
build.merge(responder.respond_to(req)?);
}
build.status(Status::Accepted).ok()
@ -127,7 +128,7 @@ pub struct NoContent;
/// Sets the status code of the response to 204 No Content. The body of the
/// response will be empty.
impl<'r> Responder<'r> for NoContent {
fn respond(self) -> Result<Response<'r>, Status> {
fn respond_to(self, _: &Request) -> Result<Response<'r>, Status> {
Response::build().status(Status::NoContent).ok()
}
}
@ -148,8 +149,8 @@ pub struct Reset;
/// Sets the status code of the response to 205 Reset Content. The body of the
/// response will be empty.
impl<'r> Responder<'r> for Reset {
fn respond(self) -> Result<Response<'r>, Status> {
impl Responder<'static> for Reset {
fn respond_to(self, _: &Request) -> Result<Response<'static>, Status> {
Response::build().status(Status::ResetContent).ok()
}
}
@ -171,8 +172,8 @@ pub struct Custom<R>(pub Status, pub R);
/// Sets the status code of the response and then delegates the remainder of the
/// response to the wrapped responder.
impl<'r, R: Responder<'r>> Responder<'r> for Custom<R> {
fn respond(self) -> Result<Response<'r>, Status> {
Response::build_from(self.1.respond()?)
fn respond_to(self, req: &Request) -> Result<Response<'r>, Status> {
Response::build_from(self.1.respond_to(req)?)
.status(self.0)
.ok()
}

View File

@ -1,6 +1,7 @@
use std::io::Read;
use std::fmt::{self, Debug};
use request::Request;
use response::{Response, Responder, DEFAULT_CHUNK_SIZE};
use http::Status;
@ -68,7 +69,7 @@ impl<T: Read> From<T> for Stream<T> {
/// response is abandoned, and the response ends abruptly. An error is printed
/// to the console with an indication of what went wrong.
impl<'r, T: Read + 'r> Responder<'r> for Stream<T> {
fn respond(self) -> Result<Response<'r>, Status> {
fn respond_to(self, _: &Request) -> Result<Response<'r>, Status> {
Response::build().chunked_body(self.0, self.1).ok()
}
}

View File

@ -29,7 +29,7 @@ use http::uri::URI;
/// The main `Rocket` type: used to mount routes and catchers and launch the
/// application.
pub struct Rocket {
config: &'static Config,
config: Config,
router: Router,
default_catchers: HashMap<u16, Catcher>,
catchers: HashMap<u16, Catcher>,
@ -96,8 +96,8 @@ macro_rules! serve {
#[cfg(feature = "tls")]
macro_rules! serve {
($rocket:expr, $addr:expr, |$server:ident, $proto:ident| $continue:expr) => ({
if let Some(ref tls) = $rocket.config.tls {
let tls = TlsServer::new(tls.certs.clone(), tls.key.clone());
if let Some(tls) = $rocket.config.tls.clone() {
let tls = TlsServer::new(tls.certs, tls.key);
let ($proto, $server) = ("https://", hyper::Server::https($addr, tls));
$continue
} else {
@ -218,7 +218,7 @@ impl Rocket {
info!("{}:", request);
// Inform the request about all of the precomputed state.
request.set_preset_state(&self.config.secret_key(), &self.state);
request.set_preset(&self.config, &self.state);
// Do a bit of preprocessing before routing; run the attached fairings.
self.preprocess_request(request, &data);
@ -346,10 +346,10 @@ impl Rocket {
/// rocket::ignite()
/// # };
/// ```
#[inline]
pub fn ignite() -> Rocket {
// Note: init() will exit the process under config errors.
let (config, initted) = config::init();
Rocket::configured(config, initted)
Rocket::configured(config::init(), true)
}
/// Creates a new `Rocket` application using the supplied custom
@ -377,14 +377,15 @@ impl Rocket {
/// # Ok(())
/// # }
/// ```
#[inline]
pub fn custom(config: Config, log: bool) -> Rocket {
let (config, initted) = config::custom_init(config);
Rocket::configured(config, log && initted)
Rocket::configured(config, log)
}
fn configured(config: &'static Config, log: bool) -> Rocket {
#[inline]
fn configured(config: Config, log: bool) -> Rocket {
if log {
logger::init(config.log_level);
logger::try_init(config.log_level, false);
}
info!("🔧 Configured for {}.", config.environment);
@ -426,17 +427,17 @@ impl Rocket {
/// path. Mounting a route with path `path` at path `base` makes the route
/// available at `base/path`.
///
/// # Panics
///
/// The `base` mount point must be a static path. That is, the mount point
/// must _not_ contain dynamic path parameters: `<param>`.
///
/// # 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.
///
/// # Panics
///
/// The `base` mount point must be a static path. That is, the mount point
/// must _not_ contain dynamic path parameters: `<param>`.
///
/// ```rust
/// # #![feature(plugin)]
/// # #![plugin(rocket_codegen)]
@ -464,8 +465,8 @@ impl Rocket {
/// use rocket::handler::Outcome;
/// use rocket::http::Method::*;
///
/// fn hi(_: &Request, _: Data) -> Outcome<'static> {
/// Outcome::of("Hello!")
/// fn hi(req: &Request, _: Data) -> Outcome<'static> {
/// Outcome::from(req, "Hello!")
/// }
///
/// # if false { // We don't actually want to launch the server in an example.
@ -473,6 +474,7 @@ impl Rocket {
/// # .launch();
/// # }
/// ```
#[inline]
pub fn mount(mut self, base: &str, routes: Vec<Route>) -> Self {
info!("🛰 {} '{}':", Magenta.paint("Mounting"), base);
@ -522,6 +524,7 @@ impl Rocket {
/// # }
/// }
/// ```
#[inline]
pub fn catch(mut self, catchers: Vec<Catcher>) -> Self {
info!("👾 {}:", Magenta.paint("Catchers"));
for c in catchers {
@ -577,6 +580,7 @@ impl Rocket {
/// # }
/// }
/// ```
#[inline]
pub fn manage<T: Send + Sync + 'static>(self, state: T) -> Self {
if !self.state.set::<T>(state) {
error!("State for this type is already being managed!");
@ -648,7 +652,7 @@ impl Rocket {
/// rocket::ignite().launch();
/// # }
/// ```
pub fn launch(self) -> LaunchError {
pub fn launch(mut self) -> LaunchError {
if let Some(error) = self.prelaunch_check() {
return error;
}
@ -662,19 +666,20 @@ impl Rocket {
Err(e) => return LaunchError::from(e)
};
// Determine the port we actually binded to.
let (addr, port) = match server.local_addr() {
Ok(server_addr) => (&self.config.address, server_addr.port()),
// Determine the address and port we actually binded to.
match server.local_addr() {
Ok(server_addr) => self.config.port = server_addr.port(),
Err(e) => return LaunchError::from(e)
};
}
// Run the launch fairings.
self.fairings.handle_launch(&self);
let full_addr = format!("{}:{}", self.config.address, self.config.port);
launch_info!("🚀 {} {}{}",
White.paint("Rocket has launched from"),
White.bold().paint(proto),
White.bold().paint(&format!("{}:{}", addr, port)));
White.bold().paint(&full_addr));
let threads = self.config.workers as usize;
if let Err(e) = server.handle_threads(self, threads) {
@ -691,9 +696,9 @@ impl Rocket {
self.router.routes()
}
/// Retrieve the configuration.
/// Retrieve the active configuration.
#[inline(always)]
pub fn config(&self) -> &Config {
self.config
&self.config
}
}

View File

@ -141,8 +141,8 @@ mod tests {
type SimpleRoute = (Method, &'static str);
fn dummy_handler(_req: &Request, _: Data) -> Outcome<'static> {
Outcome::of("hi")
fn dummy_handler(req: &Request, _: Data) -> Outcome<'static> {
Outcome::from(req, "hi")
}
fn m_collide(a: SimpleRoute, b: SimpleRoute) -> bool {

View File

@ -77,8 +77,8 @@ mod test {
use data::Data;
use handler::Outcome;
fn dummy_handler(_req: &Request, _: Data) -> Outcome<'static> {
Outcome::of("hi")
fn dummy_handler(req: &Request, _: Data) -> Outcome<'static> {
Outcome::from(req, "hi")
}
fn router_with_routes(routes: &[&'static str]) -> Router {

View File

@ -15,7 +15,7 @@ fn index(form: Form<Simple>) -> String {
form.into_inner().value
}
mod tests {
mod limits_tests {
use rocket;
use rocket::config::{Environment, Config, Limits};
use rocket::testing::MockRequest;
@ -30,44 +30,38 @@ mod tests {
rocket::custom(config, true).mount("/", routes![super::index])
}
// FIXME: Config is global (it's the only global thing). Each of these tests
// will run in different threads in the same process, so the config used by
// all of the tests will be indentical: whichever of these gets executed
// first. As such, only one test will pass; the rest will fail. Make config
// _not_ global so we can actually do these tests.
#[test]
fn large_enough() {
let rocket = rocket_with_forms_limit(128);
let mut req = MockRequest::new(Post, "/")
.body("value=Hello+world")
.header(ContentType::Form);
// #[test]
// fn large_enough() {
// let rocket = rocket_with_forms_limit(128);
// let mut req = MockRequest::new(Post, "/")
// .body("value=Hello+world")
// .header(ContentType::Form);
let mut response = req.dispatch_with(&rocket);
assert_eq!(response.body_string(), Some("Hello world".into()));
}
// let mut response = req.dispatch_with(&rocket);
// assert_eq!(response.body_string(), Some("Hello world".into()));
// }
#[test]
fn just_large_enough() {
let rocket = rocket_with_forms_limit(17);
let mut req = MockRequest::new(Post, "/")
.body("value=Hello+world")
.header(ContentType::Form);
// #[test]
// fn just_large_enough() {
// let rocket = rocket_with_forms_limit(17);
// let mut req = MockRequest::new(Post, "/")
// .body("value=Hello+world")
// .header(ContentType::Form);
let mut response = req.dispatch_with(&rocket);
assert_eq!(response.body_string(), Some("Hello world".into()));
}
// let mut response = req.dispatch_with(&rocket);
// assert_eq!(response.body_string(), Some("Hello world".into()));
// }
#[test]
fn much_too_small() {
let rocket = rocket_with_forms_limit(4);
let mut req = MockRequest::new(Post, "/")
.body("value=Hello+world")
.header(ContentType::Form);
// #[test]
// fn much_too_small() {
// let rocket = rocket_with_forms_limit(4);
// let mut req = MockRequest::new(Post, "/")
// .body("value=Hello+world")
// .header(ContentType::Form);
// let response = req.dispatch_with(&rocket);
// assert_eq!(response.status(), Status::BadRequest);
// }
let response = req.dispatch_with(&rocket);
assert_eq!(response.status(), Status::BadRequest);
}
#[test]
fn contracted() {

View File

@ -128,7 +128,7 @@ fn rocket_route_fn_hello<'_b>(_req: &'_b ::rocket::Request,
_data: ::rocket::Data)
-> ::rocket::handler::Outcome<'_b> {
let responder = hello();
::rocket::handler::Outcome::of(responder)
::rocket::handler::Outcome::from(_req, responder)
}
```