mirror of https://github.com/rwf2/Rocket.git
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:
parent
28a1ef0916
commit
9b955747e4
|
@ -8,8 +8,8 @@ use syntax::ext::base::{Annotatable, ExtCtxt};
|
||||||
use syntax::tokenstream::TokenTree;
|
use syntax::tokenstream::TokenTree;
|
||||||
use syntax::parse::token;
|
use syntax::parse::token;
|
||||||
|
|
||||||
const ERR_PARAM: &'static str = "_error";
|
const ERR_PARAM: &'static str = "__err";
|
||||||
const REQ_PARAM: &'static str = "_request";
|
const REQ_PARAM: &'static str = "__req";
|
||||||
|
|
||||||
trait ErrorGenerateExt {
|
trait ErrorGenerateExt {
|
||||||
fn generate_fn_arguments(&self, &ExtCtxt, Ident, Ident) -> Vec<TokenTree>;
|
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)
|
$req_ident: &'_b ::rocket::Request)
|
||||||
-> ::rocket::response::Result<'_b> {
|
-> ::rocket::response::Result<'_b> {
|
||||||
let user_response = $user_fn_name($fn_arguments);
|
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);
|
let status = ::rocket::http::Status::raw($code);
|
||||||
::rocket::response::Response::build().status(status).merge(response).ok()
|
::rocket::response::Response::build().status(status).merge(response).ok()
|
||||||
}
|
}
|
||||||
|
|
|
@ -78,7 +78,7 @@ impl RouteGenerateExt for RouteParams {
|
||||||
let mut items = ::rocket::request::FormItems::from($form_string);
|
let mut items = ::rocket::request::FormItems::from($form_string);
|
||||||
let obj = match ::rocket::request::FromForm::from_form_items(items.by_ref()) {
|
let obj = match ::rocket::request::FromForm::from_form_items(items.by_ref()) {
|
||||||
Ok(v) => v,
|
Ok(v) => v,
|
||||||
Err(_) => return ::rocket::Outcome::Forward(_data)
|
Err(_) => return ::rocket::Outcome::Forward(__data)
|
||||||
};
|
};
|
||||||
|
|
||||||
if !items.exhaust() {
|
if !items.exhaust() {
|
||||||
|
@ -106,7 +106,7 @@ impl RouteGenerateExt for RouteParams {
|
||||||
let ty = strip_ty_lifetimes(arg.ty.clone());
|
let ty = strip_ty_lifetimes(arg.ty.clone());
|
||||||
Some(quote_stmt!(ecx,
|
Some(quote_stmt!(ecx,
|
||||||
let $name: $ty =
|
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::Success(d) => d,
|
||||||
::rocket::Outcome::Forward(d) =>
|
::rocket::Outcome::Forward(d) =>
|
||||||
return ::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> {
|
fn generate_query_statement(&self, ecx: &ExtCtxt) -> Option<Stmt> {
|
||||||
let param = self.query_param.as_ref();
|
let param = self.query_param.as_ref();
|
||||||
let expr = quote_expr!(ecx,
|
let expr = quote_expr!(ecx,
|
||||||
match _req.uri().query() {
|
match __req.uri().query() {
|
||||||
Some(query) => 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.
|
// Note: the `None` case shouldn't happen if a route is matched.
|
||||||
let ident = param.ident().prepend(PARAM_PREFIX);
|
let ident = param.ident().prepend(PARAM_PREFIX);
|
||||||
let expr = match param {
|
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),
|
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),
|
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) => {
|
Err(e) => {
|
||||||
println!(" => Failed to parse '{}': {:?}",
|
println!(" => Failed to parse '{}': {:?}",
|
||||||
stringify!($original_ident), e);
|
stringify!($original_ident), e);
|
||||||
return ::rocket::Outcome::Forward(_data)
|
return ::rocket::Outcome::Forward(__data)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
).expect("declared param parsing statement"));
|
).expect("declared param parsing statement"));
|
||||||
|
@ -195,10 +195,10 @@ impl RouteGenerateExt for RouteParams {
|
||||||
fn_param_statements.push(quote_stmt!(ecx,
|
fn_param_statements.push(quote_stmt!(ecx,
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
let $ident: $ty = match
|
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::Success(v) => v,
|
||||||
::rocket::outcome::Outcome::Forward(_) =>
|
::rocket::outcome::Outcome::Forward(_) =>
|
||||||
return ::rocket::Outcome::forward(_data),
|
return ::rocket::Outcome::forward(__data),
|
||||||
::rocket::outcome::Outcome::Failure((code, _)) => {
|
::rocket::outcome::Outcome::Failure((code, _)) => {
|
||||||
return ::rocket::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
|
// Allow the `unreachable_code` lint for those FromParam impls that have
|
||||||
// an `Error` associated type of !.
|
// an `Error` associated type of !.
|
||||||
#[allow(unreachable_code)]
|
#[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> {
|
-> ::rocket::handler::Outcome<'_b> {
|
||||||
$param_statements
|
$param_statements
|
||||||
$query_statement
|
$query_statement
|
||||||
$data_statement
|
$data_statement
|
||||||
let responder = $user_fn_name($fn_arguments);
|
let responder = $user_fn_name($fn_arguments);
|
||||||
::rocket::handler::Outcome::of(responder)
|
::rocket::handler::Outcome::from(__req, responder)
|
||||||
}
|
}
|
||||||
).unwrap());
|
).unwrap());
|
||||||
|
|
||||||
|
|
|
@ -174,11 +174,11 @@ pub fn msg_and_help<'a, T: LintContext<'a>>(cx: &T,
|
||||||
note: &str,
|
note: &str,
|
||||||
help_sp: Option<Span>,
|
help_sp: Option<Span>,
|
||||||
help: &str) {
|
help: &str) {
|
||||||
let mut b = cx.struct_span_lint(lint, msg_sp, msg);
|
// Be conservative. If we don't know the receiver, don't emit the warning.
|
||||||
b.note(note);
|
|
||||||
if let Some(span) = help_sp {
|
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();
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,8 +18,7 @@ tera_templates = ["tera", "templates"]
|
||||||
handlebars_templates = ["handlebars", "templates"]
|
handlebars_templates = ["handlebars", "templates"]
|
||||||
|
|
||||||
# Internal use only.
|
# Internal use only.
|
||||||
templates = ["serde", "serde_json", "lazy_static_macro", "glob"]
|
templates = ["serde", "serde_json", "glob"]
|
||||||
lazy_static_macro = ["lazy_static"]
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
rocket = { version = "0.2.6", path = "../lib/" }
|
rocket = { version = "0.2.6", path = "../lib/" }
|
||||||
|
@ -36,5 +35,4 @@ rmp-serde = { version = "^0.13", optional = true }
|
||||||
# Templating dependencies only.
|
# Templating dependencies only.
|
||||||
handlebars = { version = "^0.26.1", optional = true }
|
handlebars = { version = "^0.26.1", optional = true }
|
||||||
glob = { version = "^0.2", optional = true }
|
glob = { version = "^0.2", optional = true }
|
||||||
lazy_static = { version = "^0.2", optional = true }
|
|
||||||
tera = { version = "^0.10", optional = true }
|
tera = { version = "^0.10", optional = true }
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
use std::ops::{Deref, DerefMut};
|
use std::ops::{Deref, DerefMut};
|
||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
|
|
||||||
use rocket::config;
|
|
||||||
use rocket::outcome::{Outcome, IntoOutcome};
|
use rocket::outcome::{Outcome, IntoOutcome};
|
||||||
use rocket::request::Request;
|
use rocket::request::Request;
|
||||||
use rocket::data::{self, Data, FromData};
|
use rocket::data::{self, Data, FromData};
|
||||||
|
@ -80,6 +79,7 @@ impl<T> JSON<T> {
|
||||||
/// let my_json = JSON(string);
|
/// let my_json = JSON(string);
|
||||||
/// assert_eq!(my_json.into_inner(), "Hello".to_string());
|
/// assert_eq!(my_json.into_inner(), "Hello".to_string());
|
||||||
/// ```
|
/// ```
|
||||||
|
#[inline(always)]
|
||||||
pub fn into_inner(self) -> T {
|
pub fn into_inner(self) -> T {
|
||||||
self.0
|
self.0
|
||||||
}
|
}
|
||||||
|
@ -97,10 +97,7 @@ impl<T: DeserializeOwned> FromData for JSON<T> {
|
||||||
return Outcome::Forward(data);
|
return Outcome::Forward(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
let size_limit = config::active()
|
let size_limit = request.limits().get("json").unwrap_or(LIMIT);
|
||||||
.and_then(|c| c.limits.get("json"))
|
|
||||||
.unwrap_or(LIMIT);
|
|
||||||
|
|
||||||
serde_json::from_reader(data.open().take(size_limit))
|
serde_json::from_reader(data.open().take(size_limit))
|
||||||
.map(|val| JSON(val))
|
.map(|val| JSON(val))
|
||||||
.map_err(|e| { error_!("Couldn't parse JSON body: {:?}", e); e })
|
.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
|
/// JSON and a fixed-size body with the serialized value. If serialization
|
||||||
/// fails, an `Err` of `Status::InternalServerError` is returned.
|
/// fails, an `Err` of `Status::InternalServerError` is returned.
|
||||||
impl<T: Serialize> Responder<'static> for JSON<T> {
|
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| {
|
serde_json::to_string(&self.0).map(|string| {
|
||||||
content::JSON(string).respond().unwrap()
|
content::JSON(string).respond_to(req).unwrap()
|
||||||
}).map_err(|e| {
|
}).map_err(|e| {
|
||||||
error_!("JSON failed to serialize: {:?}", e);
|
error_!("JSON failed to serialize: {:?}", e);
|
||||||
Status::InternalServerError
|
Status::InternalServerError
|
||||||
|
@ -125,12 +122,14 @@ impl<T: Serialize> Responder<'static> for JSON<T> {
|
||||||
impl<T> Deref for JSON<T> {
|
impl<T> Deref for JSON<T> {
|
||||||
type Target = T;
|
type Target = T;
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
fn deref<'a>(&'a self) -> &'a T {
|
fn deref<'a>(&'a self) -> &'a T {
|
||||||
&self.0
|
&self.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> DerefMut for JSON<T> {
|
impl<T> DerefMut for JSON<T> {
|
||||||
|
#[inline(always)]
|
||||||
fn deref_mut<'a>(&'a mut self) -> &'a mut T {
|
fn deref_mut<'a>(&'a mut self) -> &'a mut T {
|
||||||
&mut self.0
|
&mut self.0
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,7 @@
|
||||||
#![feature(drop_types_in_const, macro_reexport)]
|
#![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
|
//! This crate contains officially sanctioned contributor libraries that provide
|
||||||
//! functionality commonly used by Rocket applications.
|
//! functionality commonly used by Rocket applications.
|
||||||
|
@ -37,10 +40,6 @@
|
||||||
#[macro_use] extern crate log;
|
#[macro_use] extern crate log;
|
||||||
#[macro_use] extern crate rocket;
|
#[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")]
|
#[cfg(feature = "serde")]
|
||||||
extern crate serde;
|
extern crate serde;
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,6 @@ extern crate rmp_serde;
|
||||||
use std::ops::{Deref, DerefMut};
|
use std::ops::{Deref, DerefMut};
|
||||||
use std::io::{Cursor, Read};
|
use std::io::{Cursor, Read};
|
||||||
|
|
||||||
use rocket::config;
|
|
||||||
use rocket::outcome::{Outcome, IntoOutcome};
|
use rocket::outcome::{Outcome, IntoOutcome};
|
||||||
use rocket::request::Request;
|
use rocket::request::Request;
|
||||||
use rocket::data::{self, Data, FromData};
|
use rocket::data::{self, Data, FromData};
|
||||||
|
@ -111,11 +110,8 @@ impl<T: DeserializeOwned> FromData for MsgPack<T> {
|
||||||
return Outcome::Forward(data);
|
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 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) {
|
if let Err(e) = data.open().take(size_limit).read_to_end(&mut buf) {
|
||||||
let e = MsgPackError::InvalidDataRead(e);
|
let e = MsgPackError::InvalidDataRead(e);
|
||||||
error_!("Couldn't read request data: {:?}", 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
|
/// Content-Type `MsgPack` and a fixed-size body with the serialization. If
|
||||||
/// serialization fails, an `Err` of `Status::InternalServerError` is returned.
|
/// serialization fails, an `Err` of `Status::InternalServerError` is returned.
|
||||||
impl<T: Serialize> Responder<'static> for MsgPack<T> {
|
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| {
|
rmp_serde::to_vec(&self.0).map_err(|e| {
|
||||||
error_!("MsgPack failed to serialize: {:?}", e);
|
error_!("MsgPack failed to serialize: {:?}", e);
|
||||||
Status::InternalServerError
|
Status::InternalServerError
|
||||||
|
|
|
@ -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");
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,53 +1,35 @@
|
||||||
extern crate handlebars;
|
extern crate handlebars;
|
||||||
|
|
||||||
use super::serde::Serialize;
|
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
fn init(templates: &[(&str, &TemplateInfo)]) -> Option<Handlebars> {
|
||||||
let mut hb = Handlebars::new();
|
let mut hb = Handlebars::new();
|
||||||
let mut success = true;
|
|
||||||
for &(name, info) in templates {
|
for &(name, info) in templates {
|
||||||
let path = &info.full_path;
|
let path = &info.path;
|
||||||
if let Err(e) = hb.register_template_file(name, path) {
|
if let Err(e) = hb.register_template_file(name, path) {
|
||||||
error_!("Handlebars template '{}' failed registry: {:?}", name, e);
|
error!("Error in Handlebars template '{}'.", name);
|
||||||
success = false;
|
info_!("{}", e);
|
||||||
}
|
info_!("Template path: '{}'.", path.to_string_lossy());
|
||||||
}
|
|
||||||
|
|
||||||
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.");
|
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
if hb.get_template(name).is_none() {
|
Some(hb)
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
error_!("Handlebars template '{}' does not exist.", name);
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
match hb.render(name, context) {
|
match self.render(name, &context) {
|
||||||
Ok(string) => Some(string),
|
Ok(string) => Some(string),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error_!("Error rendering Handlebars template '{}': {}", name, e);
|
error_!("Error rendering Handlebars template '{}': {}", name, e);
|
||||||
|
@ -55,3 +37,4 @@ pub fn render<T>(name: &str, _info: &TemplateInfo, context: &T) -> Option<String
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
)+
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
|
@ -2,25 +2,28 @@ extern crate serde;
|
||||||
extern crate serde_json;
|
extern crate serde_json;
|
||||||
extern crate glob;
|
extern crate glob;
|
||||||
|
|
||||||
#[cfg(feature = "tera_templates")]
|
#[cfg(feature = "tera_templates")] mod tera_templates;
|
||||||
mod tera_templates;
|
#[cfg(feature = "handlebars_templates")] mod handlebars_templates;
|
||||||
|
mod engine;
|
||||||
#[cfg(feature = "handlebars_templates")]
|
mod context;
|
||||||
mod handlebars_templates;
|
|
||||||
|
|
||||||
#[macro_use] mod macros;
|
|
||||||
|
|
||||||
|
use self::engine::{Engine, Engines};
|
||||||
|
use self::context::Context;
|
||||||
use self::serde::Serialize;
|
use self::serde::Serialize;
|
||||||
|
use self::serde_json::{Value, to_value};
|
||||||
use self::glob::glob;
|
use self::glob::glob;
|
||||||
|
|
||||||
|
use std::borrow::Cow;
|
||||||
use std::path::{Path, PathBuf};
|
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::response::{self, Content, Responder};
|
||||||
use rocket::http::{ContentType, Status};
|
use rocket::http::{ContentType, Status};
|
||||||
|
|
||||||
|
const DEFAULT_TEMPLATE_DIR: &'static str = "templates";
|
||||||
|
|
||||||
/// The Template type implements generic support for template rendering in
|
/// The Template type implements generic support for template rendering in
|
||||||
/// Rocket.
|
/// Rocket.
|
||||||
///
|
///
|
||||||
|
@ -28,8 +31,9 @@ use rocket::http::{ContentType, Status};
|
||||||
/// the template directory. The template directory is configurable via the
|
/// the template directory. The template directory is configurable via the
|
||||||
/// `template_dir` configuration parameter and defaults to `templates/`. The
|
/// `template_dir` configuration parameter and defaults to `templates/`. The
|
||||||
/// path set in `template_dir` should be relative to the Rocket configuration
|
/// path set in `template_dir` should be relative to the Rocket configuration
|
||||||
/// file. See the [configuration chapter](https://rocket.rs/guide/overview/#configuration)
|
/// file. See the [configuration
|
||||||
/// of the guide for more information on 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
|
/// Templates are discovered according to their extension. At present, this
|
||||||
/// library supports the following templates and extensions:
|
/// 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
|
/// type, and one for the template extension. This means that template
|
||||||
/// extensions should look like: `.html.hbs`, `.html.tera`, `.xml.hbs`, etc.
|
/// 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
|
/// 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
|
/// name of a template and a context to render the template with. The context
|
||||||
/// can be any type that implements `Serialize` from
|
/// can be any type that implements `Serialize` from
|
||||||
|
@ -74,7 +85,25 @@ use rocket::http::{ContentType, Status};
|
||||||
/// features = ["handlebars_templates", "tera_templates"]
|
/// 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:
|
/// returned from a request handler directly:
|
||||||
///
|
///
|
||||||
/// ```rust,ignore
|
/// ```rust,ignore
|
||||||
|
@ -84,49 +113,68 @@ use rocket::http::{ContentType, Status};
|
||||||
/// Template::render("index", &context)
|
/// Template::render("index", &context)
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
// Fields are: (optionally rendered template, template extension)
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Template(Option<String>, Option<String>);
|
pub struct Template {
|
||||||
|
name: Cow<'static, str>,
|
||||||
|
value: Option<Value>
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct TemplateInfo {
|
pub struct TemplateInfo {
|
||||||
/// The complete path, including `template_dir`, to this template.
|
/// The complete path, including `template_dir`, to this template.
|
||||||
full_path: PathBuf,
|
|
||||||
/// The complete path, without `template_dir`, to this template.
|
|
||||||
path: PathBuf,
|
path: PathBuf,
|
||||||
/// The extension for the engine of this template.
|
/// The extension for the engine of this template.
|
||||||
extension: String,
|
extension: String,
|
||||||
/// The extension before the engine extension in the template, if any.
|
/// The extension before the engine extension in the template, if any.
|
||||||
data_type: Option<String>
|
data_type: ContentType
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Template {
|
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
|
/// Render the template named `name` with the context `context`. The
|
||||||
/// `context` can be of any type that implements `Serialize`. This is
|
/// `context` can be of any type that implements `Serialize`. This is
|
||||||
/// typically a `HashMap` or a custom `struct`.
|
/// typically a `HashMap` or a custom `struct`.
|
||||||
|
@ -141,30 +189,73 @@ impl Template {
|
||||||
/// let mut context = HashMap::new();
|
/// let mut context = HashMap::new();
|
||||||
///
|
///
|
||||||
/// # context.insert("test", "test");
|
/// # context.insert("test", "test");
|
||||||
/// let template = Template::render("index", &context);
|
/// # #[allow(unused_variables)]
|
||||||
/// # assert_eq!(template.to_string(), "");
|
/// let template = Template::render("index", context);
|
||||||
pub fn render<S, T>(name: S, context: &T) -> Template
|
#[inline]
|
||||||
where S: AsRef<str>, T: Serialize
|
pub fn render<S, C>(name: S, context: C) -> Template
|
||||||
|
where S: Into<Cow<'static, str>>, C: Serialize
|
||||||
{
|
{
|
||||||
let name = name.as_ref();
|
Template { name: name.into(), value: to_value(context).ok() }
|
||||||
let template = TEMPLATES.get(name);
|
|
||||||
if template.is_none() {
|
|
||||||
let names: Vec<_> = 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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Keep this set in-sync with the `engine_set` invocation. The macro
|
/// Render the template named `name` located at the path `root` with the
|
||||||
// `return`s a `Template` if the extenion in `template` matches an
|
/// context `context` into a `String`. This method is _very slow_ and should
|
||||||
// engine in the set. Otherwise, control will fall through.
|
/// **not** be used in any running Rocket application. This method should
|
||||||
render_set!(name, template.unwrap(), context,
|
/// only be used during testing to validate `Template` responses. For other
|
||||||
"tera_templates" => tera_templates,
|
/// uses, use [`render`](#method.render) instead.
|
||||||
"handlebars_templates" => handlebars_templates,
|
///
|
||||||
);
|
/// 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)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
unreachable!("A template extension was discovered but not rendered.")
|
#[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: {}", ts.join(","));
|
||||||
|
info_!("Searched in '{:?}'.", ctxt.root);
|
||||||
|
Status::InternalServerError
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let value = self.value.ok_or_else(|| {
|
||||||
|
error_!("The provided template context failed to serialize.");
|
||||||
|
Status::InternalServerError
|
||||||
|
})?;
|
||||||
|
|
||||||
|
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
|
/// extension and a fixed-size body containing the rendered template. If
|
||||||
/// rendering fails, an `Err` of `Status::InternalServerError` is returned.
|
/// rendering fails, an `Err` of `Status::InternalServerError` is returned.
|
||||||
impl Responder<'static> for Template {
|
impl Responder<'static> for Template {
|
||||||
fn respond(self) -> response::Result<'static> {
|
fn respond_to(self, req: &Request) -> response::Result<'static> {
|
||||||
let content_type = match self.1 {
|
let ctxt = req.guard::<State<Context>>().succeeded().ok_or_else(|| {
|
||||||
Some(ref ext) => ContentType::from_extension(ext),
|
error_!("Uninitialized template context: missing fairing.");
|
||||||
None => ContentType::HTML
|
info_!("To use templates, you must attach `Template::fairing()`.");
|
||||||
};
|
info_!("See the `Template` documentation for more information.");
|
||||||
|
Status::InternalServerError
|
||||||
|
})?;
|
||||||
|
|
||||||
match self.0 {
|
let (render, content_type) = self.finalize(&ctxt)?;
|
||||||
Some(render) => Content(content_type, render).respond(),
|
Content(content_type, render).respond_to(req)
|
||||||
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");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,59 +1,43 @@
|
||||||
extern crate tera;
|
extern crate tera;
|
||||||
|
|
||||||
use super::serde::Serialize;
|
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";
|
|
||||||
|
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
fn init(templates: &[(&str, &TemplateInfo)]) -> Option<Tera> {
|
||||||
|
// Create the Tera instance.
|
||||||
let mut tera = Tera::default();
|
let mut tera = Tera::default();
|
||||||
let ext = [".html.tera", ".htm.tera", ".xml.tera", ".html", ".htm", ".xml"];
|
let ext = [".html.tera", ".htm.tera", ".xml.tera", ".html", ".htm", ".xml"];
|
||||||
tera.autoescape_on(ext.to_vec());
|
tera.autoescape_on(ext.to_vec());
|
||||||
|
|
||||||
// Collect into a tuple of (name, path) for Tera.
|
// Collect into a tuple of (name, path) for Tera.
|
||||||
let tera_templates = templates.iter()
|
let tera_templates = templates.iter()
|
||||||
.map(|&(name, info)| (&info.full_path, Some(name)))
|
.map(|&(name, info)| (&info.path, Some(name)))
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
// Finally try to tell Tera about all of the templates.
|
// Finally try to tell Tera about all of the templates.
|
||||||
let mut success = true;
|
|
||||||
if let Err(e) = tera.add_template_files(tera_templates) {
|
if let Err(e) = tera.add_template_files(tera_templates) {
|
||||||
error_!("Failed to initialize Tera templates: {:?}", e);
|
error!("Failed to initialize Tera templating.");
|
||||||
success = false;
|
for error in e.iter().skip(1) {
|
||||||
|
info_!("{}.", error);
|
||||||
|
}
|
||||||
|
return None
|
||||||
}
|
}
|
||||||
|
|
||||||
TERA = Some(tera);
|
Some(tera)
|
||||||
success
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn render<T>(name: &str, _: &TemplateInfo, context: &T) -> Option<String>
|
fn render<C: Serialize>(&self, name: &str, context: C) -> Option<String> {
|
||||||
where T: Serialize
|
if self.get_template(name).is_err() {
|
||||||
{
|
|
||||||
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);
|
error_!("Tera template '{}' does not exist.", name);
|
||||||
return None;
|
return None;
|
||||||
};
|
};
|
||||||
|
|
||||||
match tera.render(name, context) {
|
match self.render(name, &context) {
|
||||||
Ok(string) => Some(string),
|
Ok(string) => Some(string),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error_!("Error rendering Tera template '{}'.", name);
|
error_!("Error rendering Tera template '{}'.", name);
|
||||||
|
@ -65,3 +49,4 @@ pub fn render<T>(name: &str, _: &TemplateInfo, context: &T) -> Option<String>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -2,18 +2,13 @@ extern crate rocket;
|
||||||
extern crate rocket_contrib;
|
extern crate rocket_contrib;
|
||||||
|
|
||||||
use std::env;
|
use std::env;
|
||||||
use rocket::config::Config;
|
use std::path::PathBuf;
|
||||||
use rocket::config::Environment::*;
|
|
||||||
|
|
||||||
fn init() {
|
fn template_root() -> PathBuf {
|
||||||
let cwd = env::current_dir().expect("current working directory");
|
let cwd = env::current_dir().expect("current working directory");
|
||||||
let tests_dir = cwd.join("tests");
|
cwd.join("tests").join("templates")
|
||||||
|
|
||||||
let config = Config::build(Development).root(tests_dir).unwrap();
|
|
||||||
rocket::custom(config, true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: Do something about overlapping configs.
|
|
||||||
#[cfg(feature = "tera_templates")]
|
#[cfg(feature = "tera_templates")]
|
||||||
mod tera_tests {
|
mod tera_tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
@ -27,19 +22,17 @@ mod tera_tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_tera_templates() {
|
fn test_tera_templates() {
|
||||||
init();
|
|
||||||
|
|
||||||
let mut map = HashMap::new();
|
let mut map = HashMap::new();
|
||||||
map.insert("title", "_test_");
|
map.insert("title", "_test_");
|
||||||
map.insert("content", "<script />");
|
map.insert("content", "<script />");
|
||||||
|
|
||||||
// Test with a txt file, which shouldn't escape.
|
// Test with a txt file, which shouldn't escape.
|
||||||
let template = Template::render("tera/txt_test", &map);
|
let template = Template::show(template_root(), "tera/txt_test", &map);
|
||||||
assert_eq!(&template.to_string(), UNESCAPED_EXPECTED);
|
assert_eq!(template, Some(UNESCAPED_EXPECTED.into()));
|
||||||
|
|
||||||
// Now with an HTML file, which should.
|
// Now with an HTML file, which should.
|
||||||
let template = Template::render("tera/html_test", &map);
|
let template = Template::show(template_root(), "tera/html_test", &map);
|
||||||
assert_eq!(&template.to_string(), ESCAPED_EXPECTED);
|
assert_eq!(template, Some(ESCAPED_EXPECTED.into()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -54,15 +47,13 @@ mod handlebars_tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_handlebars_templates() {
|
fn test_handlebars_templates() {
|
||||||
init();
|
|
||||||
|
|
||||||
let mut map = HashMap::new();
|
let mut map = HashMap::new();
|
||||||
map.insert("title", "_test_");
|
map.insert("title", "_test_");
|
||||||
map.insert("content", "<script /> hi");
|
map.insert("content", "<script /> hi");
|
||||||
|
|
||||||
// Test with a txt file, which shouldn't escape.
|
// Test with a txt file, which shouldn't escape.
|
||||||
let template = Template::render("hbs/test", &map);
|
let template = Template::show(template_root(), "hbs/test", &map);
|
||||||
assert_eq!(&template.to_string(), EXPECTED);
|
assert_eq!(template, Some(EXPECTED.into()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,10 +0,0 @@
|
||||||
#![feature(plugin)]
|
|
||||||
#![plugin(rocket_codegen)]
|
|
||||||
|
|
||||||
extern crate rocket;
|
|
||||||
|
|
||||||
#[get("/")]
|
|
||||||
#[allow(unmounted_route)]
|
|
||||||
pub fn hello() -> &'static str {
|
|
||||||
"Hello, world!"
|
|
||||||
}
|
|
|
@ -2,9 +2,8 @@
|
||||||
#![plugin(rocket_codegen)]
|
#![plugin(rocket_codegen)]
|
||||||
|
|
||||||
extern crate rocket;
|
extern crate rocket;
|
||||||
extern crate config;
|
|
||||||
|
|
||||||
// This example's illustration is the Rocket.toml file.
|
// This example's illustration is the Rocket.toml file.
|
||||||
fn main() {
|
fn main() {
|
||||||
rocket::ignite().mount("/hello", routes![config::hello]).launch();
|
rocket::ignite().launch();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,21 +1,22 @@
|
||||||
extern crate rocket;
|
use rocket::{self, State};
|
||||||
extern crate config as lib;
|
use rocket::fairing::AdHoc;
|
||||||
|
use rocket::config::{self, Config, Environment};
|
||||||
use rocket::config::{self, Environment};
|
use rocket::http::{Method, Status};
|
||||||
use rocket::http::Method;
|
|
||||||
use rocket::LoggingLevel;
|
use rocket::LoggingLevel;
|
||||||
use rocket::testing::MockRequest;
|
use rocket::testing::MockRequest;
|
||||||
|
|
||||||
pub fn test_config(environment: Environment) {
|
struct LocalConfig(Config);
|
||||||
// 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]);
|
|
||||||
|
|
||||||
// Get the active environment and ensure that it matches our expectations.
|
#[get("/check_config")]
|
||||||
let config = config::active().unwrap();
|
fn check_config(config: State<LocalConfig>) -> Option<()> {
|
||||||
match environment {
|
let environment = match ::std::env::var("ROCKET_ENV") {
|
||||||
Environment::Development => {
|
Ok(name) => name,
|
||||||
|
Err(_) => return None
|
||||||
|
};
|
||||||
|
|
||||||
|
let config = &config.0;
|
||||||
|
match &*environment {
|
||||||
|
"development" => {
|
||||||
assert_eq!(config.address, "localhost".to_string());
|
assert_eq!(config.address, "localhost".to_string());
|
||||||
assert_eq!(config.port, 8000);
|
assert_eq!(config.port, 8000);
|
||||||
assert_eq!(config.workers, 1);
|
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_str("hi"), Ok("Hello!"));
|
||||||
assert_eq!(config.get_bool("is_extra"), Ok(true));
|
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.address, "0.0.0.0".to_string());
|
||||||
assert_eq!(config.port, 80);
|
assert_eq!(config.port, 80);
|
||||||
assert_eq!(config.workers, 8);
|
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.environment, config::Environment::Staging);
|
||||||
assert_eq!(config.extras().count(), 0);
|
assert_eq!(config.extras().count(), 0);
|
||||||
}
|
}
|
||||||
Environment::Production => {
|
"production" => {
|
||||||
assert_eq!(config.address, "0.0.0.0".to_string());
|
assert_eq!(config.address, "0.0.0.0".to_string());
|
||||||
assert_eq!(config.port, 80);
|
assert_eq!(config.port, 80);
|
||||||
assert_eq!(config.workers, 12);
|
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.environment, config::Environment::Production);
|
||||||
assert_eq!(config.extras().count(), 0);
|
assert_eq!(config.extras().count(), 0);
|
||||||
}
|
}
|
||||||
|
_ => {
|
||||||
|
panic!("Unknown environment in envvar: {}", environment);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn test_hello() {
|
Some(())
|
||||||
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
|
||||||
assert_eq!(response.body_string(), Some("Hello, world!".into()));
|
// environment in `ignite()`. We'll read this back in the handler to config.
|
||||||
|
::std::env::set_var("ROCKET_ENV", environment.to_string());
|
||||||
|
|
||||||
|
// 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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,5 +8,4 @@ mod common;
|
||||||
#[test]
|
#[test]
|
||||||
fn test_development_config() {
|
fn test_development_config() {
|
||||||
common::test_config(rocket::config::Environment::Development);
|
common::test_config(rocket::config::Environment::Development);
|
||||||
common::test_hello();
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,5 +8,4 @@ mod common;
|
||||||
#[test]
|
#[test]
|
||||||
fn test_production_config() {
|
fn test_production_config() {
|
||||||
common::test_config(rocket::config::Environment::Production);
|
common::test_config(rocket::config::Environment::Production);
|
||||||
common::test_hello();
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,5 +8,4 @@ mod common;
|
||||||
#[test]
|
#[test]
|
||||||
fn test_staging_config() {
|
fn test_staging_config() {
|
||||||
common::test_config(rocket::config::Environment::Staging);
|
common::test_config(rocket::config::Environment::Staging);
|
||||||
common::test_hello();
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,6 +36,10 @@ fn index(cookies: Cookies) -> Template {
|
||||||
Template::render("index", &context)
|
Template::render("index", &context)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn rocket() -> rocket::Rocket {
|
||||||
rocket::ignite().mount("/", routes![submit, index]).launch();
|
rocket::ignite().mount("/", routes![submit, index]).attach(Template::fairing())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
rocket().launch();
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,9 +5,11 @@ use rocket::testing::MockRequest;
|
||||||
use rocket::http::*;
|
use rocket::http::*;
|
||||||
use rocket_contrib::Template;
|
use rocket_contrib::Template;
|
||||||
|
|
||||||
|
const TEMPLATE_ROOT: &'static str = "templates/";
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_submit() {
|
fn test_submit() {
|
||||||
let rocket = rocket::ignite().mount("/", routes![super::index, super::submit]);
|
let rocket = rocket();
|
||||||
let mut request = MockRequest::new(Method::Post, "/submit")
|
let mut request = MockRequest::new(Method::Post, "/submit")
|
||||||
.header(ContentType::Form)
|
.header(ContentType::Form)
|
||||||
.body("message=Hello from Rocket!");
|
.body("message=Hello from Rocket!");
|
||||||
|
@ -21,7 +23,7 @@ fn test_submit() {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn test_body(optional_cookie: Option<Cookie<'static>>, expected_body: String) {
|
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, "/");
|
let mut request = MockRequest::new(Method::Get, "/");
|
||||||
|
|
||||||
// Attach a cookie if one is given.
|
// 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() {
|
fn test_index() {
|
||||||
// Render the template with an empty context to test against.
|
// Render the template with an empty context to test against.
|
||||||
let mut context: HashMap<&str, &str> = HashMap::new();
|
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 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.
|
// Render the template with a context that contains the message.
|
||||||
context.insert("message", "Hello from Rocket!");
|
context.insert("message", "Hello from Rocket!");
|
||||||
|
|
||||||
// Test the route with the "message" cookie.
|
// Test the route with the "message" cookie.
|
||||||
let cookie = Cookie::new("message", "Hello from Rocket!");
|
let cookie = Cookie::new("message", "Hello from Rocket!");
|
||||||
let template = Template::render("index", &context);
|
let template = Template::show(TEMPLATE_ROOT, "index", &context).unwrap();
|
||||||
test_body(Some(cookie), template.to_string());
|
test_body(Some(cookie), template);
|
||||||
}
|
}
|
||||||
|
|
|
@ -72,7 +72,7 @@ fn rocket() -> rocket::Rocket {
|
||||||
let token_val = rocket.config().get_int("token").unwrap_or(-1);
|
let token_val = rocket.config().get_int("token").unwrap_or(-1);
|
||||||
Ok(rocket.manage(Token(token_val)))
|
Ok(rocket.manage(Token(token_val)))
|
||||||
}))
|
}))
|
||||||
.attach(AdHoc::on_launch(|rocket| {
|
.attach(AdHoc::on_launch(|_| {
|
||||||
println!("Rocket is about to launch!");
|
println!("Rocket is about to launch!");
|
||||||
}))
|
}))
|
||||||
.attach(AdHoc::on_request(|req, _| {
|
.attach(AdHoc::on_request(|req, _| {
|
||||||
|
|
|
@ -40,9 +40,13 @@ fn not_found(req: &Request) -> Template {
|
||||||
Template::render("error/404", &map)
|
Template::render("error/404", &map)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn rocket() -> rocket::Rocket {
|
||||||
rocket::ignite()
|
rocket::ignite()
|
||||||
.mount("/", routes![index, get])
|
.mount("/", routes![index, get])
|
||||||
|
.attach(Template::fairing())
|
||||||
.catch(errors![not_found])
|
.catch(errors![not_found])
|
||||||
.launch();
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
rocket().launch();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,16 +1,15 @@
|
||||||
use rocket;
|
use super::rocket;
|
||||||
use rocket::testing::MockRequest;
|
use rocket::testing::MockRequest;
|
||||||
use rocket::http::Method::*;
|
use rocket::http::Method::*;
|
||||||
use rocket::http::Status;
|
use rocket::http::Status;
|
||||||
use rocket::Response;
|
use rocket::Response;
|
||||||
use rocket_contrib::Template;
|
use rocket_contrib::Template;
|
||||||
|
|
||||||
|
const TEMPLATE_ROOT: &'static str = "templates/";
|
||||||
|
|
||||||
macro_rules! run_test {
|
macro_rules! run_test {
|
||||||
($req:expr, $test_fn:expr) => ({
|
($req:expr, $test_fn:expr) => ({
|
||||||
let rocket = rocket::ignite()
|
let rocket = rocket();
|
||||||
.mount("/", routes![super::index, super::get])
|
|
||||||
.catch(errors![super::not_found]);
|
|
||||||
|
|
||||||
let mut req = $req;
|
let mut req = $req;
|
||||||
$test_fn(req.dispatch_with(&rocket));
|
$test_fn(req.dispatch_with(&rocket));
|
||||||
})
|
})
|
||||||
|
@ -25,8 +24,8 @@ fn test_root() {
|
||||||
assert_eq!(response.status(), Status::SeeOther);
|
assert_eq!(response.status(), Status::SeeOther);
|
||||||
assert!(response.body().is_none());
|
assert!(response.body().is_none());
|
||||||
|
|
||||||
let location_headers: Vec<_> = response.headers().get("Location").collect();
|
let location: Vec<_> = response.headers().get("Location").collect();
|
||||||
assert_eq!(location_headers, vec!["/hello/Unknown"]);
|
assert_eq!(location, vec!["/hello/Unknown"]);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -36,10 +35,10 @@ fn test_root() {
|
||||||
run_test!(req, |mut response: Response| {
|
run_test!(req, |mut response: Response| {
|
||||||
let mut map = ::std::collections::HashMap::new();
|
let mut map = ::std::collections::HashMap::new();
|
||||||
map.insert("path", "/");
|
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.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()
|
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));
|
assert_eq!(response.body_string(), Some(expected));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -70,7 +69,8 @@ fn test_404() {
|
||||||
|
|
||||||
let mut map = ::std::collections::HashMap::new();
|
let mut map = ::std::collections::HashMap::new();
|
||||||
map.insert("path", "/hello/");
|
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));
|
assert_eq!(response.body_string(), Some(expected));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,13 +17,13 @@ fn forward(_req: &Request, data: Data) -> Outcome<'static> {
|
||||||
Outcome::forward(data)
|
Outcome::forward(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn hi(_req: &Request, _: Data) -> Outcome<'static> {
|
fn hi(req: &Request, _: Data) -> Outcome<'static> {
|
||||||
Outcome::of("Hello!")
|
Outcome::from(req, "Hello!")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn name<'a>(req: &'a Request, _: Data) -> Outcome<'a> {
|
fn name<'a>(req: &'a Request, _: Data) -> Outcome<'a> {
|
||||||
let param = req.get_param::<&'a RawStr>(0);
|
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> {
|
fn echo_url(req: &Request, _: Data) -> Outcome<'static> {
|
||||||
|
@ -32,7 +32,7 @@ fn echo_url(req: &Request, _: Data) -> Outcome<'static> {
|
||||||
.split_at(6)
|
.split_at(6)
|
||||||
.1;
|
.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> {
|
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");
|
let file = File::create("/tmp/upload.txt");
|
||||||
if let Ok(mut file) = file {
|
if let Ok(mut file) = file {
|
||||||
if let Ok(n) = io::copy(&mut data.open(), &mut 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.");
|
println!(" => Failed copying.");
|
||||||
|
@ -55,12 +55,13 @@ fn upload<'r>(req: &'r Request, data: Data) -> Outcome<'r> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_upload(_: &Request, _: Data) -> Outcome<'static> {
|
fn get_upload(req: &Request, _: Data) -> Outcome<'static> {
|
||||||
Outcome::of(File::open("/tmp/upload.txt").ok())
|
Outcome::from(req, File::open("/tmp/upload.txt").ok())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn not_found_handler<'r>(_: Error, req: &'r Request) -> response::Result<'r> {
|
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 {
|
fn rocket() -> rocket::Rocket {
|
||||||
|
|
|
@ -32,7 +32,7 @@ impl<'a, 'r> FromRequest<'a, 'r> for Conn {
|
||||||
type Error = ();
|
type Error = ();
|
||||||
|
|
||||||
fn from_request(request: &'a Request<'r>) -> request::Outcome<Conn, ()> {
|
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::Success(pool) => pool,
|
||||||
Outcome::Failure(e) => return Outcome::Failure(e),
|
Outcome::Failure(e) => return Outcome::Failure(e),
|
||||||
Outcome::Forward(_) => return Outcome::Forward(()),
|
Outcome::Forward(_) => return Outcome::Forward(()),
|
||||||
|
|
|
@ -76,5 +76,6 @@ fn main() {
|
||||||
.manage(db::init_pool())
|
.manage(db::init_pool())
|
||||||
.mount("/", routes![index, static_files::all])
|
.mount("/", routes![index, static_files::all])
|
||||||
.mount("/todo/", routes![new, toggle, delete])
|
.mount("/todo/", routes![new, toggle, delete])
|
||||||
|
.attach(Template::fairing())
|
||||||
.launch();
|
.launch();
|
||||||
}
|
}
|
||||||
|
|
|
@ -83,11 +83,12 @@ impl Catcher {
|
||||||
/// use rocket::http::Status;
|
/// use rocket::http::Status;
|
||||||
///
|
///
|
||||||
/// fn handle_404<'r>(_: Error, req: &'r Request) -> Result<'r> {
|
/// 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> {
|
/// fn handle_500<'r>(_: Error, req: &'r Request) -> Result<'r> {
|
||||||
/// "Whoops, we messed up!".respond()
|
/// "Whoops, we messed up!".respond_to(req)
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// let not_found_catcher = Catcher::new(404, handle_404);
|
/// let not_found_catcher = Catcher::new(404, handle_404);
|
||||||
|
@ -156,10 +157,10 @@ macro_rules! default_errors {
|
||||||
let mut map = HashMap::new();
|
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(),
|
status::Custom(Status::from_code($code).unwrap(),
|
||||||
content::HTML(error_page_template!($code, $name, $description))
|
content::HTML(error_page_template!($code, $name, $description))
|
||||||
).respond()
|
).respond_to(req)
|
||||||
}
|
}
|
||||||
|
|
||||||
map.insert($code, Catcher::new_default($code, $fn_name));
|
map.insert($code, Catcher::new_default($code, $fn_name));
|
||||||
|
|
|
@ -57,7 +57,7 @@ macro_rules! config_from_raw {
|
||||||
$($key:ident => ($type:ident, $set:ident, $map:expr)),+ | _ => $rest:expr) => (
|
$($key:ident => ($type:ident, $set:ident, $map:expr)),+ | _ => $rest:expr) => (
|
||||||
match $name {
|
match $name {
|
||||||
$(stringify!($key) => {
|
$(stringify!($key) => {
|
||||||
concat_idents!(value_as_, $type)($config, $name, $value)
|
super::custom_values::$type($config, $name, $value)
|
||||||
.and_then(|parsed| $map($config.$set(parsed)))
|
.and_then(|parsed| $map($config.$set(parsed)))
|
||||||
})+
|
})+
|
||||||
_ => $rest
|
_ => $rest
|
||||||
|
|
|
@ -50,7 +50,8 @@ pub struct Limits {
|
||||||
|
|
||||||
impl Default for Limits {
|
impl Default for Limits {
|
||||||
fn default() -> 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"))
|
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() {
|
match value.as_integer() {
|
||||||
Some(x) if x >= 0 => Ok(x as u64),
|
Some(x) if x >= 0 => Ok(x as u64),
|
||||||
_ => Err(conf.bad_type(name, value.type_str(), "an unsigned integer"))
|
_ => 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() {
|
match value.as_integer() {
|
||||||
Some(x) if x >= 0 && x <= (u16::max_value() as i64) => Ok(x as u16),
|
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"))
|
_ => 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,
|
name: &str,
|
||||||
value: &Value
|
value: &Value
|
||||||
) -> Result<LoggingLevel> {
|
) -> 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)))
|
.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,
|
name: &str,
|
||||||
value: &'v Value,
|
value: &'v Value,
|
||||||
) -> Result<(&'v str, &'v str)> {
|
) -> Result<(&'v str, &'v str)> {
|
||||||
|
@ -140,8 +141,8 @@ pub fn value_as_tls_config<'v>(conf: &Config,
|
||||||
let env = conf.environment;
|
let env = conf.environment;
|
||||||
for (key, value) in table {
|
for (key, value) in table {
|
||||||
match key.as_str() {
|
match key.as_str() {
|
||||||
"certs" => certs_path = Some(value_as_str(conf, "tls.certs", value)?),
|
"certs" => certs_path = Some(str(conf, "tls.certs", value)?),
|
||||||
"key" => key_path = Some(value_as_str(conf, "tls.key", value)?),
|
"key" => key_path = Some(str(conf, "tls.key", value)?),
|
||||||
_ => return Err(ConfigError::UnknownKey(format!("{}.tls.{}", env, key)))
|
_ => 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()
|
let table = value.as_table()
|
||||||
.ok_or_else(|| conf.bad_type(name, value.type_str(), "a table"))?;
|
.ok_or_else(|| conf.bad_type(name, value.type_str(), "a table"))?;
|
||||||
|
|
||||||
let mut limits = Limits::default();
|
let mut limits = Limits::default();
|
||||||
for (key, val) in table {
|
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);
|
limits = limits.add(key.as_str(), val);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -158,28 +158,33 @@
|
||||||
//! ## Retrieving Configuration Parameters
|
//! ## Retrieving Configuration Parameters
|
||||||
//!
|
//!
|
||||||
//! Configuration parameters for the currently active configuration environment
|
//! Configuration parameters for the currently active configuration environment
|
||||||
//! can be retrieved via the [active](fn.active.html) function and methods on
|
//! can be retrieved via the [config](/rocket/struct.Rocket.html#method.config)
|
||||||
//! the [Config](struct.Config.html) structure. The general structure is to call
|
//! method on an instance of `Rocket` and `get_` methods on the
|
||||||
//! `active` and then one of the `get_` methods on the returned `Config`
|
//! [Config](struct.Config.html) structure.
|
||||||
//! structure.
|
|
||||||
//!
|
//!
|
||||||
//! As an example, consider the following code used by the `Template` type to
|
//! The retrivial of configuration parameters usually occurs at launch time via
|
||||||
//! retrieve the value of the `template_dir` configuration parameter. If the
|
//! a [launch fairing](/rocket/fairing/trait.Fairing.html). If information about
|
||||||
//! value isn't present or isn't a string, a default value is used.
|
//! 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
|
//! ```rust
|
||||||
//! use std::path::PathBuf;
|
//! use rocket::fairing::AdHoc;
|
||||||
//! use rocket::config::{self, ConfigError};
|
|
||||||
//!
|
//!
|
||||||
//! const DEFAULT_TEMPLATE_DIR: &'static str = "templates";
|
//! struct Token(i64);
|
||||||
//!
|
//!
|
||||||
//! # #[allow(unused_variables)]
|
//! fn main() {
|
||||||
//! let template_dir = config::active().ok_or(ConfigError::NotFound)
|
//! rocket::ignite()
|
||||||
//! .map(|config| config.root().join(DEFAULT_TEMPLATE_DIR))
|
//! .attach(AdHoc::on_attach(|rocket| {
|
||||||
//! .unwrap_or_else(|_| PathBuf::from(DEFAULT_TEMPLATE_DIR));
|
//! 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 error;
|
||||||
mod environment;
|
mod environment;
|
||||||
|
@ -188,7 +193,6 @@ mod builder;
|
||||||
mod toml_ext;
|
mod toml_ext;
|
||||||
mod custom_values;
|
mod custom_values;
|
||||||
|
|
||||||
use std::sync::{Once, ONCE_INIT};
|
|
||||||
use std::fs::{self, File};
|
use std::fs::{self, File};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
|
@ -213,9 +217,6 @@ use self::toml_ext::parse_simple_toml_value;
|
||||||
use logger::{self, LoggingLevel};
|
use logger::{self, LoggingLevel};
|
||||||
use http::uncased::uncased_eq;
|
use http::uncased::uncased_eq;
|
||||||
|
|
||||||
static INIT: Once = ONCE_INIT;
|
|
||||||
static mut CONFIG: Option<RocketConfig> = None;
|
|
||||||
|
|
||||||
const CONFIG_FILENAME: &'static str = "Rocket.toml";
|
const CONFIG_FILENAME: &'static str = "Rocket.toml";
|
||||||
const GLOBAL_ENV_NAME: &'static str = "global";
|
const GLOBAL_ENV_NAME: &'static str = "global";
|
||||||
const ENV_VAR_PREFIX: &'static str = "ROCKET_";
|
const ENV_VAR_PREFIX: &'static str = "ROCKET_";
|
||||||
|
@ -466,32 +467,7 @@ impl RocketConfig {
|
||||||
/// # Panics
|
/// # Panics
|
||||||
///
|
///
|
||||||
/// If there is a problem, prints a nice error message and bails.
|
/// If there is a problem, prints a nice error message and bails.
|
||||||
pub(crate) fn init() -> (&'static Config, bool) {
|
pub(crate) fn init() -> Config {
|
||||||
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() {
|
|
||||||
let bail = |e: ConfigError| -> ! {
|
let bail = |e: ConfigError| -> ! {
|
||||||
logger::init(LoggingLevel::Debug);
|
logger::init(LoggingLevel::Debug);
|
||||||
e.pretty_print();
|
e.pretty_print();
|
||||||
|
@ -515,16 +491,8 @@ unsafe fn private_init() {
|
||||||
RocketConfig::active_default(&default_path).unwrap_or_else(|e| bail(e))
|
RocketConfig::active_default(&default_path).unwrap_or_else(|e| bail(e))
|
||||||
});
|
});
|
||||||
|
|
||||||
CONFIG = Some(config);
|
// FIXME: Should probably store all of the config.
|
||||||
}
|
config.active().clone()
|
||||||
|
|
||||||
/// 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()) }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
//! Types and traits for handling incoming body data.
|
||||||
|
|
||||||
mod data;
|
mod data;
|
||||||
mod data_stream;
|
mod data_stream;
|
||||||
mod net_stream;
|
mod net_stream;
|
||||||
|
|
|
@ -24,8 +24,8 @@ pub enum Error {
|
||||||
///
|
///
|
||||||
/// In almost every instance, a launch error occurs because of an I/O 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
|
/// 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
|
/// because of ill-defined routes that lead to collisions or because a fairing
|
||||||
/// fairing encounted an error; these are represented by the `Collision` and
|
/// encountered an error; these are represented by the `Collision` and
|
||||||
/// `FailedFairing` variants, respectively. The `Unknown` variant captures all
|
/// `FailedFairing` variants, respectively. The `Unknown` variant captures all
|
||||||
/// other kinds of launch errors.
|
/// other kinds of launch errors.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -196,7 +196,7 @@ impl Drop for LaunchError {
|
||||||
panic!("route collisions detected");
|
panic!("route collisions detected");
|
||||||
}
|
}
|
||||||
LaunchErrorKind::FailedFairing => {
|
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");
|
panic!("launch fairing failure");
|
||||||
}
|
}
|
||||||
LaunchErrorKind::Unknown(ref e) => {
|
LaunchErrorKind::Unknown(ref e) => {
|
||||||
|
|
|
@ -85,8 +85,8 @@ impl Fairings {
|
||||||
use term_painter::ToStyle;
|
use term_painter::ToStyle;
|
||||||
use term_painter::Color::{White, Magenta};
|
use term_painter::Color::{White, Magenta};
|
||||||
|
|
||||||
if self.all_fairings.len() > 0 {
|
if self.all_fairings.is_empty() {
|
||||||
info!("📡 {}:", Magenta.paint("Fairings"));
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
fn info_if_nonempty(kind: &str, fairings: &[&Fairing]) {
|
fn info_if_nonempty(kind: &str, fairings: &[&Fairing]) {
|
||||||
|
@ -97,6 +97,7 @@ impl Fairings {
|
||||||
White.paint(names.join(", ")));
|
White.paint(names.join(", ")));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
info!("📡 {}:", Magenta.paint("Fairings"));
|
||||||
info_if_nonempty("launch", &self.launch);
|
info_if_nonempty("launch", &self.launch);
|
||||||
info_if_nonempty("request", &self.request);
|
info_if_nonempty("request", &self.request);
|
||||||
info_if_nonempty("response", &self.response);
|
info_if_nonempty("response", &self.response);
|
||||||
|
|
|
@ -12,8 +12,8 @@ pub type Outcome<'r> = outcome::Outcome<Response<'r>, Status, Data>;
|
||||||
|
|
||||||
impl<'r> Outcome<'r> {
|
impl<'r> Outcome<'r> {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn of<T: Responder<'r>>(responder: T) -> Outcome<'r> {
|
pub fn from<T: Responder<'r>>(req: &Request, responder: T) -> Outcome<'r> {
|
||||||
match responder.respond() {
|
match responder.respond_to(req) {
|
||||||
Ok(response) => outcome::Outcome::Success(response),
|
Ok(response) => outcome::Outcome::Success(response),
|
||||||
Err(status) => outcome::Outcome::Failure(status)
|
Err(status) => outcome::Outcome::Failure(status)
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,6 @@
|
||||||
#![feature(lookup_host)]
|
#![feature(lookup_host)]
|
||||||
#![feature(plugin)]
|
#![feature(plugin)]
|
||||||
#![feature(never_type)]
|
#![feature(never_type)]
|
||||||
#![feature(concat_idents)]
|
|
||||||
#![feature(more_io_inner_methods)]
|
#![feature(more_io_inner_methods)]
|
||||||
|
|
||||||
#![plugin(pear_codegen)]
|
#![plugin(pear_codegen)]
|
||||||
|
|
|
@ -141,13 +141,20 @@ impl Log for RocketLogger {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub fn init(level: LoggingLevel) {
|
pub fn try_init(level: LoggingLevel, verbose: bool) {
|
||||||
let result = log::set_logger(|max_log_level| {
|
let result = log::set_logger(|max_log_level| {
|
||||||
max_log_level.set(level.max_log_level().to_log_level_filter());
|
max_log_level.set(level.max_log_level().to_log_level_filter());
|
||||||
Box::new(RocketLogger(level))
|
Box::new(RocketLogger(level))
|
||||||
});
|
});
|
||||||
|
|
||||||
if let Err(err) = result {
|
if let Err(err) = result {
|
||||||
|
if verbose {
|
||||||
println!("Logger failed to initialize: {}", err);
|
println!("Logger failed to initialize: {}", err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub fn init(level: LoggingLevel) {
|
||||||
|
try_init(level, true)
|
||||||
|
}
|
||||||
|
|
|
@ -28,7 +28,6 @@ use std::marker::PhantomData;
|
||||||
use std::fmt::{self, Debug};
|
use std::fmt::{self, Debug};
|
||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
|
|
||||||
use config;
|
|
||||||
use http::Status;
|
use http::Status;
|
||||||
use request::Request;
|
use request::Request;
|
||||||
use data::{self, Data, FromData};
|
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.
|
/// Parses a `Form` from incoming form data.
|
||||||
///
|
///
|
||||||
/// If the content type of the request data is not
|
/// 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 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);
|
let mut stream = data.open().take(limit);
|
||||||
if let Err(e) = stream.read_to_string(&mut form_string) {
|
if let Err(e) = stream.read_to_string(&mut form_string) {
|
||||||
error_!("IO Error: {:?}", e);
|
error_!("IO Error: {:?}", e);
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
use std::net::SocketAddr;
|
use std::net::SocketAddr;
|
||||||
|
|
||||||
use outcome::{self, IntoOutcome};
|
|
||||||
use request::Request;
|
use request::Request;
|
||||||
|
use outcome::{self, IntoOutcome};
|
||||||
use outcome::Outcome::*;
|
use outcome::Outcome::*;
|
||||||
|
|
||||||
use http::{Status, ContentType, Accept, Method, Cookies, Session};
|
use http::{Status, ContentType, Accept, Method, Cookies, Session};
|
||||||
|
|
|
@ -9,17 +9,20 @@ use term_painter::ToStyle;
|
||||||
use state::{Container, Storage};
|
use state::{Container, Storage};
|
||||||
|
|
||||||
use error::Error;
|
use error::Error;
|
||||||
use super::{FromParam, FromSegments};
|
use super::{FromParam, FromSegments, FromRequest, Outcome};
|
||||||
|
|
||||||
use router::Route;
|
use router::Route;
|
||||||
|
use config::{Config, Limits};
|
||||||
use http::uri::{URI, Segments};
|
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::{RawStr, ContentType, Accept, MediaType};
|
||||||
use http::hyper;
|
use http::hyper;
|
||||||
|
|
||||||
struct PresetState<'r> {
|
struct PresetState<'r> {
|
||||||
key: &'r Key,
|
// The running Rocket instances configuration.
|
||||||
managed_state: &'r Container,
|
config: &'r Config,
|
||||||
|
// The managed state of the running Rocket instance.
|
||||||
|
state: &'r Container,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct RequestState<'r> {
|
struct RequestState<'r> {
|
||||||
|
@ -286,30 +289,19 @@ impl<'r> Request<'r> {
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn session(&self) -> Session {
|
pub fn session(&self) -> Session {
|
||||||
|
let key = self.preset().config.secret_key();
|
||||||
match self.extra.session.try_borrow_mut() {
|
match self.extra.session.try_borrow_mut() {
|
||||||
Ok(jar) => Session::new(jar, self.preset().key),
|
Ok(jar) => Session::new(jar, key),
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
error_!("Multiple `Session` instances are active at once.");
|
error_!("Multiple `Session` instances are active at once.");
|
||||||
info_!("An instance of `Session` must be dropped before another \
|
info_!("An instance of `Session` must be dropped before another \
|
||||||
can be retrieved.");
|
can be retrieved.");
|
||||||
warn_!("The retrieved `Session` instance will be empty.");
|
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 the Content-Type header of `self`. If the header is not present,
|
||||||
/// returns `None`. The Content-Type header is cached after the first call
|
/// returns `None`. The Content-Type header is cached after the first call
|
||||||
/// to this function. As a result, subsequent calls will always return the
|
/// 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())
|
self.accept().and_then(|accept| accept.first()).map(|wmt| wmt.media_type())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
pub fn format(&self) -> Option<&MediaType> {
|
pub fn format(&self) -> Option<&MediaType> {
|
||||||
static ANY: MediaType = MediaType::Any;
|
static ANY: MediaType = MediaType::Any;
|
||||||
if self.method.supports_payload() {
|
if self.method.supports_payload() {
|
||||||
|
@ -386,7 +377,7 @@ impl<'r> Request<'r> {
|
||||||
///
|
///
|
||||||
/// # #[allow(dead_code)]
|
/// # #[allow(dead_code)]
|
||||||
/// fn name<'a>(req: &'a Request, _: Data) -> Outcome<'a> {
|
/// 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> {
|
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)
|
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
|
/// Get the `n`th path parameter as a string, if it exists. This is used by
|
||||||
/// codegen.
|
/// codegen.
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
|
@ -468,10 +450,9 @@ impl<'r> Request<'r> {
|
||||||
Some(Segments(&path[i..j]))
|
Some(Segments(&path[i..j]))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the managed state container, if it exists. For internal use only!
|
/// Get the limits.
|
||||||
#[inline(always)]
|
pub fn limits(&self) -> &'r Limits {
|
||||||
pub fn get_state<T: Send + Sync + 'static>(&self) -> Option<&'r T> {
|
&self.preset().config.limits
|
||||||
self.preset().managed_state.try_get()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[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!
|
/// Set the precomputed state. For internal use only!
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub(crate) fn set_preset_state(&mut self, key: &'r Key, state: &'r Container) {
|
pub(crate) fn set_preset(&mut self, config: &'r Config, state: &'r Container) {
|
||||||
self.extra.preset = Some(PresetState { key, managed_state: state });
|
self.extra.preset = Some(PresetState { config, state });
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convert from Hyper types into a Rocket Request.
|
/// Convert from Hyper types into a Rocket Request.
|
||||||
|
|
|
@ -22,6 +22,7 @@
|
||||||
//! let response = content::HTML("<h1>Hello, world!</h1>");
|
//! let response = content::HTML("<h1>Hello, world!</h1>");
|
||||||
//! ```
|
//! ```
|
||||||
|
|
||||||
|
use request::Request;
|
||||||
use response::{Response, Responder};
|
use response::{Response, Responder};
|
||||||
use http::{Status, ContentType};
|
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.
|
/// delegates the remainder of the response to the wrapped responder.
|
||||||
impl<'r, R: Responder<'r>> Responder<'r> for Content<R> {
|
impl<'r, R: Responder<'r>> Responder<'r> for Content<R> {
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn respond(self) -> Result<Response<'r>, Status> {
|
fn respond_to(self, req: &Request) -> Result<Response<'r>, Status> {
|
||||||
Response::build()
|
Response::build()
|
||||||
.merge(self.1.respond()?)
|
.merge(self.1.respond_to(req)?)
|
||||||
.header(self.0)
|
.header(self.0)
|
||||||
.ok()
|
.ok()
|
||||||
}
|
}
|
||||||
|
@ -71,8 +72,8 @@ macro_rules! ctrs {
|
||||||
/// Sets the Content-Type of the response then delegates the
|
/// Sets the Content-Type of the response then delegates the
|
||||||
/// remainder of the response to the wrapped responder.
|
/// remainder of the response to the wrapped responder.
|
||||||
impl<'r, R: Responder<'r>> Responder<'r> for $name<R> {
|
impl<'r, R: Responder<'r>> Responder<'r> for $name<R> {
|
||||||
fn respond(self) -> Result<Response<'r>, Status> {
|
fn respond_to(self, req: &Request) -> Result<Response<'r>, Status> {
|
||||||
Content(ContentType::$name, self.0).respond()
|
Content(ContentType::$name, self.0).respond_to(req)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)+
|
)+
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
use request::Request;
|
||||||
use response::{Response, Responder};
|
use response::{Response, Responder};
|
||||||
use http::Status;
|
use http::Status;
|
||||||
|
|
||||||
|
@ -6,8 +7,8 @@ use http::Status;
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Failure(pub Status);
|
pub struct Failure(pub Status);
|
||||||
|
|
||||||
impl<'r> Responder<'r> for Failure {
|
impl Responder<'static> for Failure {
|
||||||
fn respond(self) -> Result<Response<'r>, Status> {
|
fn respond_to(self, _: &Request) -> Result<Response<'static>, Status> {
|
||||||
Err(self.0)
|
Err(self.0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -181,10 +181,10 @@ impl<'r, R: Responder<'r>> Flash<R> {
|
||||||
/// response handling to the wrapped responder. As a result, the `Outcome` of
|
/// response handling to the wrapped responder. As a result, the `Outcome` of
|
||||||
/// the response is the `Outcome` of the wrapped `Responder`.
|
/// the response is the `Outcome` of the wrapped `Responder`.
|
||||||
impl<'r, R: Responder<'r>> Responder<'r> for Flash<R> {
|
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);
|
trace_!("Flash: setting message: {}:{}", self.name, self.message);
|
||||||
let cookie = self.cookie();
|
let cookie = self.cookie();
|
||||||
Response::build_from(self.responder.respond()?)
|
Response::build_from(self.responder.respond_to(req)?)
|
||||||
.header_adjoin(&cookie)
|
.header_adjoin(&cookie)
|
||||||
.ok()
|
.ok()
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ use std::path::{Path, PathBuf};
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::ops::{Deref, DerefMut};
|
use std::ops::{Deref, DerefMut};
|
||||||
|
|
||||||
|
use request::Request;
|
||||||
use response::{Response, Responder};
|
use response::{Response, Responder};
|
||||||
use http::{Status, ContentType};
|
use http::{Status, ContentType};
|
||||||
|
|
||||||
|
@ -78,8 +79,8 @@ impl NamedFile {
|
||||||
/// [ContentType::from_extension](/rocket/http/struct.ContentType.html#method.from_extension)
|
/// [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
|
/// 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.
|
/// Content-Type than that implied by its extension, use a `File` directly.
|
||||||
impl<'r> Responder<'r> for NamedFile {
|
impl Responder<'static> for NamedFile {
|
||||||
fn respond(self) -> Result<Response<'r>, Status> {
|
fn respond_to(self, _: &Request) -> Result<Response<'static>, Status> {
|
||||||
let mut response = Response::new();
|
let mut response = Response::new();
|
||||||
if let Some(ext) = self.path().extension() {
|
if let Some(ext) = self.path().extension() {
|
||||||
// TODO: Use Cow for lowercase.
|
// TODO: Use Cow for lowercase.
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
use request::Request;
|
||||||
use response::{Response, Responder};
|
use response::{Response, Responder};
|
||||||
use http::hyper::header;
|
use http::hyper::header;
|
||||||
use http::Status;
|
use http::Status;
|
||||||
|
@ -104,7 +105,7 @@ impl Redirect {
|
||||||
/// the `Location` header field. The body of the response is empty. This
|
/// the `Location` header field. The body of the response is empty. This
|
||||||
/// responder does not fail.
|
/// responder does not fail.
|
||||||
impl Responder<'static> for Redirect {
|
impl Responder<'static> for Redirect {
|
||||||
fn respond(self) -> Result<Response<'static>, Status> {
|
fn respond_to(self, _: &Request) -> Result<Response<'static>, Status> {
|
||||||
Response::build()
|
Response::build()
|
||||||
.status(self.0)
|
.status(self.0)
|
||||||
.header(header::Location(self.1))
|
.header(header::Location(self.1))
|
||||||
|
|
|
@ -3,7 +3,8 @@ use std::io::Cursor;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
use http::{Status, ContentType};
|
use http::{Status, ContentType};
|
||||||
use response::{Response, Stream};
|
use response::Response;
|
||||||
|
use request::Request;
|
||||||
|
|
||||||
/// Trait implemented by types that generate responses for clients.
|
/// Trait implemented by types that generate responses for clients.
|
||||||
///
|
///
|
||||||
|
@ -49,12 +50,14 @@ use response::{Response, Stream};
|
||||||
///
|
///
|
||||||
/// * **File**
|
/// * **File**
|
||||||
///
|
///
|
||||||
/// Streams the `File` to the client. This is essentially an alias to
|
/// Responds with a sized body containing the data in the `File`. No
|
||||||
/// `Stream::from(file)`.
|
/// `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<T>**
|
/// * **Option<T>**
|
||||||
///
|
///
|
||||||
|
@ -95,6 +98,16 @@ use response::{Response, Stream};
|
||||||
/// or `ResponseBuilder` struct. Ensure that you document the merging or joining
|
/// or `ResponseBuilder` struct. Ensure that you document the merging or joining
|
||||||
/// behavior appropriately.
|
/// 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
|
/// # Example
|
||||||
///
|
///
|
||||||
/// Say that you have a custom type, `Person`:
|
/// Say that you have a custom type, `Person`:
|
||||||
|
@ -133,11 +146,12 @@ use response::{Response, Stream};
|
||||||
/// #
|
/// #
|
||||||
/// use std::io::Cursor;
|
/// use std::io::Cursor;
|
||||||
///
|
///
|
||||||
|
/// use rocket::request::Request;
|
||||||
/// use rocket::response::{self, Response, Responder};
|
/// use rocket::response::{self, Response, Responder};
|
||||||
/// use rocket::http::ContentType;
|
/// use rocket::http::ContentType;
|
||||||
///
|
///
|
||||||
/// impl<'r> Responder<'r> for Person {
|
/// impl Responder<'static> for Person {
|
||||||
/// fn respond(self) -> response::Result<'r> {
|
/// fn respond_to(self, _: &Request) -> response::Result<'static> {
|
||||||
/// Response::build()
|
/// Response::build()
|
||||||
/// .sized_body(Cursor::new(format!("{}:{}", self.name, self.age)))
|
/// .sized_body(Cursor::new(format!("{}:{}", self.name, self.age)))
|
||||||
/// .raw_header("X-Person-Name", self.name)
|
/// .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 `Ok` if a `Response` could be generated successfully. Otherwise,
|
||||||
/// returns an `Err` with a failing `Status`.
|
/// 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,
|
/// 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
|
/// 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
|
/// 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
|
/// to generate a final error response, which is then written out to the
|
||||||
/// client.
|
/// 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
|
/// Returns a response with Content-Type `text/plain` and a fixed-size body
|
||||||
/// containing the string `self`. Always returns `Ok`.
|
/// 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 {
|
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()
|
Response::build()
|
||||||
.header(ContentType::Plain)
|
.header(ContentType::Plain)
|
||||||
.sized_body(Cursor::new(self))
|
.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
|
/// Returns a response with Content-Type `text/plain` and a fixed-size body
|
||||||
/// containing the string `self`. Always returns `Ok`.
|
/// containing the string `self`. Always returns `Ok`.
|
||||||
impl Responder<'static> for String {
|
impl Responder<'static> for String {
|
||||||
fn respond(self) -> Result<Response<'static>, Status> {
|
fn respond_to(self, _: &Request) -> Result<Response<'static>, Status> {
|
||||||
Response::build()
|
Response::build()
|
||||||
.header(ContentType::Plain)
|
.header(ContentType::Plain)
|
||||||
.sized_body(Cursor::new(self))
|
.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 {
|
impl Responder<'static> for File {
|
||||||
fn respond(self) -> Result<Response<'static>, Status> {
|
fn respond_to(self, _: &Request) -> Result<Response<'static>, Status> {
|
||||||
Stream::from(self).respond()
|
Response::build().sized_body(self).ok()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns an empty, default `Response`. Always returns `Ok`.
|
/// Returns an empty, default `Response`. Always returns `Ok`.
|
||||||
impl Responder<'static> for () {
|
impl Responder<'static> for () {
|
||||||
fn respond(self) -> Result<Response<'static>, Status> {
|
fn respond_to(self, _: &Request) -> Result<Response<'static>, Status> {
|
||||||
Ok(Response::new())
|
Ok(Response::new())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -216,11 +219,11 @@ impl Responder<'static> for () {
|
||||||
/// If `self` is `Some`, responds with the wrapped `Responder`. Otherwise prints
|
/// If `self` is `Some`, responds with the wrapped `Responder`. Otherwise prints
|
||||||
/// a warning message and returns an `Err` of `Status::NotFound`.
|
/// a warning message and returns an `Err` of `Status::NotFound`.
|
||||||
impl<'r, R: Responder<'r>> Responder<'r> for Option<R> {
|
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(|| {
|
self.map_or_else(|| {
|
||||||
warn_!("Response was `None`.");
|
warn_!("Response was `None`.");
|
||||||
Err(Status::NotFound)
|
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
|
/// an error message with the `Err` value returns an `Err` of
|
||||||
/// `Status::InternalServerError`.
|
/// `Status::InternalServerError`.
|
||||||
impl<'r, R: Responder<'r>, E: fmt::Debug> Responder<'r> for Result<R, E> {
|
impl<'r, R: Responder<'r>, E: fmt::Debug> Responder<'r> for Result<R, E> {
|
||||||
default fn respond(self) -> Result<Response<'r>, Status> {
|
default fn respond_to(self, req: &Request) -> Result<Response<'r>, Status> {
|
||||||
self.map(|r| r.respond()).unwrap_or_else(|e| {
|
self.map(|r| r.respond_to(req)).unwrap_or_else(|e| {
|
||||||
error_!("Response was `Err`: {:?}.", e);
|
error_!("Response was `Err`: {:?}.", e);
|
||||||
Err(Status::InternalServerError)
|
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
|
/// Responds with the wrapped `Responder` in `self`, whether it is `Ok` or
|
||||||
/// `Err`.
|
/// `Err`.
|
||||||
impl<'r, R: Responder<'r>, E: Responder<'r> + fmt::Debug> Responder<'r> for Result<R, E> {
|
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 {
|
match self {
|
||||||
Ok(responder) => responder.respond(),
|
Ok(responder) => responder.respond_to(req),
|
||||||
Err(responder) => responder.respond(),
|
Err(responder) => responder.respond_to(req),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1129,9 +1129,11 @@ impl<'r> fmt::Debug for Response<'r> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
use request::Request;
|
||||||
|
|
||||||
impl<'r> Responder<'r> for Response<'r> {
|
impl<'r> Responder<'r> for Response<'r> {
|
||||||
/// This is the identity implementation. It simply returns `Ok(self)`.
|
/// 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)
|
Ok(self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
use std::hash::{Hash, Hasher};
|
use std::hash::{Hash, Hasher};
|
||||||
use std::collections::hash_map::DefaultHasher;
|
use std::collections::hash_map::DefaultHasher;
|
||||||
|
|
||||||
|
use request::Request;
|
||||||
use response::{Responder, Response};
|
use response::{Responder, Response};
|
||||||
use http::hyper::header;
|
use http::hyper::header;
|
||||||
use http::Status;
|
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
|
/// information about the created resource. If no responder is provided, the
|
||||||
/// response body will be empty.
|
/// response body will be empty.
|
||||||
impl<'r, R: Responder<'r>> Responder<'r> for Created<R> {
|
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();
|
let mut build = Response::build();
|
||||||
if let Some(responder) = self.1 {
|
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()
|
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
|
/// a `Responder` is provided that implements `Hash`. The `ETag` header is set
|
||||||
/// to a hash value of the responder.
|
/// to a hash value of the responder.
|
||||||
impl<'r, R: Responder<'r> + Hash> Responder<'r> for Created<R> {
|
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 hasher = DefaultHasher::default();
|
||||||
let mut build = Response::build();
|
let mut build = Response::build();
|
||||||
if let Some(responder) = self.1 {
|
if let Some(responder) = self.1 {
|
||||||
responder.hash(&mut hasher);
|
responder.hash(&mut hasher);
|
||||||
let hash = hasher.finish().to_string();
|
let hash = hasher.finish().to_string();
|
||||||
|
|
||||||
build.merge(responder.respond()?);
|
build.merge(responder.respond_to(req)?);
|
||||||
build.header(header::ETag(header::EntityTag::strong(hash)));
|
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
|
/// Sets the status code of the response to 202 Accepted. If the responder is
|
||||||
/// `Some`, it is used to finalize the response.
|
/// `Some`, it is used to finalize the response.
|
||||||
impl<'r, R: Responder<'r>> Responder<'r> for Accepted<R> {
|
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();
|
let mut build = Response::build();
|
||||||
if let Some(responder) = self.0 {
|
if let Some(responder) = self.0 {
|
||||||
build.merge(responder.respond()?);
|
build.merge(responder.respond_to(req)?);
|
||||||
}
|
}
|
||||||
|
|
||||||
build.status(Status::Accepted).ok()
|
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
|
/// Sets the status code of the response to 204 No Content. The body of the
|
||||||
/// response will be empty.
|
/// response will be empty.
|
||||||
impl<'r> Responder<'r> for NoContent {
|
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()
|
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
|
/// Sets the status code of the response to 205 Reset Content. The body of the
|
||||||
/// response will be empty.
|
/// response will be empty.
|
||||||
impl<'r> Responder<'r> for Reset {
|
impl Responder<'static> for Reset {
|
||||||
fn respond(self) -> Result<Response<'r>, Status> {
|
fn respond_to(self, _: &Request) -> Result<Response<'static>, Status> {
|
||||||
Response::build().status(Status::ResetContent).ok()
|
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
|
/// Sets the status code of the response and then delegates the remainder of the
|
||||||
/// response to the wrapped responder.
|
/// response to the wrapped responder.
|
||||||
impl<'r, R: Responder<'r>> Responder<'r> for Custom<R> {
|
impl<'r, R: Responder<'r>> Responder<'r> for Custom<R> {
|
||||||
fn respond(self) -> Result<Response<'r>, Status> {
|
fn respond_to(self, req: &Request) -> Result<Response<'r>, Status> {
|
||||||
Response::build_from(self.1.respond()?)
|
Response::build_from(self.1.respond_to(req)?)
|
||||||
.status(self.0)
|
.status(self.0)
|
||||||
.ok()
|
.ok()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
use std::fmt::{self, Debug};
|
use std::fmt::{self, Debug};
|
||||||
|
|
||||||
|
use request::Request;
|
||||||
use response::{Response, Responder, DEFAULT_CHUNK_SIZE};
|
use response::{Response, Responder, DEFAULT_CHUNK_SIZE};
|
||||||
use http::Status;
|
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
|
/// response is abandoned, and the response ends abruptly. An error is printed
|
||||||
/// to the console with an indication of what went wrong.
|
/// to the console with an indication of what went wrong.
|
||||||
impl<'r, T: Read + 'r> Responder<'r> for Stream<T> {
|
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()
|
Response::build().chunked_body(self.0, self.1).ok()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,7 +29,7 @@ use http::uri::URI;
|
||||||
/// The main `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.
|
/// application.
|
||||||
pub struct Rocket {
|
pub struct Rocket {
|
||||||
config: &'static Config,
|
config: Config,
|
||||||
router: Router,
|
router: Router,
|
||||||
default_catchers: HashMap<u16, Catcher>,
|
default_catchers: HashMap<u16, Catcher>,
|
||||||
catchers: HashMap<u16, Catcher>,
|
catchers: HashMap<u16, Catcher>,
|
||||||
|
@ -96,8 +96,8 @@ macro_rules! serve {
|
||||||
#[cfg(feature = "tls")]
|
#[cfg(feature = "tls")]
|
||||||
macro_rules! serve {
|
macro_rules! serve {
|
||||||
($rocket:expr, $addr:expr, |$server:ident, $proto:ident| $continue:expr) => ({
|
($rocket:expr, $addr:expr, |$server:ident, $proto:ident| $continue:expr) => ({
|
||||||
if let Some(ref tls) = $rocket.config.tls {
|
if let Some(tls) = $rocket.config.tls.clone() {
|
||||||
let tls = TlsServer::new(tls.certs.clone(), tls.key.clone());
|
let tls = TlsServer::new(tls.certs, tls.key);
|
||||||
let ($proto, $server) = ("https://", hyper::Server::https($addr, tls));
|
let ($proto, $server) = ("https://", hyper::Server::https($addr, tls));
|
||||||
$continue
|
$continue
|
||||||
} else {
|
} else {
|
||||||
|
@ -218,7 +218,7 @@ impl Rocket {
|
||||||
info!("{}:", request);
|
info!("{}:", request);
|
||||||
|
|
||||||
// Inform the request about all of the precomputed state.
|
// 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.
|
// Do a bit of preprocessing before routing; run the attached fairings.
|
||||||
self.preprocess_request(request, &data);
|
self.preprocess_request(request, &data);
|
||||||
|
@ -346,10 +346,10 @@ impl Rocket {
|
||||||
/// rocket::ignite()
|
/// rocket::ignite()
|
||||||
/// # };
|
/// # };
|
||||||
/// ```
|
/// ```
|
||||||
|
#[inline]
|
||||||
pub fn ignite() -> Rocket {
|
pub fn ignite() -> Rocket {
|
||||||
// Note: init() will exit the process under config errors.
|
// Note: init() will exit the process under config errors.
|
||||||
let (config, initted) = config::init();
|
Rocket::configured(config::init(), true)
|
||||||
Rocket::configured(config, initted)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a new `Rocket` application using the supplied custom
|
/// Creates a new `Rocket` application using the supplied custom
|
||||||
|
@ -377,14 +377,15 @@ impl Rocket {
|
||||||
/// # Ok(())
|
/// # Ok(())
|
||||||
/// # }
|
/// # }
|
||||||
/// ```
|
/// ```
|
||||||
|
#[inline]
|
||||||
pub fn custom(config: Config, log: bool) -> Rocket {
|
pub fn custom(config: Config, log: bool) -> Rocket {
|
||||||
let (config, initted) = config::custom_init(config);
|
Rocket::configured(config, log)
|
||||||
Rocket::configured(config, log && initted)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn configured(config: &'static Config, log: bool) -> Rocket {
|
#[inline]
|
||||||
|
fn configured(config: Config, log: bool) -> Rocket {
|
||||||
if log {
|
if log {
|
||||||
logger::init(config.log_level);
|
logger::try_init(config.log_level, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
info!("🔧 Configured for {}.", config.environment);
|
info!("🔧 Configured for {}.", config.environment);
|
||||||
|
@ -426,17 +427,17 @@ impl Rocket {
|
||||||
/// path. Mounting a route with path `path` at path `base` makes the route
|
/// path. Mounting a route with path `path` at path `base` makes the route
|
||||||
/// available at `base/path`.
|
/// 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
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// Use the `routes!` macro to mount routes created using the code
|
/// Use the `routes!` macro to mount routes created using the code
|
||||||
/// generation facilities. Requests to the `/hello/world` URI will be
|
/// generation facilities. Requests to the `/hello/world` URI will be
|
||||||
/// dispatched to the `hi` route.
|
/// 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
|
/// ```rust
|
||||||
/// # #![feature(plugin)]
|
/// # #![feature(plugin)]
|
||||||
/// # #![plugin(rocket_codegen)]
|
/// # #![plugin(rocket_codegen)]
|
||||||
|
@ -464,8 +465,8 @@ impl Rocket {
|
||||||
/// use rocket::handler::Outcome;
|
/// use rocket::handler::Outcome;
|
||||||
/// use rocket::http::Method::*;
|
/// use rocket::http::Method::*;
|
||||||
///
|
///
|
||||||
/// fn hi(_: &Request, _: Data) -> Outcome<'static> {
|
/// fn hi(req: &Request, _: Data) -> Outcome<'static> {
|
||||||
/// Outcome::of("Hello!")
|
/// Outcome::from(req, "Hello!")
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// # if false { // We don't actually want to launch the server in an example.
|
/// # if false { // We don't actually want to launch the server in an example.
|
||||||
|
@ -473,6 +474,7 @@ impl Rocket {
|
||||||
/// # .launch();
|
/// # .launch();
|
||||||
/// # }
|
/// # }
|
||||||
/// ```
|
/// ```
|
||||||
|
#[inline]
|
||||||
pub fn mount(mut self, base: &str, routes: Vec<Route>) -> Self {
|
pub fn mount(mut self, base: &str, routes: Vec<Route>) -> Self {
|
||||||
info!("🛰 {} '{}':", Magenta.paint("Mounting"), base);
|
info!("🛰 {} '{}':", Magenta.paint("Mounting"), base);
|
||||||
|
|
||||||
|
@ -522,6 +524,7 @@ impl Rocket {
|
||||||
/// # }
|
/// # }
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
#[inline]
|
||||||
pub fn catch(mut self, catchers: Vec<Catcher>) -> Self {
|
pub fn catch(mut self, catchers: Vec<Catcher>) -> Self {
|
||||||
info!("👾 {}:", Magenta.paint("Catchers"));
|
info!("👾 {}:", Magenta.paint("Catchers"));
|
||||||
for c in catchers {
|
for c in catchers {
|
||||||
|
@ -577,6 +580,7 @@ impl Rocket {
|
||||||
/// # }
|
/// # }
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
#[inline]
|
||||||
pub fn manage<T: Send + Sync + 'static>(self, state: T) -> Self {
|
pub fn manage<T: Send + Sync + 'static>(self, state: T) -> Self {
|
||||||
if !self.state.set::<T>(state) {
|
if !self.state.set::<T>(state) {
|
||||||
error!("State for this type is already being managed!");
|
error!("State for this type is already being managed!");
|
||||||
|
@ -648,7 +652,7 @@ impl Rocket {
|
||||||
/// rocket::ignite().launch();
|
/// rocket::ignite().launch();
|
||||||
/// # }
|
/// # }
|
||||||
/// ```
|
/// ```
|
||||||
pub fn launch(self) -> LaunchError {
|
pub fn launch(mut self) -> LaunchError {
|
||||||
if let Some(error) = self.prelaunch_check() {
|
if let Some(error) = self.prelaunch_check() {
|
||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
|
@ -662,19 +666,20 @@ impl Rocket {
|
||||||
Err(e) => return LaunchError::from(e)
|
Err(e) => return LaunchError::from(e)
|
||||||
};
|
};
|
||||||
|
|
||||||
// Determine the port we actually binded to.
|
// Determine the address and port we actually binded to.
|
||||||
let (addr, port) = match server.local_addr() {
|
match server.local_addr() {
|
||||||
Ok(server_addr) => (&self.config.address, server_addr.port()),
|
Ok(server_addr) => self.config.port = server_addr.port(),
|
||||||
Err(e) => return LaunchError::from(e)
|
Err(e) => return LaunchError::from(e)
|
||||||
};
|
}
|
||||||
|
|
||||||
// Run the launch fairings.
|
// Run the launch fairings.
|
||||||
self.fairings.handle_launch(&self);
|
self.fairings.handle_launch(&self);
|
||||||
|
|
||||||
|
let full_addr = format!("{}:{}", self.config.address, self.config.port);
|
||||||
launch_info!("🚀 {} {}{}",
|
launch_info!("🚀 {} {}{}",
|
||||||
White.paint("Rocket has launched from"),
|
White.paint("Rocket has launched from"),
|
||||||
White.bold().paint(proto),
|
White.bold().paint(proto),
|
||||||
White.bold().paint(&format!("{}:{}", addr, port)));
|
White.bold().paint(&full_addr));
|
||||||
|
|
||||||
let threads = self.config.workers as usize;
|
let threads = self.config.workers as usize;
|
||||||
if let Err(e) = server.handle_threads(self, threads) {
|
if let Err(e) = server.handle_threads(self, threads) {
|
||||||
|
@ -691,9 +696,9 @@ impl Rocket {
|
||||||
self.router.routes()
|
self.router.routes()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Retrieve the configuration.
|
/// Retrieve the active configuration.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn config(&self) -> &Config {
|
pub fn config(&self) -> &Config {
|
||||||
self.config
|
&self.config
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -141,8 +141,8 @@ mod tests {
|
||||||
|
|
||||||
type SimpleRoute = (Method, &'static str);
|
type SimpleRoute = (Method, &'static str);
|
||||||
|
|
||||||
fn dummy_handler(_req: &Request, _: Data) -> Outcome<'static> {
|
fn dummy_handler(req: &Request, _: Data) -> Outcome<'static> {
|
||||||
Outcome::of("hi")
|
Outcome::from(req, "hi")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn m_collide(a: SimpleRoute, b: SimpleRoute) -> bool {
|
fn m_collide(a: SimpleRoute, b: SimpleRoute) -> bool {
|
||||||
|
|
|
@ -77,8 +77,8 @@ mod test {
|
||||||
use data::Data;
|
use data::Data;
|
||||||
use handler::Outcome;
|
use handler::Outcome;
|
||||||
|
|
||||||
fn dummy_handler(_req: &Request, _: Data) -> Outcome<'static> {
|
fn dummy_handler(req: &Request, _: Data) -> Outcome<'static> {
|
||||||
Outcome::of("hi")
|
Outcome::from(req, "hi")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn router_with_routes(routes: &[&'static str]) -> Router {
|
fn router_with_routes(routes: &[&'static str]) -> Router {
|
||||||
|
|
|
@ -15,7 +15,7 @@ fn index(form: Form<Simple>) -> String {
|
||||||
form.into_inner().value
|
form.into_inner().value
|
||||||
}
|
}
|
||||||
|
|
||||||
mod tests {
|
mod limits_tests {
|
||||||
use rocket;
|
use rocket;
|
||||||
use rocket::config::{Environment, Config, Limits};
|
use rocket::config::{Environment, Config, Limits};
|
||||||
use rocket::testing::MockRequest;
|
use rocket::testing::MockRequest;
|
||||||
|
@ -30,44 +30,38 @@ mod tests {
|
||||||
rocket::custom(config, true).mount("/", routes![super::index])
|
rocket::custom(config, true).mount("/", routes![super::index])
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: Config is global (it's the only global thing). Each of these tests
|
#[test]
|
||||||
// will run in different threads in the same process, so the config used by
|
fn large_enough() {
|
||||||
// all of the tests will be indentical: whichever of these gets executed
|
let rocket = rocket_with_forms_limit(128);
|
||||||
// first. As such, only one test will pass; the rest will fail. Make config
|
let mut req = MockRequest::new(Post, "/")
|
||||||
// _not_ global so we can actually do these tests.
|
.body("value=Hello+world")
|
||||||
|
.header(ContentType::Form);
|
||||||
|
|
||||||
// #[test]
|
let mut response = req.dispatch_with(&rocket);
|
||||||
// fn large_enough() {
|
assert_eq!(response.body_string(), Some("Hello world".into()));
|
||||||
// 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);
|
#[test]
|
||||||
// assert_eq!(response.body_string(), Some("Hello world".into()));
|
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]
|
let mut response = req.dispatch_with(&rocket);
|
||||||
// fn just_large_enough() {
|
assert_eq!(response.body_string(), Some("Hello world".into()));
|
||||||
// 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);
|
#[test]
|
||||||
// assert_eq!(response.body_string(), Some("Hello world".into()));
|
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]
|
let response = req.dispatch_with(&rocket);
|
||||||
// fn much_too_small() {
|
assert_eq!(response.status(), Status::BadRequest);
|
||||||
// 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);
|
|
||||||
// }
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn contracted() {
|
fn contracted() {
|
||||||
|
|
|
@ -128,7 +128,7 @@ fn rocket_route_fn_hello<'_b>(_req: &'_b ::rocket::Request,
|
||||||
_data: ::rocket::Data)
|
_data: ::rocket::Data)
|
||||||
-> ::rocket::handler::Outcome<'_b> {
|
-> ::rocket::handler::Outcome<'_b> {
|
||||||
let responder = hello();
|
let responder = hello();
|
||||||
::rocket::handler::Outcome::of(responder)
|
::rocket::handler::Outcome::from(_req, responder)
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue