Expand description
An update dispatching model based on dptree.
In teloxide, update dispatching is declarative: it takes the form of a
chain of responsibility pattern enriched with a number of combinator
functions, which together form an instance of the dptree::Handler type.
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:
#[derive(Clone, Default)]
pub enum State {
#[default]
Start,
ReceiveFullName,
ReceiveProductChoice {
full_name: String,
},
}Then, we define a type Command to represent user commands such as
/start or /help:
#[derive(BotCommands, Clone)]
#[command(rename_rule = "lowercase", description = "These commands are supported:")]
enum Command {
#[command(description = "display this text.")]
Help,
#[command(description = "start the purchase procedure.")]
Start,
#[command(description = "cancel the purchase procedure.")]
Cancel,
}Now the key question: how to elegantly dispatch on different combinations of
State, Command, and Telegram updates? – i.e., we may want to execute
specific endpoints only in response to specific user commands and while we
are in a given dialogue state (and possibly under other circumstances!). The
solution is to use dptree:
fn schema() -> UpdateHandler<Box<dyn std::error::Error + Send + Sync + 'static>> {
use dptree::case;
let command_handler = teloxide::filter_command::<Command, _>()
.branch(
case![State::Start]
.branch(case![Command::Help].endpoint(help))
.branch(case![Command::Start].endpoint(start)),
)
.branch(case![Command::Cancel].endpoint(cancel));
let message_handler = Update::filter_message()
.branch(command_handler)
.branch(case![State::ReceiveFullName].endpoint(receive_full_name))
.branch(dptree::endpoint(invalid_state));
let callback_query_handler = Update::filter_callback_query().branch(
case![State::ReceiveProductChoice { full_name }].endpoint(receive_product_selection),
);
dialogue::enter::<Update, InMemStorage<State>, State, _>()
.branch(message_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 witha, then, if it neglects the update, tryb”. - 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:
type MyDialogue = Dialogue<State, InMemStorage<State>>;
type HandlerResult = Result<(), Box<dyn std::error::Error + Send + Sync>>;
async fn start(bot: Bot, dialogue: MyDialogue, msg: Message) -> HandlerResult {
todo!()
}
async fn help(bot: Bot, msg: Message) -> HandlerResult {
todo!()
}
async fn cancel(bot: Bot, dialogue: MyDialogue, msg: Message) -> HandlerResult {
todo!()
}
async fn invalid_state(bot: Bot, msg: Message) -> HandlerResult {
todo!()
}
async fn receive_full_name(bot: Bot, dialogue: MyDialogue, msg: Message) -> HandlerResult {
todo!()
}
async fn receive_product_selection(
bot: Bot,
dialogue: MyDialogue,
full_name: String, // Available from `State::ReceiveProductChoice`.
q: CallbackQuery,
) -> HandlerResult {
todo!()
}Each parameter is supplied as a dependency by teloxide. In particular:
bot: Botcomes from the dispatcher (see below)msg: Messagecomes fromUpdate::filter_messageq: CallbackQuerycomes fromUpdate::filter_callback_querydialogue: MyDialoguecomes fromdialogue::enterfull_name: Stringcomes fromdptree::case![State::ReceiveProductChoice { full_name }]
Inside main, we plug the schema into Dispatcher like this:
#[tokio::main]
async fn main() {
let bot = Bot::from_env();
Dispatcher::builder(bot, schema())
.dependencies(dptree::deps![InMemStorage::<State>::new()])
.enable_ctrlc_handler()
.build()
.dispatch()
.await;
}In a call to DispatcherBuilder::dependencies, we specify a list of
additional dependencies that all handlers will receive as parameters. Here,
we only specify an in-memory storage of dialogues needed for
dialogue::enter. However, in production bots, you normally also pass a
database connection, configuration, and other stuff.
All in all, dptree can be seen as an extensible alternative to pattern
matching, with support for dependency injection (DI) and a few other
useful features. See examples/dispatching_features.rs as a more involved
example.
§Dispatching or REPLs?
The difference between dispatching and the REPLs (crate::repl & co) is
that dispatching gives you a greater degree of flexibility at the cost of a
bit more complicated setup.
Here are things that dispatching can do, but REPLs can’t:
- Handle different kinds of
Update - Pass dependencies to handlers
- Disable a default Ctrl-C handling
- Control your default and error handlers
- Use dialogues
- Use
dptree-related functionality - Probably more
Thus, REPLs are good for simple bots and rapid prototyping, but for more involved scenarios, we recommend using dispatching over REPLs.
Modules§
- dialogue
- Support for user dialogues.
Structs§
- Default
Key - Default distribution key for dispatching.
- Dispatcher
- The base for update dispatching.
- Dispatcher
Builder - The builder for
Dispatcher. - DpHandler
Description - Handler description that is used by
Dispatcher. - Idle
Shutdown Error - This error is returned from
ShutdownToken::shutdownwhen trying to shutdown an idleDispatcher. - Shutdown
Token - A token which used to shutdown
Dispatcher.
Traits§
- Handler
Ext - Extension methods for working with
dptreehandlers. - Message
Filter Ext - Filter methods for
Message. - Update
Filter Ext - Filter methods for
Update. - Update
Handler Tracing Ext tracing
Functions§
- filter_
command - Returns a handler that accepts a parsed command
C. - filter_
mention_ command - Returns a handler that accepts a parsed command
Cif the command contains a bot mention, for example/start@my_bot.
Type Aliases§
- Update
Handler - A handler that processes updates from Telegram.