Skip to main content

ferogram_fsm/
lib.rs

1// Copyright (c) Ankit Chaubey <ankitchaubey.dev@gmail.com>
2//
3// ferogram: async Telegram MTProto client in Rust
4// https://github.com/ankit-chaubey/ferogram
5//
6// Licensed under either the MIT License or the Apache License 2.0.
7// See the LICENSE-MIT or LICENSE-APACHE file in this repository:
8// https://github.com/ankit-chaubey/ferogram
9//
10// Feel free to use, modify, and share this code.
11// Please keep this notice when redistributing.
12
13#![cfg_attr(docsrs, feature(doc_cfg))]
14#![doc(html_root_url = "https://docs.rs/ferogram-fsm/0.6.4")]
15//! FSM state management for ferogram bots.
16//!
17//! This crate is part of [ferogram](https://crates.io/crates/ferogram), an async Rust
18//! MTProto client built by [Ankit Chaubey](https://github.com/ankit-chaubey).
19//!
20//! - Channel: [t.me/Ferogram](https://t.me/Ferogram)
21//! - Chat: [t.me/FerogramChat](https://t.me/FerogramChat)
22//!
23//! Provides a finite-state machine layer for multi-step bot conversations.
24//! Each user/chat slot holds an optional state string and an arbitrary
25//! key-value data bag. Handlers are gated on the current state and receive
26//! a [`StateContext`] to transition to the next state or read/write data.
27//!
28//! Most users reach this through the `ferogram` crate's handler builder
29//! (`.state::<MyState>(MyState::WaitingName)`). Use `ferogram-fsm` directly
30//! only when building a custom dispatcher or storage backend.
31//!
32//! # What's in here
33//!
34//! - **[`FsmState`]**: Trait that state enums must implement. Serialises a
35//!   variant to a string key and deserialises it back. Derived automatically
36//!   via `#[derive(FsmState)]` from `ferogram-derive`.
37//! - **[`StateContext`]**: Injected into state-matched handlers. Exposes
38//!   [`StateContext::transition`] to move to the next state, [`StateContext::clear_state`]
39//!   to finish the conversation, and typed [`StateContext::set_data`] /
40//!   [`StateContext::get_data`] for per-slot JSON-serialised fields.
41//! - **[`StateStorage`]**: Async trait for the persistence backend. Implement
42//!   it to add Redis, SQLite, or any other store.
43//! - **[`MemoryStorage`]**: Built-in in-process backend backed by `DashMap`.
44//!   Zero setup; state is lost on restart.
45//! - **[`StateKey`]** / **[`StateKeyStrategy`]**: Controls how the storage
46//!   slot is keyed. The default strategy keys by `(chat_id, user_id)` so
47//!   each user in a group has independent state.
48//! - **[`StorageError`]**: Error type returned by all storage operations.
49//!
50//! # Example
51//!
52//! ```rust,no_run
53//! use ferogram_fsm::{FsmState, MemoryStorage, StateContext};
54//!
55//! #[derive(Clone, Debug, PartialEq)]
56//! enum OrderState { WaitingItem, WaitingQty, Done }
57//!
58//! impl FsmState for OrderState {
59//!     fn as_key(&self) -> String {
60//!         match self {
61//!             Self::WaitingItem => "WaitingItem".into(),
62//!             Self::WaitingQty  => "WaitingQty".into(),
63//!             Self::Done        => "Done".into(),
64//!         }
65//!     }
66//!     fn from_key(key: &str) -> Option<Self> {
67//!         match key {
68//!             "WaitingItem" => Some(Self::WaitingItem),
69//!             "WaitingQty"  => Some(Self::WaitingQty),
70//!             "Done"        => Some(Self::Done),
71//!             _             => None,
72//!         }
73//!     }
74//! }
75//!
76//! // In practice, use #[derive(FsmState)] from ferogram-derive instead.
77//! ```
78
79#![deny(unsafe_code)]
80
81mod context;
82mod error;
83mod key;
84mod storage;
85
86pub use context::StateContext;
87pub use error::StorageError;
88pub use key::{MessageLike, StateKey, StateKeyStrategy};
89pub use storage::{MemoryStorage, StateStorage};
90
91/// A type that can be used as an FSM state.
92///
93/// Implement this trait on an enum to use it with [`StateContext`] and
94/// the FSM dispatcher.
95///
96/// In practice you will derive this via `#[derive(FsmState)]`:
97///
98/// ```rust,no_run
99/// #[derive(Clone, Debug, PartialEq)]
100/// enum CheckoutState {
101///     Cart,
102///     Address,
103///     Payment,
104///     Confirmation,
105/// }
106/// ```
107pub trait FsmState: Send + Sync + 'static {
108    /// Serialize this state variant to a string key (e.g. `"WaitingProduct"`).
109    fn as_key(&self) -> String;
110
111    /// Deserialize a state variant from a key string. Returns `None` if the
112    /// key does not match any variant.
113    fn from_key(key: &str) -> Option<Self>
114    where
115        Self: Sized;
116}