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}