mirror of https://github.com/rwf2/Rocket.git
Add templating support in contrib crate.
The contrib crate now contains support for both Handlebars and Tera. No documentation yet. resolves #5
This commit is contained in:
parent
53e5377482
commit
f74e286e31
|
@ -22,6 +22,7 @@ members = [
|
||||||
"examples/from_request",
|
"examples/from_request",
|
||||||
"examples/stream",
|
"examples/stream",
|
||||||
"examples/json",
|
"examples/json",
|
||||||
|
"examples/handlebars_templates",
|
||||||
]
|
]
|
||||||
|
|
||||||
[replace]
|
[replace]
|
||||||
|
|
|
@ -6,11 +6,28 @@ authors = ["Sergio Benitez <sb@sergio.bz>"]
|
||||||
[features]
|
[features]
|
||||||
default = ["json"]
|
default = ["json"]
|
||||||
json = ["serde", "serde_json"]
|
json = ["serde", "serde_json"]
|
||||||
|
tera_templates = ["tera", "templates"]
|
||||||
|
handlebars_templates = ["handlebars", "templates"]
|
||||||
|
|
||||||
|
# Internal use only.
|
||||||
|
templates = ["serde", "serde_json", "lazy_static_macro", "glob"]
|
||||||
|
lazy_static_macro = ["lazy_static"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
rocket = { path = "../lib/" }
|
rocket = { path = "../lib/" }
|
||||||
log = "*"
|
log = "*"
|
||||||
|
|
||||||
# JSON module dependencies
|
# JSON and templating dependencies.
|
||||||
serde = { version = "*", optional = true }
|
serde = { version = "*", optional = true }
|
||||||
serde_json = { version = "*", optional = true }
|
serde_json = { version = "*", optional = true }
|
||||||
|
|
||||||
|
# Templating dependencies only.
|
||||||
|
handlebars = { version = "*", optional = true, features = ["serde_type"] }
|
||||||
|
glob = { version = "*", optional = true }
|
||||||
|
lazy_static = { version = "*", optional = true }
|
||||||
|
|
||||||
|
# Tera dependency
|
||||||
|
[dependencies.tera]
|
||||||
|
git = "https://github.com/SergioBenitez/tera"
|
||||||
|
branch = "array-get-filter"
|
||||||
|
optional = true
|
||||||
|
|
|
@ -44,6 +44,7 @@ use self::serde_json::Error as JSONError;
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct JSON<T>(pub T);
|
pub struct JSON<T>(pub T);
|
||||||
|
|
||||||
impl<T> JSON<T> {
|
impl<T> JSON<T> {
|
||||||
|
|
|
@ -32,9 +32,19 @@
|
||||||
#[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_attr(feature = "json", macro_use)]
|
#[cfg_attr(feature = "json", macro_use)]
|
||||||
#[cfg(feature = "json")]
|
#[cfg(feature = "json")]
|
||||||
mod json;
|
mod json;
|
||||||
|
|
||||||
|
#[cfg(feature = "templates")]
|
||||||
|
mod templates;
|
||||||
|
|
||||||
#[cfg(feature = "json")]
|
#[cfg(feature = "json")]
|
||||||
pub use json::JSON;
|
pub use json::JSON;
|
||||||
|
|
||||||
|
#[cfg(feature = "templates")]
|
||||||
|
pub use templates::Template;
|
||||||
|
|
|
@ -0,0 +1,35 @@
|
||||||
|
extern crate handlebars;
|
||||||
|
|
||||||
|
use std::sync::RwLock;
|
||||||
|
|
||||||
|
use super::serde::Serialize;
|
||||||
|
use super::TemplateInfo;
|
||||||
|
|
||||||
|
use self::handlebars::Handlebars;
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
static ref HANDLEBARS: RwLock<Handlebars> = RwLock::new(Handlebars::new());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const EXT: &'static str = "hbs";
|
||||||
|
|
||||||
|
pub fn render<T>(name: &str, info: &TemplateInfo, context: &T) -> Option<String>
|
||||||
|
where T: Serialize
|
||||||
|
{
|
||||||
|
// FIXME: Expose a callback to register each template at launch => no lock.
|
||||||
|
if HANDLEBARS.read().unwrap().get_template(name).is_none() {
|
||||||
|
let p = &info.full_path;
|
||||||
|
if let Err(e) = HANDLEBARS.write().unwrap().register_template_file(name, p) {
|
||||||
|
error_!("Handlebars template '{}' failed registry: {:?}", name, e);
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
match HANDLEBARS.read().unwrap().render(name, context) {
|
||||||
|
Ok(string) => Some(string),
|
||||||
|
Err(e) => {
|
||||||
|
error_!("Error rendering Handlebars template '{}': {}", name, e);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,44 @@
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! engine_set {
|
||||||
|
($($feature:expr => $engine:ident),+) => ({
|
||||||
|
use std::collections::HashSet;
|
||||||
|
let mut set = HashSet::new();
|
||||||
|
$(
|
||||||
|
#[cfg(feature = $feature)]
|
||||||
|
fn $engine(set: &mut HashSet<String>) {
|
||||||
|
set.insert($engine::EXT.to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = $feature))]
|
||||||
|
fn $engine(_: &mut HashSet<String>) { }
|
||||||
|
|
||||||
|
$engine(&mut set);
|
||||||
|
)+
|
||||||
|
set
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
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
|
||||||
|
}
|
||||||
|
)+});
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,102 @@
|
||||||
|
extern crate serde;
|
||||||
|
extern crate serde_json;
|
||||||
|
extern crate glob;
|
||||||
|
|
||||||
|
#[cfg(feature = "tera_templates")]
|
||||||
|
pub mod tera_templates;
|
||||||
|
|
||||||
|
#[cfg(feature = "handlebars_templates")]
|
||||||
|
pub mod handlebars_templates;
|
||||||
|
|
||||||
|
#[macro_use] mod macros;
|
||||||
|
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use self::serde::Serialize;
|
||||||
|
use rocket::response::{data, Outcome, FreshHyperResponse, Responder};
|
||||||
|
use rocket::Rocket;
|
||||||
|
use self::glob::glob;
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
static ref TEMPLATES: HashMap<String, TemplateInfo> = discover_templates();
|
||||||
|
static ref TEMPLATE_DIR: String =
|
||||||
|
Rocket::config("template_dir").unwrap_or("templates").to_string();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Removes the file path's extension or does nothing if there is none.
|
||||||
|
fn remove_extension<P: AsRef<Path>>(path: P) -> PathBuf {
|
||||||
|
PathBuf::from(path.as_ref().file_stem().unwrap())
|
||||||
|
}
|
||||||
|
|
||||||
|
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::new();
|
||||||
|
for ext in engines {
|
||||||
|
let mut path: PathBuf = [&*TEMPLATE_DIR, "**", "*"].iter().collect();
|
||||||
|
path.set_extension(ext);
|
||||||
|
for p in glob(path.to_str().unwrap()).unwrap().filter_map(Result::ok) {
|
||||||
|
let canonical_path = remove_extension(&p);
|
||||||
|
let name = remove_extension(&canonical_path);
|
||||||
|
let data_type = canonical_path.extension();
|
||||||
|
templates.insert(name.to_string_lossy().into_owned(), TemplateInfo {
|
||||||
|
full_path: p.to_path_buf(),
|
||||||
|
path: p.strip_prefix(&*TEMPLATE_DIR).unwrap().to_path_buf(),
|
||||||
|
canonical_path: canonical_path.clone(),
|
||||||
|
extension: p.extension().unwrap().to_string_lossy().into_owned(),
|
||||||
|
data_type: data_type.map(|d| d.to_string_lossy().into_owned())
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
templates
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Template(Option<String>, Option<String>);
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct TemplateInfo {
|
||||||
|
full_path: PathBuf,
|
||||||
|
path: PathBuf,
|
||||||
|
canonical_path: PathBuf,
|
||||||
|
extension: String,
|
||||||
|
data_type: Option<String>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Template {
|
||||||
|
pub fn render<S, T>(name: S, context: T) -> Template
|
||||||
|
where S: AsRef<str>, T: Serialize
|
||||||
|
{
|
||||||
|
let name = name.as_ref();
|
||||||
|
let template = TEMPLATES.get(name);
|
||||||
|
if template.is_none() {
|
||||||
|
error_!("Template '{}' does not exist.", name);
|
||||||
|
return Template(None, None);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keep this set in-sync with the `engine_set` invocation.
|
||||||
|
render_set!(name, template.unwrap(), context,
|
||||||
|
"tera_templates" => tera_templates,
|
||||||
|
"handlebars_templates" => handlebars_templates
|
||||||
|
);
|
||||||
|
|
||||||
|
unreachable!("A template extension was discovered but not rendered.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Responder for Template {
|
||||||
|
fn respond<'a>(&mut self, res: FreshHyperResponse<'a>) -> Outcome<'a> {
|
||||||
|
match self.0 {
|
||||||
|
// FIXME: Detect the data type using the extension in self.1.
|
||||||
|
// Refactor response::named_file to use the extension map there.
|
||||||
|
Some(ref render) => data::HTML(render.as_str()).respond(res),
|
||||||
|
None => Outcome::Bad(res),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
extern crate tera;
|
||||||
|
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use self::tera::Renderer;
|
||||||
|
|
||||||
|
use super::serde::Serialize;
|
||||||
|
use super::serde_json;
|
||||||
|
use super::{TemplateInfo, TEMPLATE_DIR};
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
static ref TERA: tera::Tera = {
|
||||||
|
let path: PathBuf = [&*TEMPLATE_DIR, "**", "*.tera"].iter().collect();
|
||||||
|
tera::Tera::new(path.to_str().unwrap())
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const EXT: &'static str = "tera";
|
||||||
|
|
||||||
|
pub fn render<T>(name: &str, info: &TemplateInfo, context: &T) -> Option<String>
|
||||||
|
where T: Serialize
|
||||||
|
{
|
||||||
|
let template = match TERA.get_template(&info.path.to_string_lossy()) {
|
||||||
|
Ok(template) => template,
|
||||||
|
Err(_) => {
|
||||||
|
error_!("Tera template '{}' does not exist.", name);
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let value = serde_json::to_value(&context);
|
||||||
|
let mut renderer = Renderer::new_with_json(template, &TERA, value);
|
||||||
|
match renderer.render() {
|
||||||
|
Ok(string) => Some(string),
|
||||||
|
Err(e) => {
|
||||||
|
error_!("Error rendering Tera template '{}': {}", name, e);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
[package]
|
||||||
|
name = "handlebars_templates"
|
||||||
|
version = "0.0.1"
|
||||||
|
authors = ["Sergio Benitez <sb@sergio.bz>"]
|
||||||
|
workspace = "../../"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
rocket = { path = "../../lib" }
|
||||||
|
rocket_codegen = { path = "../../codegen" }
|
||||||
|
serde = "*"
|
||||||
|
serde_macros = "*"
|
||||||
|
serde_json = "*"
|
||||||
|
|
||||||
|
[dependencies.rocket_contrib]
|
||||||
|
path = "../../contrib"
|
||||||
|
default-features = false
|
||||||
|
features = ["handlebars_templates"]
|
|
@ -0,0 +1,44 @@
|
||||||
|
#![feature(plugin, custom_derive)]
|
||||||
|
#![plugin(rocket_codegen, serde_macros)]
|
||||||
|
|
||||||
|
extern crate rocket_contrib;
|
||||||
|
extern crate rocket;
|
||||||
|
extern crate serde_json;
|
||||||
|
|
||||||
|
use rocket::{Rocket, Request, Error};
|
||||||
|
use rocket::response::Redirect;
|
||||||
|
use rocket_contrib::Template;
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
struct TemplateContext {
|
||||||
|
name: String,
|
||||||
|
items: Vec<String>
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("/")]
|
||||||
|
fn index() -> Redirect {
|
||||||
|
Redirect::to("/hello/Unknown")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("/hello/<name>")]
|
||||||
|
fn get(name: String) -> Template {
|
||||||
|
let context = TemplateContext {
|
||||||
|
name: name,
|
||||||
|
items: vec!["One", "Two", "Three"].iter().map(|s| s.to_string()).collect()
|
||||||
|
};
|
||||||
|
|
||||||
|
Template::render("index", context)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[error(404)]
|
||||||
|
fn not_found<'r>(_: Error, req: &'r Request<'r>) -> Template {
|
||||||
|
let mut map = std::collections::HashMap::new();
|
||||||
|
map.insert("path", req.uri().as_str());
|
||||||
|
Template::render("404", map)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let mut rocket = Rocket::new("localhost", 8000);
|
||||||
|
rocket.catch(errors![not_found]);
|
||||||
|
rocket.mount_and_launch("/", routes![index, get]);
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<title>404</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>404: Hey! There's nothing here.</h1>
|
||||||
|
The page at {{ path }} does not exist!
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,16 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<title>Handlebars Demo</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Hi {{name}}</h1>
|
||||||
|
<h3>Here are your items:</h3>
|
||||||
|
<ul>
|
||||||
|
{{#each items}}
|
||||||
|
<Li>{{this}}</li>
|
||||||
|
{{/each}}
|
||||||
|
</ul>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -8,9 +8,17 @@ workspace = "../../"
|
||||||
rocket = { path = "../../lib" }
|
rocket = { path = "../../lib" }
|
||||||
rocket_codegen = { path = "../../codegen" }
|
rocket_codegen = { path = "../../codegen" }
|
||||||
lazy_static = "*"
|
lazy_static = "*"
|
||||||
tera = { git = "https://github.com/Keats/tera" }
|
|
||||||
serde = "0.8"
|
serde = "0.8"
|
||||||
serde_json = "0.8"
|
serde_json = "0.8"
|
||||||
serde_macros = "0.8"
|
serde_macros = "0.8"
|
||||||
diesel = { git = "https://github.com/SergioBenitez/diesel", features = ["sqlite"] }
|
diesel = { git = "https://github.com/SergioBenitez/diesel", features = ["sqlite"] }
|
||||||
diesel_codegen = { git = "https://github.com/SergioBenitez/diesel", default_features = false, features = ["sqlite"] }
|
diesel_codegen = { git = "https://github.com/SergioBenitez/diesel", default_features = false, features = ["sqlite"] }
|
||||||
|
|
||||||
|
[dependencies.tera]
|
||||||
|
git = "https://github.com/SergioBenitez/tera"
|
||||||
|
branch = "array-get-filter"
|
||||||
|
|
||||||
|
[dependencies.rocket_contrib]
|
||||||
|
path = "../../contrib"
|
||||||
|
default_features = false
|
||||||
|
features = [ "tera_templates" ]
|
||||||
|
|
|
@ -2,9 +2,9 @@
|
||||||
#![plugin(rocket_codegen, serde_macros, diesel_codegen)]
|
#![plugin(rocket_codegen, serde_macros, diesel_codegen)]
|
||||||
|
|
||||||
extern crate rocket;
|
extern crate rocket;
|
||||||
extern crate tera;
|
|
||||||
#[macro_use] extern crate diesel;
|
#[macro_use] extern crate diesel;
|
||||||
#[macro_use] extern crate lazy_static;
|
#[macro_use] extern crate lazy_static;
|
||||||
|
#[macro_use] extern crate rocket_contrib;
|
||||||
extern crate serde_json;
|
extern crate serde_json;
|
||||||
|
|
||||||
mod static_files;
|
mod static_files;
|
||||||
|
@ -12,63 +12,64 @@ mod task;
|
||||||
|
|
||||||
use rocket::Rocket;
|
use rocket::Rocket;
|
||||||
use rocket::response::{Flash, Redirect};
|
use rocket::response::{Flash, Redirect};
|
||||||
|
use rocket_contrib::Template;
|
||||||
use task::Task;
|
use task::Task;
|
||||||
|
|
||||||
lazy_static!(static ref TERA: tera::Tera = tera::Tera::new("static/*.html"););
|
#[derive(Debug, Serialize)]
|
||||||
|
struct Context<'a, 'b>{msg: Option<(&'a str, &'b str)>, tasks: Vec<Task>}
|
||||||
|
|
||||||
fn ctxt(msg: Option<(&str, &str)>) -> tera::Context {
|
impl<'a, 'b> Context<'a, 'b> {
|
||||||
let unwrapped_msg = msg.unwrap_or(("", ""));
|
pub fn err(msg: &'a str) -> Context<'static, 'a> {
|
||||||
let mut context = tera::Context::new();
|
Context{msg: Some(("error", msg)), tasks: Task::all()}
|
||||||
context.add("has_msg", &msg.is_some());
|
}
|
||||||
context.add("msg_type", &unwrapped_msg.0.to_string());
|
|
||||||
context.add("msg", &unwrapped_msg.1.to_string());
|
pub fn raw(msg: Option<(&'a str, &'b str)>) -> Context<'a, 'b> {
|
||||||
context.add("tasks", &Task::all());
|
Context{msg: msg, tasks: Task::all()}
|
||||||
context
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[post("/", form = "<todo>")]
|
#[post("/", form = "<todo>")]
|
||||||
fn new(todo: Task) -> Result<Flash<Redirect>, tera::TeraResult<String>> {
|
fn new(todo: Task) -> Result<Flash<Redirect>, Template> {
|
||||||
if todo.description.is_empty() {
|
if todo.description.is_empty() {
|
||||||
let context = ctxt(Some(("error", "Description cannot be empty.")));
|
Err(Template::render("index", Context::err("Description cannot be empty.")))
|
||||||
Err(TERA.render("index.html", context))
|
|
||||||
} else if todo.insert() {
|
} else if todo.insert() {
|
||||||
Ok(Flash::success(Redirect::to("/"), "Todo successfully added."))
|
Ok(Flash::success(Redirect::to("/"), "Todo successfully added."))
|
||||||
} else {
|
} else {
|
||||||
let context = ctxt(Some(("error", "Whoops! The server failed.")));
|
Err(Template::render("index", Context::err("Whoops! The server failed.")))
|
||||||
Err(TERA.render("index.html", context))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Should likely do something to simulate PUT.
|
// Should likely do something to simulate PUT.
|
||||||
#[get("/<id>/toggle")]
|
#[get("/<id>/toggle")]
|
||||||
fn toggle(id: i32) -> Result<Redirect, tera::TeraResult<String>> {
|
fn toggle(id: i32) -> Result<Redirect, Template> {
|
||||||
if Task::toggle_with_id(id) {
|
if Task::toggle_with_id(id) {
|
||||||
Ok(Redirect::to("/"))
|
Ok(Redirect::to("/"))
|
||||||
} else {
|
} else {
|
||||||
let context = ctxt(Some(("error", "Could not toggle that task.")));
|
Err(Template::render("index", Context::err("Couldn't toggle task.")))
|
||||||
Err(TERA.render("index.html", context))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Should likely do something to simulate DELETE.
|
// Should likely do something to simulate DELETE.
|
||||||
#[get("/<id>/delete")]
|
#[get("/<id>/delete")]
|
||||||
fn delete(id: i32) -> Result<Flash<Redirect>, tera::TeraResult<String>> {
|
fn delete(id: i32) -> Result<Flash<Redirect>, Template> {
|
||||||
if Task::delete_with_id(id) {
|
if Task::delete_with_id(id) {
|
||||||
Ok(Flash::success(Redirect::to("/"), "Todo was deleted."))
|
Ok(Flash::success(Redirect::to("/"), "Todo was deleted."))
|
||||||
} else {
|
} else {
|
||||||
let context = ctxt(Some(("error", "Could not delete that task.")));
|
Err(Template::render("index", Context::err("Couldn't delete task.")))
|
||||||
Err(TERA.render("index.html", context))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/")]
|
#[get("/")]
|
||||||
fn index(msg: Option<Flash<()>>) -> tera::TeraResult<String> {
|
fn index(msg: Option<Flash<()>>) -> Template {
|
||||||
TERA.render("index.html", ctxt(msg.as_ref().map(|m| (m.name(), m.msg()))))
|
Template::render("index", match msg {
|
||||||
|
Some(ref msg) => Context::raw(Some((msg.name(), msg.msg()))),
|
||||||
|
None => Context::raw(None),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let mut rocket = Rocket::new("127.0.0.1", 8000);
|
let mut rocket = Rocket::new("127.0.0.1", 8000);
|
||||||
rocket.mount("/", routes![index, static_files::all])
|
rocket.mount("/", routes![index, static_files::all])
|
||||||
.mount("/todo/", routes![new, delete, toggle]);
|
.mount("/todo/", routes![new, toggle, delete]);
|
||||||
rocket.launch();
|
rocket.launch();
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,10 +23,10 @@
|
||||||
<div class="ten columns">
|
<div class="ten columns">
|
||||||
<input type="text" placeholder="enter a task description..."
|
<input type="text" placeholder="enter a task description..."
|
||||||
name="description" id="description" value="" autofocus
|
name="description" id="description" value="" autofocus
|
||||||
class="u-full-width {% if has_msg %}field-{{msg_type}}{% endif %}" />
|
class="u-full-width {% if msg %}field-{{msg|first}}{% endif %}" />
|
||||||
{% if has_msg %}
|
{% if msg %}
|
||||||
<small class="field-{{msg_type}}-msg">
|
<small class="field-{{msg|first}}-msg">
|
||||||
{{ msg }}
|
{{ msg | get(i=1) }}
|
||||||
</small>
|
</small>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
|
@ -3,6 +3,8 @@ use std::convert::AsRef;
|
||||||
use hyper::header::{SetCookie, CookiePair};
|
use hyper::header::{SetCookie, CookiePair};
|
||||||
use request::{Request, FromRequest};
|
use request::{Request, FromRequest};
|
||||||
|
|
||||||
|
const FLASH_COOKIE_NAME: &'static str = "_flash";
|
||||||
|
|
||||||
pub struct Flash<R> {
|
pub struct Flash<R> {
|
||||||
name: String,
|
name: String,
|
||||||
message: String,
|
message: String,
|
||||||
|
@ -30,9 +32,9 @@ impl<R: Responder> Flash<R> {
|
||||||
Flash::new(responder, "error", msg)
|
Flash::new(responder, "error", msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn cookie_pair(&self) -> CookiePair {
|
fn cookie_pair(&self) -> CookiePair {
|
||||||
let content = format!("{}{}{}", self.name.len(), self.name, self.message);
|
let content = format!("{}{}{}", self.name.len(), self.name, self.message);
|
||||||
let mut pair = CookiePair::new("_flash".to_string(), content);
|
let mut pair = CookiePair::new(FLASH_COOKIE_NAME.to_string(), content);
|
||||||
pair.path = Some("/".to_string());
|
pair.path = Some("/".to_string());
|
||||||
pair.max_age = Some(300);
|
pair.max_age = Some(300);
|
||||||
pair
|
pair
|
||||||
|
@ -78,10 +80,10 @@ impl<'r, 'c> FromRequest<'r, 'c> for Flash<()> {
|
||||||
|
|
||||||
fn from_request(request: &'r Request<'c>) -> Result<Self, Self::Error> {
|
fn from_request(request: &'r Request<'c>) -> Result<Self, Self::Error> {
|
||||||
trace_!("Flash: attemping to retrieve message.");
|
trace_!("Flash: attemping to retrieve message.");
|
||||||
request.cookies().find("_flash").ok_or(()).and_then(|cookie| {
|
request.cookies().find(FLASH_COOKIE_NAME).ok_or(()).and_then(|cookie| {
|
||||||
// Clear the flash message.
|
// Clear the flash message.
|
||||||
trace_!("Flash: retrieving message: {:?}", cookie);
|
trace_!("Flash: retrieving message: {:?}", cookie);
|
||||||
request.cookies().remove("flash");
|
request.cookies().remove(FLASH_COOKIE_NAME);
|
||||||
|
|
||||||
// Parse the flash.
|
// Parse the flash.
|
||||||
let content = cookie.pair().1;
|
let content = cookie.pair().1;
|
||||||
|
|
|
@ -39,7 +39,8 @@ impl Rocket {
|
||||||
Ok(req) => req,
|
Ok(req) => req,
|
||||||
Err(ref reason) => {
|
Err(ref reason) => {
|
||||||
let mock_request = Request::mock(Method::Get, uri.as_str());
|
let mock_request = Request::mock(Method::Get, uri.as_str());
|
||||||
return self.handle_internal_error(reason, &mock_request, res);
|
debug_!("Bad request: {}", reason);
|
||||||
|
return self.handle_internal_error(&mock_request, res);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -65,10 +66,7 @@ impl Rocket {
|
||||||
res = match outcome {
|
res = match outcome {
|
||||||
Outcome::Complete | Outcome::FailStop => return,
|
Outcome::Complete | Outcome::FailStop => return,
|
||||||
Outcome::FailForward(r) => r,
|
Outcome::FailForward(r) => r,
|
||||||
Outcome::Bad(r) => {
|
Outcome::Bad(r) => return self.handle_internal_error(&request, r)
|
||||||
let reason = "Reason: bad response.";
|
|
||||||
return self.handle_internal_error(reason, &request, r)
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -77,10 +75,9 @@ impl Rocket {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Call on internal server error.
|
// Call on internal server error.
|
||||||
fn handle_internal_error<'r>(&self, reason: &str, request: &'r Request<'r>,
|
fn handle_internal_error<'r>(&self, request: &'r Request<'r>,
|
||||||
response: FreshHyperResponse) {
|
response: FreshHyperResponse) {
|
||||||
error!("Internal server error.");
|
error_!("Internal server error.");
|
||||||
debug!("{}", reason);
|
|
||||||
let catcher = self.catchers.get(&500).unwrap();
|
let catcher = self.catchers.get(&500).unwrap();
|
||||||
catcher.handle(Error::Internal, request).respond(response);
|
catcher.handle(Error::Internal, request).respond(response);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue