1use crate::cookie::CookieOptions;
2use crate::error::SessionError;
3use async_session::{Session, SessionStore};
4use async_trait::async_trait;
5use std::{ops::Deref, sync::Arc};
6use warp::{Rejection, Reply};
7
8#[derive(Debug, Clone)]
9pub struct ArcSessionStore<T: SessionStore>(pub Arc<T>);
10
11#[async_trait]
12impl<T> SessionStore for ArcSessionStore<T>
13where
14 T: SessionStore,
15{
16 async fn load_session(&self, cookie_value: String) -> async_session::Result<Option<Session>> {
17 self.0.deref().load_session(cookie_value).await
18 }
19 async fn store_session(&self, session: Session) -> async_session::Result<Option<String>> {
20 self.0.deref().store_session(session).await
21 }
22 async fn destroy_session(&self, session: Session) -> async_session::Result {
23 self.0.deref().destroy_session(session).await
24 }
25 async fn clear_store(&self) -> async_session::Result {
26 self.0.deref().clear_store().await
27 }
28}
29
30#[derive(Clone)]
33pub struct SessionWithStore<S: SessionStore> {
34 pub session: Session,
35 pub session_store: S,
36 pub cookie_options: CookieOptions,
37}
38
39pub struct WithSession<T: Reply> {
42 reply: T,
43 cookie_options: CookieOptions,
44}
45
46impl<T> WithSession<T>
47where
48 T: Reply,
49{
50 pub async fn new<S: SessionStore>(
56 reply: T,
57 session_with_store: SessionWithStore<S>,
58 ) -> Result<WithSession<T>, Rejection> {
59 let mut cookie_options = session_with_store.cookie_options;
60
61 if session_with_store.session.is_destroyed() {
62 cookie_options.cookie_value = Some("".to_string());
63 cookie_options.max_age = Some(0);
64
65 session_with_store
66 .session_store
67 .destroy_session(session_with_store.session)
68 .await
69 .map_err(|source| SessionError::DestroyError { source })?;
70 } else {
71 if session_with_store.session.data_changed() {
72 match session_with_store
73 .session_store
74 .store_session(session_with_store.session)
75 .await
76 .map_err(|source| SessionError::StoreError { source })?
77 {
78 Some(sid) => cookie_options.cookie_value = Some(sid),
79 None => (),
80 }
81 }
82 }
83
84 Ok(WithSession {
85 reply,
86 cookie_options,
87 })
88 }
89}
90
91impl<T> Reply for WithSession<T>
92where
93 T: Reply,
94{
95 fn into_response(self) -> warp::reply::Response {
96 let mut res = self.reply.into_response();
97 if let Some(_) = self.cookie_options.cookie_value {
98 res.headers_mut().append(
99 "Set-Cookie",
100 warp::http::header::HeaderValue::from_str(&self.cookie_options.to_string())
101 .unwrap(),
102 );
103 }
104
105 res
106 }
107}
108
109#[cfg(test)]
110mod tests {
111 use super::{SessionWithStore, WithSession};
112 use crate::cookie::CookieOptions;
113 use async_session::{MemoryStore, Session};
114
115 #[tokio::test]
116 async fn test_session_reply_with_no_data_changed() {
117 let html_reply = warp::reply::html("".to_string());
118 let session = Session::new();
119 let session_store = MemoryStore::new();
120 let cookie_options = CookieOptions::default();
121 let session_with_store = SessionWithStore {
122 session,
123 session_store,
124 cookie_options,
125 };
126
127 assert_eq!(session_with_store.session.data_changed(), false);
128 WithSession::new(html_reply, session_with_store)
129 .await
130 .unwrap();
131 }
132
133 #[tokio::test]
134 async fn test_session_reply_with_data_changed() {
135 let html_reply = warp::reply::html("".to_string());
136 let mut session = Session::new();
137 session.insert("key", "value").unwrap();
138 let session_store = MemoryStore::new();
139 let cookie_options = CookieOptions::default();
140 let session_with_store = SessionWithStore {
141 session,
142 session_store,
143 cookie_options,
144 };
145
146 assert_eq!(session_with_store.session.data_changed(), true);
147 WithSession::new(html_reply, session_with_store)
148 .await
149 .unwrap();
150 }
151
152 #[tokio::test]
153 async fn test_session_reply_with_session_destroyed() {
154 let html_reply = warp::reply::html("".to_string());
155 let mut session = Session::new();
156 session.destroy();
157 let session_store = MemoryStore::new();
158 let cookie_options = CookieOptions::default();
159 let session_with_store = SessionWithStore {
160 session,
161 session_store,
162 cookie_options,
163 };
164
165 assert_eq!(session_with_store.session.is_destroyed(), true);
166 WithSession::new(html_reply, session_with_store)
167 .await
168 .unwrap();
169 }
170}