use std::convert::AsRef;
use time::Duration;
use outcome::IntoOutcome;
use response::{Response, Responder};
use request::{self, Request, FromRequest};
use http::{Status, Cookie};
use std::sync::atomic::{AtomicBool, Ordering};
const FLASH_COOKIE_NAME: &str = "_flash";
const FLASH_COOKIE_DELIM: char = ':';
#[derive(Debug)]
pub struct Flash<R> {
name: String,
message: String,
consumed: AtomicBool,
inner: R,
}
pub type FlashMessage<'a, 'r> = ::response::Flash<&'a Request<'r>>;
impl<'r, R: Responder<'r>> Flash<R> {
pub fn new<N: AsRef<str>, M: AsRef<str>>(res: R, name: N, msg: M) -> Flash<R> {
Flash {
name: name.as_ref().to_string(),
message: msg.as_ref().to_string(),
consumed: AtomicBool::default(),
inner: res,
}
}
pub fn success<S: AsRef<str>>(responder: R, msg: S) -> Flash<R> {
Flash::new(responder, "success", msg)
}
pub fn warning<S: AsRef<str>>(responder: R, msg: S) -> Flash<R> {
Flash::new(responder, "warning", msg)
}
pub fn error<S: AsRef<str>>(responder: R, msg: S) -> Flash<R> {
Flash::new(responder, "error", msg)
}
fn cookie(&self) -> Cookie<'static> {
let content = format!("{}{}{}{}",
self.name.len(), FLASH_COOKIE_DELIM, self.name, self.message);
Cookie::build(FLASH_COOKIE_NAME, content)
.max_age(Duration::minutes(5))
.path("/")
.finish()
}
}
impl<'r, R: Responder<'r>> Responder<'r> for Flash<R> {
fn respond_to(self, req: &Request) -> Result<Response<'r>, Status> {
trace_!("Flash: setting message: {}:{}", self.name, self.message);
req.cookies().add(self.cookie());
self.inner.respond_to(req)
}
}
impl<'a, 'r> Flash<&'a Request<'r>> {
fn named(name: &str, msg: &str, req: &'a Request<'r>) -> Flash<&'a Request<'r>> {
Flash {
name: name.to_string(),
message: msg.to_string(),
consumed: AtomicBool::new(false),
inner: req,
}
}
fn clear_cookie_if_needed(&self) {
if !self.consumed.swap(true, Ordering::Relaxed) {
let cookie = Cookie::build(FLASH_COOKIE_NAME, "").path("/").finish();
self.inner.cookies().remove(cookie);
}
}
pub fn name(&self) -> &str {
self.clear_cookie_if_needed();
&self.name
}
pub fn msg(&self) -> &str {
self.clear_cookie_if_needed();
&self.message
}
}
impl<'a, 'r> FromRequest<'a, 'r> for Flash<&'a Request<'r>> {
type Error = ();
fn from_request(req: &'a Request<'r>) -> request::Outcome<Self, Self::Error> {
trace_!("Flash: attempting to retrieve message.");
req.cookies().get(FLASH_COOKIE_NAME).ok_or(()).and_then(|cookie| {
trace_!("Flash: retrieving message: {:?}", cookie);
let content = cookie.value();
let (len_str, kv) = match content.find(FLASH_COOKIE_DELIM) {
Some(i) => (&content[..i], &content[(i + 1)..]),
None => return Err(()),
};
match len_str.parse::<usize>() {
Ok(i) if i <= kv.len() => Ok(Flash::named(&kv[..i], &kv[i..], req)),
_ => Err(())
}
}).into_outcome(Status::BadRequest)
}
}