use time::Duration;
use serde::ser::{Serialize, Serializer, SerializeStruct};
use crate::outcome::IntoOutcome;
use crate::response::{self, Responder};
use crate::request::{self, Request, FromRequest};
use crate::http::{Status, Cookie, CookieJar};
use std::sync::atomic::{AtomicBool, Ordering};
const FLASH_COOKIE_NAME: &str = "_flash";
const FLASH_COOKIE_DELIM: char = ':';
#[derive(Debug)]
pub struct Flash<R> {
kind: String,
message: String,
consumed: AtomicBool,
inner: R,
}
pub type FlashMessage<'a> = crate::response::Flash<&'a CookieJar<'a>>;
impl<R> Flash<R> {
pub fn new<K: Into<String>, M: Into<String>>(res: R, kind: K, message: M) -> Flash<R> {
Flash {
kind: kind.into(),
message: message.into(),
consumed: AtomicBool::default(),
inner: res,
}
}
pub fn success<S: Into<String>>(responder: R, message: S) -> Flash<R> {
Flash::new(responder, "success", message.into())
}
pub fn warning<S: Into<String>>(responder: R, message: S) -> Flash<R> {
Flash::new(responder, "warning", message.into())
}
pub fn error<S: Into<String>>(responder: R, message: S) -> Flash<R> {
Flash::new(responder, "error", message.into())
}
fn cookie(&self) -> Cookie<'static> {
let content = format!("{}{}{}{}",
self.kind.len(), FLASH_COOKIE_DELIM, self.kind, self.message);
Cookie::build((FLASH_COOKIE_NAME, content))
.max_age(Duration::minutes(5))
.build()
}
}
impl<'r, 'o: 'r, R: Responder<'r, 'o>> Responder<'r, 'o> for Flash<R> {
fn respond_to(self, req: &'r Request<'_>) -> response::Result<'o> {
req.cookies().add(self.cookie());
self.inner.respond_to(req)
}
}
impl<'r> FlashMessage<'r> {
fn named<S: Into<String>>(kind: S, message: S, req: &'r Request<'_>) -> Self {
Flash {
kind: kind.into(),
message: message.into(),
consumed: AtomicBool::new(false),
inner: req.cookies(),
}
}
fn clear_cookie_if_needed(&self) {
if !self.consumed.swap(true, Ordering::Relaxed) {
self.inner.remove(FLASH_COOKIE_NAME);
}
}
pub fn into_inner(self) -> (String, String) {
self.clear_cookie_if_needed();
(self.kind, self.message)
}
pub fn kind(&self) -> &str {
self.clear_cookie_if_needed();
&self.kind
}
pub fn message(&self) -> &str {
self.clear_cookie_if_needed();
&self.message
}
}
#[crate::async_trait]
impl<'r> FromRequest<'r> for FlashMessage<'r> {
type Error = ();
async fn from_request(req: &'r Request<'_>) -> 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(())
}
}).or_error(Status::BadRequest)
}
}
impl Serialize for FlashMessage<'_> {
fn serialize<S: Serializer>(&self, ser: S) -> Result<S::Ok, S::Error> {
let mut flash = ser.serialize_struct("Flash", 2)?;
flash.serialize_field("kind", self.kind())?;
flash.serialize_field("message", self.message())?;
flash.end()
}
}