#![deny(missing_docs)]
use actix_service::{Service, Transform};
use actix_web::cookie::{Cookie, CookieJar};
use actix_web::dev::{ServiceRequest, ServiceResponse};
use actix_web::error::ErrorBadRequest;
use actix_web::{Error, FromRequest, HttpMessage, HttpRequest, HttpResponse, Responder};
use futures::future::{ok as fut_ok, Either as EitherFuture, Future, FutureResult, IntoFuture};
use futures::Poll;
use serde::de::DeserializeOwned;
use serde::Serialize;
use serde_json;
#[cfg(test)]
mod tests;
pub(crate) const FLASH_COOKIE_NAME: &str = "_flash";
#[derive(Debug)]
pub struct FlashMessage<T>(T)
where
T: DeserializeOwned;
impl<T> FromRequest for FlashMessage<T>
where
T: DeserializeOwned,
{
type Config = ();
type Future = Result<FlashMessage<T>, Self::Error>;
type Error = Error;
fn from_request(req: &HttpRequest, _: &mut actix_web::dev::Payload) -> Self::Future {
if let Some(cookie) = req.cookie(FLASH_COOKIE_NAME) {
let inner = serde_json::from_str(cookie.value())
.map_err(|_| ErrorBadRequest("Invalid flash cookie"))?;
Ok(FlashMessage(inner))
} else {
Err(ErrorBadRequest("No flash cookie"))
}
}
}
impl<M> FlashMessage<M>
where
M: Serialize + DeserializeOwned,
{
pub fn new(inner: M) -> Self {
FlashMessage(inner)
}
pub fn into_inner(self) -> M {
self.0
}
}
pub struct FlashResponse<R, M>
where
R: Responder,
M: Serialize + DeserializeOwned,
{
delegate_to: R,
message: Option<FlashMessage<M>>,
}
impl<R, M> Responder for FlashResponse<R, M>
where
R: Responder + 'static,
R::Future: 'static,
M: Serialize + DeserializeOwned + 'static,
{
type Error = actix_http::Error;
type Future = Box<dyn Future<Item = HttpResponse, Error = Self::Error>>;
fn respond_to(mut self, req: &HttpRequest) -> Self::Future {
let message = self.message.take();
let out = self
.delegate_to
.respond_to(req)
.into_future()
.map_err(|e| e.into())
.and_then(|mut response| {
if let Some(msg) = message {
let data =
serde_json::to_string(&msg.into_inner()).expect("Serialize cannot fail");
let mut flash_cookie = Cookie::new(FLASH_COOKIE_NAME, data);
flash_cookie.set_path("/");
let out = response
.add_cookie(&flash_cookie)
.into_future()
.map_err(|e| e.into())
.map(|_| response);
EitherFuture::A(out)
} else {
EitherFuture::B(futures::future::ok(response))
}
});
Box::new(out)
}
}
impl<R, M> FlashResponse<R, M>
where
R: Responder,
M: Serialize + DeserializeOwned,
{
pub fn new(message: Option<M>, response: R) -> Self {
Self {
delegate_to: response,
message: message.map(FlashMessage),
}
}
}
impl<M> FlashResponse<HttpResponse, M>
where
M: Serialize + DeserializeOwned,
{
pub fn with_redirect(message: M, location: &str) -> Self {
let response = actix_web::HttpResponse::SeeOther()
.header(actix_web::http::header::LOCATION, location)
.finish();
Self::new(Some(message), response)
}
}
#[derive(Debug, Default)]
pub struct FlashMiddleware;
impl<S, B> Transform<S> for FlashMiddleware
where
S: Service<Request = ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
S::Future: 'static,
B: 'static,
{
type Request = ServiceRequest;
type Response = ServiceResponse<B>;
type Error = Error;
type InitError = ();
type Transform = FlashMiddlewareServiceWrapper<S>;
type Future = FutureResult<Self::Transform, Self::InitError>;
fn new_transform(&self, service: S) -> Self::Future {
fut_ok(FlashMiddlewareServiceWrapper(service))
}
}
pub struct FlashMiddlewareServiceWrapper<S>(S);
impl<S, B> Service for FlashMiddlewareServiceWrapper<S>
where
S: Service<Request = ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
S::Future: 'static,
B: 'static,
{
type Request = ServiceRequest;
type Response = ServiceResponse<B>;
type Error = Error;
type Future = Box<dyn Future<Item = Self::Response, Error = Self::Error>>;
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
self.0.poll_ready()
}
fn call(&mut self, req: ServiceRequest) -> Self::Future {
Box::new(self.0.call(req).and_then(move |mut res| {
let mut jar = CookieJar::new();
if let Some(cookie) = res.request().cookie(FLASH_COOKIE_NAME) {
jar.add_original(cookie);
jar.remove(Cookie::named(FLASH_COOKIE_NAME));
}
for cookie in jar.delta() {
res.response_mut().add_cookie(cookie)?;
}
Ok(res)
}))
}
}