wecomx_auth/
credentials.rs1use std::sync::{Arc, RwLock};
7
8use serde::{Deserialize, Serialize};
9
10use crate::bot::BotCredential;
11use crate::error::AuthError;
12
13#[derive(Debug, Clone, Default, Serialize, Deserialize)]
15pub struct Credentials {
16 pub bot: Option<BotCredential>,
17 pub token: Option<String>,
18}
19
20pub trait CredentialStore: Send + Sync {
25 fn load(&self) -> Result<Option<Credentials>, AuthError>;
31
32 fn save(&self, credentials: &Credentials) -> Result<(), AuthError>;
37
38 fn clear(&self) -> Result<(), AuthError>;
43}
44
45#[derive(Debug, Clone, Default)]
49pub struct MemoryCredentialStore {
50 inner: Arc<RwLock<Option<Credentials>>>,
51}
52
53impl MemoryCredentialStore {
54 pub fn new(initial: Credentials) -> Self {
56 Self {
57 inner: Arc::new(RwLock::new(
58 Some(initial).filter(|c| c.bot.is_some() || c.token.is_some()),
59 )),
60 }
61 }
62
63 pub fn shared(self) -> Arc<Self> {
65 Arc::new(self)
66 }
67}
68
69impl CredentialStore for MemoryCredentialStore {
70 fn load(&self) -> Result<Option<Credentials>, AuthError> {
71 Ok(self.inner.read().unwrap_or_else(|e| e.into_inner()).clone())
72 }
73
74 fn save(&self, credentials: &Credentials) -> Result<(), AuthError> {
75 let value = Some(credentials.clone()).filter(|c| c.bot.is_some() || c.token.is_some());
76 *self.inner.write().unwrap_or_else(|e| e.into_inner()) = value;
77 Ok(())
78 }
79
80 fn clear(&self) -> Result<(), AuthError> {
81 *self.inner.write().unwrap_or_else(|e| e.into_inner()) = None;
82 Ok(())
83 }
84}
85
86#[cfg(test)]
87mod tests {
88 use super::*;
100
101 fn bot(id: &str) -> BotCredential {
102 BotCredential::new(id.to_string(), "secret".into())
103 }
104
105 #[test]
107 fn memory_store_roundtrip() {
108 let store = MemoryCredentialStore::new(Credentials::default());
109 assert!(store.load().unwrap().is_none());
110
111 let creds = Credentials {
112 bot: Some(bot("bot1")),
113 token: Some("tok-1".into()),
114 };
115 store.save(&creds).unwrap();
116 let loaded = store.load().unwrap().unwrap();
117 assert_eq!(loaded.bot.as_ref().map(|b| b.id.as_str()), Some("bot1"));
118 assert_eq!(loaded.token.as_deref(), Some("tok-1"));
119 }
120
121 #[test]
123 fn memory_store_save_empty_clears() {
124 let store = MemoryCredentialStore::new(Credentials {
125 bot: Some(bot("bot1")),
126 token: None,
127 });
128 store.save(&Credentials::default()).unwrap();
129 assert!(store.load().unwrap().is_none());
130 }
131
132 #[test]
134 fn memory_store_clear() {
135 let store = MemoryCredentialStore::new(Credentials {
136 bot: Some(bot("bot1")),
137 token: Some("tok-1".into()),
138 });
139 store.clear().unwrap();
140 assert!(store.load().unwrap().is_none());
141 store.clear().unwrap();
142 }
143}