Skip to main content

rustigram_bot/
state.rs

1use dashmap::DashMap;
2use std::any::{Any, TypeId};
3use std::sync::Arc;
4
5/// Thread-safe, type-erased key-value store for sharing state between handlers.
6///
7/// Backed by a `DashMap` so reads never block writes.
8#[derive(Clone, Default)]
9/// Thread-safe, type-keyed store for sharing data across handlers.
10///
11/// Values are stored by their [`TypeId`] so each type occupies exactly one
12/// slot. Use this for things that are global to the bot — database pools,
13/// configuration, shared counters.
14///
15/// `StateStorage` is cheap to clone (internally `Arc`-backed) and reads
16/// never block writes.
17///
18/// # Example
19///
20/// ```rust,ignore
21/// use rustigram_bot::state::StateStorage;
22///
23/// let store = StateStorage::new();
24/// store.insert(42_u32);
25/// store.insert(my_db_pool);
26///
27/// let n: u32 = store.get().unwrap(); // 42
28/// ```
29pub struct StateStorage {
30    inner: Arc<DashMap<TypeId, Arc<dyn Any + Send + Sync>>>,
31}
32
33impl StateStorage {
34    /// Creates a new empty `StateStorage`.
35    pub fn new() -> Self {
36        Self::default()
37    }
38
39    /// Inserts a value. Replaces any existing value of the same type.
40    pub fn insert<T: Any + Send + Sync + 'static>(&self, value: T) {
41        self.inner.insert(TypeId::of::<T>(), Arc::new(value));
42    }
43
44    /// Returns a clone of the stored value for type `T`, if present.
45    pub fn get<T: Any + Send + Sync + Clone + 'static>(&self) -> Option<T> {
46        self.inner
47            .get(&TypeId::of::<T>())
48            .and_then(|v| v.downcast_ref::<T>().cloned())
49    }
50
51    /// Returns an `Arc` reference to the stored value for type `T`.
52    pub fn get_arc<T: Any + Send + Sync + 'static>(&self) -> Option<Arc<T>> {
53        self.inner
54            .get(&TypeId::of::<T>())
55            .and_then(|v| Arc::clone(&*v).downcast::<T>().ok())
56    }
57}
58
59/// Per-user or per-chat FSM state keyed by `(chat_id, user_id)`.
60///
61/// Stores the current dialogue state as a type-erased value so the state
62/// machine type does not need to be known at the storage level.
63#[derive(Clone, Default)]
64/// Per-user conversation state store for finite state machines.
65///
66/// State is keyed by `(chat_id, user_id)` pairs. Any `'static` type can be
67/// stored — no shared trait is required. Use this to track where a user is
68/// in a multi-step conversation.
69///
70/// # Example
71///
72/// ```rust,ignore
73/// use rustigram_bot::state::DialogueStorage;
74///
75/// #[derive(Clone)]
76/// enum State { AwaitingName, AwaitingEmail { name: String } }
77///
78/// let storage = DialogueStorage::new();
79/// storage.set(chat_id, user_id, State::AwaitingName);
80///
81/// match storage.get::<State>(chat_id, user_id) {
82///     Some(State::AwaitingName) => { /* ask for name */ }
83///     Some(State::AwaitingEmail { name }) => { /* ask for email */ }
84///     None => { /* no active dialogue */ }
85/// }
86///
87/// storage.remove(chat_id, user_id); // clear when done
88/// ```
89pub struct DialogueStorage {
90    inner: Arc<DashMap<(i64, i64), Arc<dyn Any + Send + Sync>>>,
91}
92
93impl DialogueStorage {
94    /// Creates a new empty `DialogueStorage`.
95    pub fn new() -> Self {
96        Self::default()
97    }
98
99    /// Sets the dialogue state for a `(chat_id, user_id)` pair.
100    pub fn set<S: Any + Send + Sync + 'static>(&self, chat_id: i64, user_id: i64, state: S) {
101        self.inner.insert((chat_id, user_id), Arc::new(state));
102    }
103
104    /// Returns the dialogue state for a `(chat_id, user_id)` pair.
105    pub fn get<S: Any + Send + Sync + Clone + 'static>(
106        &self,
107        chat_id: i64,
108        user_id: i64,
109    ) -> Option<S> {
110        self.inner
111            .get(&(chat_id, user_id))
112            .and_then(|v| v.downcast_ref::<S>().cloned())
113    }
114
115    /// Removes the dialogue state for a `(chat_id, user_id)` pair.
116    pub fn remove(&self, chat_id: i64, user_id: i64) {
117        self.inner.remove(&(chat_id, user_id));
118    }
119}