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}