1use std::collections::HashMap;
25use std::error::Error;
26
27use async_trait::async_trait;
28use thiserror::Error;
29use tower_sessions::session::{Id, Record};
30use tower_sessions::{SessionStore, session_store};
31
32use crate::db::{Auto, Database, DatabaseError, Model, query};
33use crate::session::db::Session;
34use crate::session::store::{ERROR_PREFIX, MAX_COLLISION_RETRIES};
35use crate::utils::chrono::DateTimeWithOffsetAdapter;
36
37#[derive(Debug, Error)]
39#[non_exhaustive]
40pub enum DbStoreError {
41 #[error("{ERROR_PREFIX} {0} ")]
43 DatabaseError(#[from] DatabaseError),
44 #[error("{ERROR_PREFIX} sessionāid collision retried too many times ({0})")]
46 TooManyIdCollisions(u32),
47 #[error("{ERROR_PREFIX} JSON serialization error: {0}")]
49 Serialize(Box<dyn Error + Send + Sync>),
50 #[error("{ERROR_PREFIX} JSON serialization error: {0}")]
52 Deserialize(Box<dyn Error + Send + Sync>),
53}
54
55impl From<DbStoreError> for session_store::Error {
56 fn from(err: DbStoreError) -> Self {
57 match err {
58 DbStoreError::DatabaseError(db_err) => {
59 session_store::Error::Backend(db_err.to_string())
60 }
61 DbStoreError::Serialize(ser_err) => session_store::Error::Encode(ser_err.to_string()),
62 DbStoreError::Deserialize(de_err) => session_store::Error::Decode(de_err.to_string()),
63 other => session_store::Error::Backend(other.to_string()),
64 }
65 }
66}
67
68#[derive(Clone, Debug)]
89pub struct DbStore {
90 connection: Database,
91}
92
93impl DbStore {
94 #[must_use]
112 pub fn new(connection: Database) -> DbStore {
113 DbStore { connection }
114 }
115}
116
117#[async_trait]
118impl SessionStore for DbStore {
119 async fn create(&self, record: &mut Record) -> session_store::Result<()> {
120 for _ in 0..=MAX_COLLISION_RETRIES {
121 let key = record.id.to_string();
122
123 let data = serde_json::to_string(&record.data).unwrap();
124 let expiry = DateTimeWithOffsetAdapter::try_from(record.expiry_date)
125 .expect("Failed to convert expiry date to a valid datetime")
126 .into_chrono_db_safe();
127
128 let mut model = Session {
129 id: Auto::auto(),
130 key,
131 data,
132 expiry,
133 };
134
135 let res = self.connection.insert(&mut model).await;
136 match res {
137 Ok(()) => {
138 return Ok(());
139 }
140 Err(DatabaseError::UniqueViolation) => {
141 record.id = Id::default();
143 }
144 Err(err) => return Err(DbStoreError::DatabaseError(err))?,
145 }
146 }
147 Err(DbStoreError::TooManyIdCollisions(MAX_COLLISION_RETRIES))?
148 }
149
150 async fn save(&self, record: &Record) -> session_store::Result<()> {
151 let key = record.id.to_string();
153 let data = serde_json::to_string(&record.data)
154 .map_err(|err| DbStoreError::Serialize(Box::new(err)))?;
155
156 let query = query!(Session, $key == key)
157 .get(&self.connection)
158 .await
159 .map_err(DbStoreError::DatabaseError)?;
160 if let Some(mut model) = query {
161 model.data = data;
162 model
163 .update(&self.connection)
164 .await
165 .map_err(DbStoreError::DatabaseError)?;
166 } else {
167 let mut record = record.clone();
168 self.create(&mut record).await?;
169 }
170 Ok(())
171 }
172
173 async fn load(&self, session_id: &Id) -> session_store::Result<Option<Record>> {
174 let key = session_id.to_string();
175 let query = query!(Session, $key == key)
176 .get(&self.connection)
177 .await
178 .map_err(DbStoreError::DatabaseError)?;
179 if let Some(session) = query {
180 let data = serde_json::from_str::<HashMap<String, serde_json::Value>>(&session.data)
181 .map_err(|err| DbStoreError::Serialize(Box::new(err)))?;
182
183 let id = session
184 .key
185 .parse::<Id>()
186 .map_err(|err| DbStoreError::Deserialize(Box::new(err)))?;
187
188 let expiry_date = DateTimeWithOffsetAdapter::new(session.expiry).into_offsetdatetime();
189
190 let rec = Record {
191 id,
192 data,
193 expiry_date,
194 };
195
196 Ok(Some(rec))
197 } else {
198 Ok(None)
199 }
200 }
201
202 async fn delete(&self, session_id: &Id) -> session_store::Result<()> {
203 let key = session_id.to_string();
204 query!(Session, $key == key)
205 .delete(&self.connection)
206 .await
207 .map_err(DbStoreError::DatabaseError)?;
208 Ok(())
209 }
210}
211
212#[cfg(test)]
213mod tests {
214 use std::io;
215
216 use tower_sessions::session::Record;
217
218 use super::*;
219 use crate::db::DatabaseError;
220
221 #[cot::test]
222 async fn test_from_db_store_error_to_session_store_error() {
223 let sqlx_err = sqlx::Error::Protocol("protocol error".into());
224 let db_err = DatabaseError::DatabaseEngineError(sqlx_err);
225 let sess_err: session_store::Error = DbStoreError::DatabaseError(db_err).into();
226 assert!(matches!(sess_err, session_store::Error::Backend(_)));
227
228 let io_err = io::Error::other("oops");
229 let serialize_err: session_store::Error = DbStoreError::Serialize(Box::new(io_err)).into();
230
231 assert!(matches!(serialize_err, session_store::Error::Encode(_)));
232
233 let parse_err = serde_json::from_str::<Record>("not a json").unwrap_err();
234 let deserialize_err: session_store::Error =
235 DbStoreError::Deserialize(Box::new(parse_err)).into();
236 assert!(matches!(deserialize_err, session_store::Error::Decode(_)));
237
238 let sess_err: session_store::Error = DbStoreError::TooManyIdCollisions(99).into();
239 assert!(matches!(sess_err, session_store::Error::Backend(_)));
240 }
241}