jacquard_common/
session.rs1use alloc::boxed::Box;
4#[cfg(feature = "std")]
5use alloc::string::ToString;
6use alloc::sync::Arc;
7use core::error::Error as StdError;
8#[cfg(feature = "std")]
9use core::fmt::Display;
10use core::future::Future;
11use core::hash::Hash;
12use hashbrown::HashMap;
13#[cfg(feature = "std")]
14use miette::Diagnostic;
15use serde::Serialize;
16use serde::de::DeserializeOwned;
17use serde_json::Value;
18
19#[cfg(feature = "std")]
20use std::path::{Path, PathBuf};
21
22#[cfg(not(feature = "std"))]
24use maitake_sync::RwLock;
25#[cfg(feature = "std")]
26use tokio::sync::RwLock;
27
28#[derive(Debug, thiserror::Error)]
30#[cfg_attr(feature = "std", derive(Diagnostic))]
31#[non_exhaustive]
32pub enum SessionStoreError {
33 #[cfg(feature = "std")]
35 #[error("I/O error: {0}")]
36 #[cfg_attr(feature = "std", diagnostic(code(jacquard::session_store::io)))]
37 Io(#[from] std::io::Error),
38 #[error("serialization error: {0}")]
40 #[cfg_attr(feature = "std", diagnostic(code(jacquard::session_store::serde)))]
41 Serde(#[from] serde_json::Error),
42 #[error(transparent)]
44 #[cfg_attr(feature = "std", diagnostic(code(jacquard::session_store::other)))]
45 Other(#[from] Box<dyn StdError + Send + Sync>),
46}
47
48#[cfg_attr(not(target_arch = "wasm32"), trait_variant::make(Send))]
50pub trait SessionStore<K, T>: Send + Sync
51where
52 K: Eq + Hash,
53 T: Clone,
54{
55 fn get(&self, key: &K) -> impl Future<Output = Option<T>>;
57 fn set(&self, key: K, session: T) -> impl Future<Output = Result<(), SessionStoreError>>;
59 fn del(&self, key: &K) -> impl Future<Output = Result<(), SessionStoreError>>;
61}
62
63#[derive(Clone)]
65pub struct MemorySessionStore<K, T>(Arc<RwLock<HashMap<K, T>>>);
66
67impl<K, T> Default for MemorySessionStore<K, T> {
68 fn default() -> Self {
69 Self(Arc::new(RwLock::new(HashMap::new())))
70 }
71}
72
73impl<K, T> SessionStore<K, T> for MemorySessionStore<K, T>
74where
75 K: Eq + Hash + Send + Sync,
76 T: Clone + Send + Sync,
77{
78 async fn get(&self, key: &K) -> Option<T> {
79 self.0.read().await.get(key).cloned()
80 }
81 async fn set(&self, key: K, session: T) -> Result<(), SessionStoreError> {
82 self.0.write().await.insert(key, session);
83 Ok(())
84 }
85 async fn del(&self, key: &K) -> Result<(), SessionStoreError> {
86 self.0.write().await.remove(key);
87 Ok(())
88 }
89}
90
91#[cfg(feature = "std")]
103#[derive(Clone, Debug)]
104pub struct FileTokenStore {
105 pub path: PathBuf,
107}
108
109#[cfg(feature = "std")]
110impl FileTokenStore {
111 pub fn try_new(path: impl AsRef<Path>) -> Result<Self, SessionStoreError> {
121 let path = path.as_ref();
122
123 if let Some(parent) = path.parent() {
125 if !parent.as_os_str().is_empty() && !parent.exists() {
126 std::fs::create_dir_all(parent)?;
127 }
128 }
129
130 if !path.exists() {
132 std::fs::write(path, b"{}")?;
133 }
134
135 Ok(Self {
136 path: path.to_path_buf(),
137 })
138 }
139
140 pub fn new(path: impl AsRef<Path>) -> Self {
147 Self::try_new(path).expect("failed to initialize FileTokenStore")
148 }
149}
150
151#[cfg(feature = "std")]
152impl<K: Eq + Hash + Display + Send + Sync, T: Clone + Serialize + DeserializeOwned + Send + Sync>
153 SessionStore<K, T> for FileTokenStore
154{
155 async fn get(&self, key: &K) -> Option<T> {
157 let file = std::fs::read_to_string(&self.path).ok()?;
158 let store: Value = serde_json::from_str(&file).ok()?;
159
160 let session = store.get(key.to_string())?;
161 serde_json::from_value(session.clone()).ok()
162 }
163 async fn set(&self, key: K, session: T) -> Result<(), SessionStoreError> {
165 let file = std::fs::read_to_string(&self.path)?;
166 let mut store: Value = serde_json::from_str(&file)?;
167 let key_string = key.to_string();
168 if let Some(store) = store.as_object_mut() {
169 store.insert(key_string, serde_json::to_value(session.clone())?);
170
171 std::fs::write(&self.path, serde_json::to_string_pretty(&store)?)?;
172 Ok(())
173 } else {
174 Err(SessionStoreError::Other("invalid store".into()))
175 }
176 }
177 async fn del(&self, key: &K) -> Result<(), SessionStoreError> {
179 let file = std::fs::read_to_string(&self.path)?;
180 let mut store: Value = serde_json::from_str(&file)?;
181 let key_string = key.to_string();
182 if let Some(store) = store.as_object_mut() {
183 store.remove(&key_string);
184
185 std::fs::write(&self.path, serde_json::to_string_pretty(&store)?)?;
186 Ok(())
187 } else {
188 Err(SessionStoreError::Other("invalid store".into()))
189 }
190 }
191}