1use std::rc::Rc;
41use std::task::{Context, Poll};
42
43use futures::future::{err, ok, LocalBoxFuture, Ready, TryFutureExt};
44
45use serde::{de::DeserializeOwned, Serialize, Deserialize};
46
47use actix_service::{Service, Transform};
48
49#[cfg(feature = "v2")]
50pub(crate) use actix_web_v2 as actix_web;
51
52#[cfg(feature = "v3")]
53pub(crate) use actix_web_v3 as actix_web;
54
55use actix_web::cookie::{Cookie, CookieJar};
56use actix_web::dev::{MessageBody, ServiceRequest, ServiceResponse};
57use actix_web::error::{Error, ErrorBadRequest, Result};
58use actix_web::{FromRequest, HttpMessage, HttpRequest, HttpResponse, Responder};
59
60#[derive(Debug)]
61struct FlashCookie(Cookie<'static>);
62#[derive(Clone)]
63struct FlashCookieValue(String);
64
65#[derive(Debug)]
67pub struct Message<T>(T);
68
69#[derive(Deserialize)]
70struct ValuedMessage<T> {
71 #[serde(rename="_")]
72 value: T
73}
74
75#[derive(Serialize)]
76struct ValuedMessageRef<'a, T> {
77 #[serde(rename="_")]
78 value: &'a T
79}
80
81impl<T> FromRequest for Message<T>
82where
83 T: DeserializeOwned + Serialize,
84{
85 type Config = ();
86 type Future = Ready<Result<Self, Self::Error>>;
87 type Error = Error;
88
89 fn from_request(req: &HttpRequest, _: &mut actix_web::dev::Payload) -> Self::Future {
90 if let Some(cookie) = req.extensions().get::<FlashCookie>() {
91 match serde_json::from_str(cookie.0.value()) {
92 Ok(ValuedMessage { value }) => { return ok(Message(value)); },
93 _ => {}
94 }
95 }
96 err(ErrorBadRequest("Invalid/missing flash cookie"))
97 }
98}
99
100impl<T> Message<T> {
101 pub fn new(inner: T) -> Self {
102 Self(inner)
103 }
104
105 pub fn into_inner(self) -> T {
106 self.0
107 }
108}
109
110pub struct Response<R, T>
112where
113 R: Responder,
114 T: Serialize + DeserializeOwned,
115{
116 responder: R,
117 message: Option<Message<T>>,
118}
119
120impl<R, T> Responder for Response<R, T>
121where
122 R: Responder + 'static,
123 T: Serialize + DeserializeOwned + 'static,
124{
125 type Error = Error;
126 type Future = LocalBoxFuture<'static, Result<HttpResponse, Self::Error>>;
127
128 fn respond_to(mut self, req: &HttpRequest) -> Self::Future {
129 let msg = self.message.take();
130
131 let out = self.responder.respond_to(req).err_into().and_then(|mut res| async {
132 if let Some(msg) = msg {
133 let json = serde_json::to_string(&ValuedMessageRef { value: &msg.0 })?;
134 res.extensions_mut().insert(FlashCookieValue(json));
135 }
136 Ok(res)
137 });
138
139 Box::pin(out)
140 }
141}
142
143impl<R, T> Response<R, T>
144where
145 R: Responder,
146 T: Serialize + DeserializeOwned,
147{
148 pub fn new(message: Option<T>, responder: R) -> Self {
149 Self { responder, message: message.map(Message) }
150 }
151}
152
153impl<T> Response<HttpResponse, T>
154where
155 T: Serialize + DeserializeOwned,
156{
157 pub fn with_redirect(message: T, location: &str) -> Self {
159 let response =
160 HttpResponse::SeeOther().header(actix_web::http::header::LOCATION, location).finish();
161 Self { message: Some(Message(message)), responder: response }
162 }
163}
164
165pub struct Flash {
167 cookie_name: Rc<str>,
168}
169
170impl Flash {
171 pub fn new<I: Into<Rc<str>>>(cookie_name: I) -> Self {
173 Self { cookie_name: cookie_name.into() }
174 }
175}
176
177impl Default for Flash {
178 fn default() -> Self {
179 Self::new("_flash")
180 }
181}
182
183pub struct FlashMiddleware<S> {
185 cookie_name: Rc<str>,
186 service: S,
187}
188
189impl<S, B> Transform<S> for Flash
190where
191 S: Service<Request = ServiceRequest, Response = ServiceResponse<B>, Error = Error> + 'static,
192 B: MessageBody + 'static,
193{
194 type Request = ServiceRequest;
195 type Response = ServiceResponse<B>;
196 type Error = Error;
197 type InitError = ();
198 type Transform = FlashMiddleware<S>;
199 type Future = Ready<Result<Self::Transform, Self::InitError>>;
200
201 fn new_transform(&self, service: S) -> Self::Future {
202 ok(FlashMiddleware { service, cookie_name: self.cookie_name.clone() })
203 }
204}
205
206impl<S, B> Service for FlashMiddleware<S>
207where
208 S: Service<Request = ServiceRequest, Response = ServiceResponse<B>, Error = Error> + 'static,
209 B: MessageBody + 'static,
210{
211 type Request = ServiceRequest;
212 type Response = ServiceResponse<B>;
213 type Error = Error;
214 type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;
215
216 fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
217 self.service.poll_ready(cx)
218 }
219
220 fn call(&mut self, req: ServiceRequest) -> Self::Future {
221 let cookie_name = String::from(self.cookie_name.as_ref());
222
223 if let Some(cookie) = req.cookie(&cookie_name) {
224 req.extensions_mut().insert(FlashCookie(cookie));
225 }
226
227 Box::pin(self.service.call(req).and_then(|mut res| async move {
228 let maybe_set_cookie = res.response().extensions().get::<FlashCookieValue>().cloned();
229
230 if let Some(FlashCookieValue(json)) = maybe_set_cookie {
231 let mut cookie = Cookie::new(cookie_name.clone(), json);
232 cookie.set_path("/");
233 res.response_mut().add_cookie(&cookie)?;
234 }
235
236 let mut jar = CookieJar::new();
237 if let Some(cookie) = res.request().cookie(&cookie_name) {
238 jar.add_original(cookie);
239 jar.remove(Cookie::build(cookie_name, "").path("/").finish());
240 }
241
242 for cookie in jar.delta() {
243 res.response_mut().add_cookie(cookie)?;
244 }
245
246 Ok(res)
247 }))
248 }
249}