mirror of
https://github.com/teloxide/teloxide.git
synced 2025-03-22 06:45:37 +01:00
Improve the dispatching explanation (docs)
This commit is contained in:
parent
3d9f4ef2b0
commit
9ab3b3a1c5
1 changed files with 62 additions and 68 deletions
|
@ -4,31 +4,23 @@
|
||||||
//! [chain of responsibility] pattern enriched with a number of combinator
|
//! [chain of responsibility] pattern enriched with a number of combinator
|
||||||
//! functions, which together form an instance of the [`dptree::Handler`] type.
|
//! functions, which together form an instance of the [`dptree::Handler`] type.
|
||||||
//!
|
//!
|
||||||
//! Let us look at this simple example:
|
//! Take [`examples/purchase.rs`] as an example of dispatching logic. First, we
|
||||||
//!
|
//! define a type named `State` to represent the current state of a dialogue:
|
||||||
//! [[`examples/purchase.rs`](https://github.com/teloxide/teloxide/blob/master/examples/purchase.rs)]
|
|
||||||
//! ```no_run
|
|
||||||
//! // Imports omitted...
|
|
||||||
//! # use teloxide::{
|
|
||||||
//! # dispatching::{dialogue::InMemStorage, UpdateHandler},
|
|
||||||
//! # prelude::*,
|
|
||||||
//! # types::{InlineKeyboardButton, InlineKeyboardMarkup},
|
|
||||||
//! # utils::command::BotCommands,
|
|
||||||
//! # };
|
|
||||||
//!
|
|
||||||
//! type MyDialogue = Dialogue<State, InMemStorage<State>>;
|
|
||||||
//! type HandlerResult = Result<(), Box<dyn std::error::Error + Send + Sync>>;
|
|
||||||
//!
|
//!
|
||||||
|
//! ```ignore
|
||||||
//! #[derive(Clone, Default)]
|
//! #[derive(Clone, Default)]
|
||||||
//! pub enum State {
|
//! pub enum State {
|
||||||
//! #[default]
|
//! #[default]
|
||||||
//! Start,
|
//! Start,
|
||||||
//! ReceiveFullName,
|
//! ReceiveFullName,
|
||||||
//! ReceiveProductChoice {
|
//! ReceiveProductChoice { full_name: String },
|
||||||
//! full_name: String,
|
|
||||||
//! },
|
|
||||||
//! }
|
//! }
|
||||||
|
//! ```
|
||||||
//!
|
//!
|
||||||
|
//! Then, we define a type `Command` to represent user commands such as
|
||||||
|
//! `/start` or `/help`:
|
||||||
|
//!
|
||||||
|
//! ```ignore
|
||||||
//! #[derive(BotCommands, Clone)]
|
//! #[derive(BotCommands, Clone)]
|
||||||
//! #[command(rename = "lowercase", description = "These commands are supported:")]
|
//! #[command(rename = "lowercase", description = "These commands are supported:")]
|
||||||
//! enum Command {
|
//! enum Command {
|
||||||
|
@ -39,23 +31,15 @@
|
||||||
//! #[command(description = "cancel the purchase procedure.")]
|
//! #[command(description = "cancel the purchase procedure.")]
|
||||||
//! Cancel,
|
//! Cancel,
|
||||||
//! }
|
//! }
|
||||||
|
//! ```
|
||||||
//!
|
//!
|
||||||
//! #[tokio::main]
|
//! Now the key question: how to elegantly dispatch on different combinations of
|
||||||
//! async fn main() {
|
//! `State`, `Command`, and Telegram updates? -- i.e., we may want to execute
|
||||||
//! // Setup is omitted...
|
//! specific endpoints only in response to specific user commands and while we
|
||||||
//! # pretty_env_logger::init();
|
//! are in a given dialogue state (and possibly under other circumstances!). The
|
||||||
//! # log::info!("Starting purchase bot...");
|
//! solution is to use [`dptree`]:
|
||||||
//! #
|
|
||||||
//! # let bot = Bot::from_env().auto_send();
|
|
||||||
//! #
|
|
||||||
//! # Dispatcher::builder(bot, schema())
|
|
||||||
//! # .dependencies(dptree::deps![InMemStorage::<State>::new()])
|
|
||||||
//! # .build()
|
|
||||||
//! # .setup_ctrlc_handler()
|
|
||||||
//! # .dispatch()
|
|
||||||
//! # .await;
|
|
||||||
//! }
|
|
||||||
//!
|
//!
|
||||||
|
//! ```ignore
|
||||||
//! fn schema() -> UpdateHandler<Box<dyn std::error::Error + Send + Sync + 'static>> {
|
//! fn schema() -> UpdateHandler<Box<dyn std::error::Error + Send + Sync + 'static>> {
|
||||||
//! let command_handler = teloxide::filter_command::<Command, _>()
|
//! let command_handler = teloxide::filter_command::<Command, _>()
|
||||||
//! .branch(
|
//! .branch(
|
||||||
|
@ -79,7 +63,30 @@
|
||||||
//! .branch(message_handler)
|
//! .branch(message_handler)
|
||||||
//! .branch(callback_query_handler)
|
//! .branch(callback_query_handler)
|
||||||
//! }
|
//! }
|
||||||
|
//! ```
|
||||||
//!
|
//!
|
||||||
|
//! The overall logic should be clear. Throughout the above example, we use
|
||||||
|
//! several techniques:
|
||||||
|
//!
|
||||||
|
//! - **Branching:** `a.branch(b)` roughly means "try to handle an update with
|
||||||
|
//! `a`, then, if it
|
||||||
|
//! neglects the update, try `b`".
|
||||||
|
//! - **Pattern matching:** We also use the [`dptree::case!`] macro
|
||||||
|
//! extensively, which acts as a filter on an enumeration: if it is of a
|
||||||
|
//! certain variant, it passes the variant's payload down the handler chain;
|
||||||
|
//! otherwise, it neglects an update.
|
||||||
|
//! - **Endpoints:** To specify the final function to handle an update, we use
|
||||||
|
//! [`dptree::Handler::endpoint`].
|
||||||
|
//!
|
||||||
|
//! Notice the clear and uniform code structure: regardless of the dispatch
|
||||||
|
//! criteria, we use the same program constructions. In future, you may want to
|
||||||
|
//! introduce your application-specific filters or data structures to match upon
|
||||||
|
//! -- no problem, reuse [`dptree::Handler::filter`], [`dptree::case!`], and
|
||||||
|
//! other combinators in the same way!
|
||||||
|
//!
|
||||||
|
//! Finally, we define our endpoints like this:
|
||||||
|
//!
|
||||||
|
//! ```ignore
|
||||||
//! // Handler definitions omitted...
|
//! // Handler definitions omitted...
|
||||||
//!
|
//!
|
||||||
//! async fn start(bot: AutoSend<Bot>, msg: Message, dialogue: MyDialogue) -> HandlerResult {
|
//! async fn start(bot: AutoSend<Bot>, msg: Message, dialogue: MyDialogue) -> HandlerResult {
|
||||||
|
@ -116,55 +123,42 @@
|
||||||
//! }
|
//! }
|
||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
//! The above code shows how to dispatch on different combinations of a state
|
//! Each parameter is supplied as a dependency by teloxide. In particular:
|
||||||
//! and command _elegantly_. We give a top-bottom explanation of the function
|
//! - `bot: AutoSend<Bot>` comes from the dispatcher (see below);
|
||||||
//! `schema`, which constructs the main update handler:
|
//! - `msg: Message` comes from [`Update::filter_message`];
|
||||||
|
//! - `q: CallbackQuery` comes from [`Update::filter_callback_query`];
|
||||||
|
//! - `dialogue: MyDialogue` comes from [`dialogue::enter`];
|
||||||
|
//! - `full_name: String` comes from `dptree::case![State::ReceiveProductChoice
|
||||||
|
//! { full_name }]`.
|
||||||
//!
|
//!
|
||||||
//! - We call the [`dialogue::enter`] function to initiate dialogue
|
//! Inside `main`, we plug the schema into [`Dispatcher`] like this:
|
||||||
//! interaction. Then we call [`dptree::Handler::branch`] two times to form a
|
|
||||||
//! tree of responsibility of `message_handler` and `callback_query_handler`.
|
|
||||||
//! - Inside `message_handler`, we use [`Update::filter_message`] as a filter
|
|
||||||
//! for incoming messages. Then we create a tree of responsibility again,
|
|
||||||
//! consisting of three branches with a similar structure.
|
|
||||||
//! - Inside `callback_query_handler`, we use
|
|
||||||
//! [`Update::filter_callback_query`] as a filter and create one branch for
|
|
||||||
//! handling product selection.
|
|
||||||
//!
|
|
||||||
//! `a.branch(b)` roughly means "try to handle an update with `a`, then, if it
|
|
||||||
//! fails, try `b`". We use branching multiple times here, which is a natural
|
|
||||||
//! pattern for describing dispatching logic. We also use the [`dptree::case!`]
|
|
||||||
//! macro extensively, which acts as a filter on an enumeration: if it is of a
|
|
||||||
//! certain variant, it passes the variant's payload down the handler chain;
|
|
||||||
//! otherwise, it neglects an update. Note how we utilise this macro both for
|
|
||||||
//! `State` and `Command` in the same way!
|
|
||||||
//!
|
|
||||||
//! Finally, we plug the schema into [`Dispatcher`] like this:
|
|
||||||
//!
|
//!
|
||||||
//! ```ignore
|
//! ```ignore
|
||||||
//! # #[tokio::main]
|
//! #[tokio::main]
|
||||||
//! # async fn main() {
|
//! async fn main() {
|
||||||
//! let bot = Bot::from_env().auto_send();
|
//! let bot = Bot::from_env().auto_send();
|
||||||
//!
|
//!
|
||||||
//! Dispatcher::builder(bot, schema())
|
//! Dispatcher::builder(bot, schema())
|
||||||
//! .dependencies(dptree::deps![InMemStorage::<State>::new()])
|
//! .dependencies(dptree::deps![InMemStorage::<State>::new()])
|
||||||
//! .build()
|
//! .build()
|
||||||
//! .setup_ctrlc_handler()
|
//! .setup_ctrlc_handler()
|
||||||
//! .dispatch()
|
//! .dispatch()
|
||||||
//! .await;
|
//! .await;
|
||||||
//! # }
|
//! }
|
||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
//! In a call to [`DispatcherBuilder::dependencies`], we specify a list of
|
//! In a call to [`DispatcherBuilder::dependencies`], we specify a list of
|
||||||
//! dependencies that all handlers will receive as parameters. Here, we only
|
//! additional dependencies that all handlers will receive as parameters. Here,
|
||||||
//! specify an in-memory storage of dialogues needed for [`dialogue::enter`].
|
//! we only specify an in-memory storage of dialogues needed for
|
||||||
//! However, in production bots, you normally also pass a database connection,
|
//! [`dialogue::enter`]. However, in production bots, you normally also pass a
|
||||||
//! configuration, and other stuff.
|
//! database connection, configuration, and other stuff.
|
||||||
//!
|
//!
|
||||||
//! All in all, [`dptree`] can be seen as an extensible alternative to pattern
|
//! All in all, [`dptree`] can be seen as an extensible alternative to pattern
|
||||||
//! matching, with support for [dependency injection (DI)] and a few other
|
//! matching, with support for [dependency injection (DI)] and a few other
|
||||||
//! useful features. See [`examples/dispatching_features.rs`] as a more involved
|
//! useful features. See [`examples/dispatching_features.rs`] as a more involved
|
||||||
//! example.
|
//! example.
|
||||||
//!
|
//!
|
||||||
|
//! [`examples/purchase.rs`]: https://github.com/teloxide/teloxide/blob/master/examples/purchase.rs
|
||||||
//! [`Update::filter_message`]: crate::types::Update::filter_message
|
//! [`Update::filter_message`]: crate::types::Update::filter_message
|
||||||
//! [`Update::filter_callback_query`]: crate::types::Update::filter_callback_query
|
//! [`Update::filter_callback_query`]: crate::types::Update::filter_callback_query
|
||||||
//! [chain of responsibility]: https://en.wikipedia.org/wiki/Chain-of-responsibility_pattern
|
//! [chain of responsibility]: https://en.wikipedia.org/wiki/Chain-of-responsibility_pattern
|
||||||
|
|
Loading…
Add table
Reference in a new issue