jacquard_common/
session.rs1use miette::Diagnostic;
4use serde::Serialize;
5use serde::de::DeserializeOwned;
6use serde_json::Value;
7use std::collections::HashMap;
8use std::error::Error as StdError;
9use std::fmt::Display;
10use std::future::Future;
11use std::hash::Hash;
12use std::path::{Path, PathBuf};
13use std::sync::Arc;
14use tokio::sync::RwLock;
15
16#[derive(Debug, thiserror::Error, Diagnostic)]
18pub enum SessionStoreError {
19 #[error("I/O error: {0}")]
21 #[diagnostic(code(jacquard::session_store::io))]
22 Io(#[from] std::io::Error),
23 #[error("serialization error: {0}")]
25 #[diagnostic(code(jacquard::session_store::serde))]
26 Serde(#[from] serde_json::Error),
27 #[error(transparent)]
29 #[diagnostic(code(jacquard::session_store::other))]
30 Other(#[from] Box<dyn StdError + Send + Sync>),
31}
32
33#[cfg_attr(not(target_arch = "wasm32"), trait_variant::make(Send))]
35pub trait SessionStore<K, T>: Send + Sync
36where
37 K: Eq + Hash,
38 T: Clone,
39{
40 fn get(&self, key: &K) -> impl Future<Output = Option<T>>;
42 fn set(&self, key: K, session: T) -> impl Future<Output = Result<(), SessionStoreError>>;
44 fn del(&self, key: &K) -> impl Future<Output = Result<(), SessionStoreError>>;
46}
47
48#[derive(Clone)]
50pub struct MemorySessionStore<K, T>(Arc<RwLock<HashMap<K, T>>>);
51
52impl<K, T> Default for MemorySessionStore<K, T> {
53 fn default() -> Self {
54 Self(Arc::new(RwLock::new(HashMap::new())))
55 }
56}
57
58impl<K, T> SessionStore<K, T> for MemorySessionStore<K, T>
59where
60 K: Eq + Hash + Send + Sync,
61 T: Clone + Send + Sync + 'static,
62{
63 async fn get(&self, key: &K) -> Option<T> {
64 self.0.read().await.get(key).cloned()
65 }
66 async fn set(&self, key: K, session: T) -> Result<(), SessionStoreError> {
67 self.0.write().await.insert(key, session);
68 Ok(())
69 }
70 async fn del(&self, key: &K) -> Result<(), SessionStoreError> {
71 self.0.write().await.remove(key);
72 Ok(())
73 }
74}
75
76#[derive(Clone, Debug)]
88pub struct FileTokenStore {
89 pub path: PathBuf,
91}
92
93impl FileTokenStore {
94 pub fn new(path: impl AsRef<Path>) -> Self {
96 std::fs::create_dir_all(path.as_ref().parent().unwrap()).unwrap();
97 std::fs::write(path.as_ref(), b"{}").unwrap();
98
99 Self {
100 path: path.as_ref().to_path_buf(),
101 }
102 }
103}
104
105impl<
106 K: Eq + Hash + Display + Send + Sync + 'static,
107 T: Clone + Serialize + DeserializeOwned + Send + Sync + 'static,
108> SessionStore<K, T> for FileTokenStore
109{
110 async fn get(&self, key: &K) -> Option<T> {
112 let file = std::fs::read_to_string(&self.path).ok()?;
113 let store: Value = serde_json::from_str(&file).ok()?;
114
115 let session = store.get(key.to_string())?;
116 serde_json::from_value(session.clone()).ok()
117 }
118 async fn set(&self, key: K, session: T) -> Result<(), SessionStoreError> {
120 let file = std::fs::read_to_string(&self.path)?;
121 let mut store: Value = serde_json::from_str(&file)?;
122 let key_string = key.to_string();
123 if let Some(store) = store.as_object_mut() {
124 store.insert(key_string, serde_json::to_value(session.clone())?);
125
126 std::fs::write(&self.path, serde_json::to_string_pretty(&store)?)?;
127 Ok(())
128 } else {
129 Err(SessionStoreError::Other("invalid store".into()))
130 }
131 }
132 async fn del(&self, key: &K) -> Result<(), SessionStoreError> {
134 let file = std::fs::read_to_string(&self.path)?;
135 let mut store: Value = serde_json::from_str(&file)?;
136 let key_string = key.to_string();
137 if let Some(store) = store.as_object_mut() {
138 store.remove(&key_string);
139
140 std::fs::write(&self.path, serde_json::to_string_pretty(&store)?)?;
141 Ok(())
142 } else {
143 Err(SessionStoreError::Other("invalid store".into()))
144 }
145 }
146}