mirror of
https://github.com/teloxide/teloxide.git
synced 2024-12-22 22:46:39 +01:00
Merge pull request #613 from teloxide/dptree-case
Deprecate `teloxide::handler!` in favour of `dpree::case!`
Former-commit-id: 0d36a1e2b7
This commit is contained in:
commit
1d1ff7db5a
8 changed files with 35 additions and 197 deletions
|
@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- The `dispatching::filter_command` function (also accessible as `teloxide::filter_command`) as a shortcut for `dptree::entry().filter_command()`.
|
- The `dispatching::filter_command` function (also accessible as `teloxide::filter_command`) as a shortcut for `dptree::entry().filter_command()`.
|
||||||
|
- Re-export `dptree::case!` as `teloxide::handler!` (the former is preferred for new code).
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
|
|
|
@ -62,7 +62,7 @@ teloxide-macros = { version = "0.6.1", optional = true }
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
|
|
||||||
dptree = "0.2.0"
|
dptree = "0.2.1"
|
||||||
|
|
||||||
tokio = { version = "1.8", features = ["fs"] }
|
tokio = { version = "1.8", features = ["fs"] }
|
||||||
tokio-util = "0.6"
|
tokio-util = "0.6"
|
||||||
|
|
|
@ -221,12 +221,11 @@ async fn main() {
|
||||||
bot,
|
bot,
|
||||||
Update::filter_message()
|
Update::filter_message()
|
||||||
.enter_dialogue::<Message, InMemStorage<State>, State>()
|
.enter_dialogue::<Message, InMemStorage<State>, State>()
|
||||||
.branch(teloxide::handler![State::Start].endpoint(start))
|
.branch(dptree::case![State::Start].endpoint(start))
|
||||||
.branch(teloxide::handler![State::ReceiveFullName].endpoint(receive_full_name))
|
.branch(dptree::case![State::ReceiveFullName].endpoint(receive_full_name))
|
||||||
.branch(teloxide::handler![State::ReceiveAge { full_name }].endpoint(receive_age))
|
.branch(dptree::case![State::ReceiveAge { full_name }].endpoint(receive_age))
|
||||||
.branch(
|
.branch(
|
||||||
teloxide::handler![State::ReceiveLocation { full_name, age }]
|
dptree::case![State::ReceiveLocation { full_name, age }].endpoint(receive_location),
|
||||||
.endpoint(receive_location),
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.dependencies(dptree::deps![InMemStorage::<State>::new()])
|
.dependencies(dptree::deps![InMemStorage::<State>::new()])
|
||||||
|
|
|
@ -50,9 +50,9 @@ async fn main() {
|
||||||
|
|
||||||
let handler = Update::filter_message()
|
let handler = Update::filter_message()
|
||||||
.enter_dialogue::<Message, ErasedStorage<State>, State>()
|
.enter_dialogue::<Message, ErasedStorage<State>, State>()
|
||||||
.branch(teloxide::handler![State::Start].endpoint(start))
|
.branch(dptree::case![State::Start].endpoint(start))
|
||||||
.branch(
|
.branch(
|
||||||
teloxide::handler![State::GotNumber(n)]
|
dptree::case![State::GotNumber(n)]
|
||||||
.branch(dptree::entry().filter_command::<Command>().endpoint(got_number))
|
.branch(dptree::entry().filter_command::<Command>().endpoint(got_number))
|
||||||
.branch(dptree::endpoint(invalid_command)),
|
.branch(dptree::endpoint(invalid_command)),
|
||||||
);
|
);
|
||||||
|
|
|
@ -43,12 +43,11 @@ async fn main() {
|
||||||
bot,
|
bot,
|
||||||
Update::filter_message()
|
Update::filter_message()
|
||||||
.enter_dialogue::<Message, InMemStorage<State>, State>()
|
.enter_dialogue::<Message, InMemStorage<State>, State>()
|
||||||
.branch(teloxide::handler![State::Start].endpoint(start))
|
.branch(dptree::case![State::Start].endpoint(start))
|
||||||
.branch(teloxide::handler![State::ReceiveFullName].endpoint(receive_full_name))
|
.branch(dptree::case![State::ReceiveFullName].endpoint(receive_full_name))
|
||||||
.branch(teloxide::handler![State::ReceiveAge { full_name }].endpoint(receive_age))
|
.branch(dptree::case![State::ReceiveAge { full_name }].endpoint(receive_age))
|
||||||
.branch(
|
.branch(
|
||||||
teloxide::handler![State::ReceiveLocation { full_name, age }]
|
dptree::case![State::ReceiveLocation { full_name, age }].endpoint(receive_location),
|
||||||
.endpoint(receive_location),
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.dependencies(dptree::deps![InMemStorage::<State>::new()])
|
.dependencies(dptree::deps![InMemStorage::<State>::new()])
|
||||||
|
|
|
@ -13,7 +13,10 @@
|
||||||
// ```
|
// ```
|
||||||
|
|
||||||
use teloxide::{
|
use teloxide::{
|
||||||
dispatching::dialogue::{self, InMemStorage},
|
dispatching::{
|
||||||
|
dialogue::{self, InMemStorage},
|
||||||
|
UpdateHandler,
|
||||||
|
},
|
||||||
prelude::*,
|
prelude::*,
|
||||||
types::{InlineKeyboardButton, InlineKeyboardMarkup},
|
types::{InlineKeyboardButton, InlineKeyboardMarkup},
|
||||||
utils::command::BotCommands,
|
utils::command::BotCommands,
|
||||||
|
@ -53,30 +56,7 @@ async fn main() {
|
||||||
|
|
||||||
let bot = Bot::from_env().auto_send();
|
let bot = Bot::from_env().auto_send();
|
||||||
|
|
||||||
let command_handler = teloxide::filter_command::<Command, _>()
|
Dispatcher::builder(bot, schema())
|
||||||
.branch(
|
|
||||||
teloxide::handler![State::Start]
|
|
||||||
.branch(teloxide::handler![Command::Help].endpoint(help))
|
|
||||||
.branch(teloxide::handler![Command::Start].endpoint(start)),
|
|
||||||
)
|
|
||||||
.branch(teloxide::handler![Command::Cancel].endpoint(cancel));
|
|
||||||
|
|
||||||
let message_handler = Update::filter_message()
|
|
||||||
.branch(command_handler)
|
|
||||||
.branch(teloxide::handler![State::ReceiveFullName].endpoint(receive_full_name))
|
|
||||||
.branch(dptree::endpoint(invalid_state));
|
|
||||||
|
|
||||||
let callback_query_handler = Update::filter_callback_query().chain(
|
|
||||||
teloxide::handler![State::ReceiveProductChoice { full_name }]
|
|
||||||
.endpoint(receive_product_selection),
|
|
||||||
);
|
|
||||||
|
|
||||||
Dispatcher::builder(
|
|
||||||
bot,
|
|
||||||
dialogue::enter::<Update, InMemStorage<State>, State, _>()
|
|
||||||
.branch(message_handler)
|
|
||||||
.branch(callback_query_handler),
|
|
||||||
)
|
|
||||||
.dependencies(dptree::deps![InMemStorage::<State>::new()])
|
.dependencies(dptree::deps![InMemStorage::<State>::new()])
|
||||||
.build()
|
.build()
|
||||||
.setup_ctrlc_handler()
|
.setup_ctrlc_handler()
|
||||||
|
@ -84,6 +64,30 @@ async fn main() {
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn schema() -> UpdateHandler<Box<dyn std::error::Error + Send + Sync + 'static>> {
|
||||||
|
let command_handler = teloxide::filter_command::<Command, _>()
|
||||||
|
.branch(
|
||||||
|
dptree::case![State::Start]
|
||||||
|
.branch(dptree::case![Command::Help].endpoint(help))
|
||||||
|
.branch(dptree::case![Command::Start].endpoint(start)),
|
||||||
|
)
|
||||||
|
.branch(dptree::case![Command::Cancel].endpoint(cancel));
|
||||||
|
|
||||||
|
let message_handler = Update::filter_message()
|
||||||
|
.branch(command_handler)
|
||||||
|
.branch(dptree::case![State::ReceiveFullName].endpoint(receive_full_name))
|
||||||
|
.branch(dptree::endpoint(invalid_state));
|
||||||
|
|
||||||
|
let callback_query_handler = Update::filter_callback_query().chain(
|
||||||
|
dptree::case![State::ReceiveProductChoice { full_name }]
|
||||||
|
.endpoint(receive_product_selection),
|
||||||
|
);
|
||||||
|
|
||||||
|
dialogue::enter::<Update, InMemStorage<State>, State, _>()
|
||||||
|
.branch(message_handler)
|
||||||
|
.branch(callback_query_handler)
|
||||||
|
}
|
||||||
|
|
||||||
async fn start(bot: AutoSend<Bot>, msg: Message, dialogue: MyDialogue) -> HandlerResult {
|
async fn start(bot: AutoSend<Bot>, msg: Message, dialogue: MyDialogue) -> HandlerResult {
|
||||||
bot.send_message(msg.chat.id, "Let's start! What's your full name?").await?;
|
bot.send_message(msg.chat.id, "Let's start! What's your full name?").await?;
|
||||||
dialogue.update(State::ReceiveFullName).await?;
|
dialogue.update(State::ReceiveFullName).await?;
|
||||||
|
|
|
@ -218,168 +218,3 @@ where
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Filters an enumeration, passing its payload forwards.
|
|
||||||
///
|
|
||||||
/// This macro expands to a [`dptree::Handler`] that acts on your enumeration
|
|
||||||
/// type: if the enumeration is of a certain variant, the execution continues;
|
|
||||||
/// otherwise, `dptree` will try the next branch. This is very useful for
|
|
||||||
/// dialogue FSM transitions and Telegram command filtering; for a complete
|
|
||||||
/// example, please see [`examples/purchase.rs`].
|
|
||||||
///
|
|
||||||
/// Variants can take the following forms:
|
|
||||||
///
|
|
||||||
/// - `State::MyVariant` for empty variants;
|
|
||||||
/// - `State::MyVariant(param1, ..., paramN)` for function-like variants;
|
|
||||||
/// - `State::MyVariant { param1, ..., paramN }` for `struct`-like variants.
|
|
||||||
///
|
|
||||||
/// In the first case, this macro results in a simple [`dptree::filter`]; in the
|
|
||||||
/// second and third cases, this macro results in [`dptree::filter_map`] that
|
|
||||||
/// passes the payload of `MyVariant` to the next handler if the match occurs.
|
|
||||||
/// (This next handler can be an endpoint or a more complex one.) The payload
|
|
||||||
/// format depend on the form of `MyVariant`:
|
|
||||||
///
|
|
||||||
/// - For `State::MyVariant(param)` and `State::MyVariant { param }`, the
|
|
||||||
/// payload is `param`.
|
|
||||||
/// - For `State::MyVariant(param,)` and `State::MyVariant { param, }`, the
|
|
||||||
/// payload is `(param,)`.
|
|
||||||
/// - For `State::MyVariant(param1, ..., paramN)` and `State::MyVariant {
|
|
||||||
/// param1, ..., paramN }`, the payload is `(param1, ..., paramN)` (where
|
|
||||||
/// `N`>1).
|
|
||||||
///
|
|
||||||
/// ## Dependency requirements
|
|
||||||
///
|
|
||||||
/// - Your dialogue state enumeration `State`.
|
|
||||||
///
|
|
||||||
/// [`examples/purchase.rs`]: https://github.com/teloxide/teloxide/blob/master/examples/purchase.rs
|
|
||||||
#[macro_export]
|
|
||||||
macro_rules! handler {
|
|
||||||
($($variant:ident)::+) => {
|
|
||||||
$crate::dptree::filter(|state| matches!(state, $($variant)::+))
|
|
||||||
};
|
|
||||||
($($variant:ident)::+ ($param:ident)) => {
|
|
||||||
$crate::dptree::filter_map(|state| match state {
|
|
||||||
$($variant)::+($param) => Some($param),
|
|
||||||
_ => None,
|
|
||||||
})
|
|
||||||
};
|
|
||||||
($($variant:ident)::+ ($($param:ident),+ $(,)?)) => {
|
|
||||||
$crate::dptree::filter_map(|state| match state {
|
|
||||||
$($variant)::+($($param),+) => Some(($($param),+ ,)),
|
|
||||||
_ => None,
|
|
||||||
})
|
|
||||||
};
|
|
||||||
($($variant:ident)::+ {$param:ident}) => {
|
|
||||||
$crate::dptree::filter_map(|state| match state {
|
|
||||||
$($variant)::+{$param} => Some($param),
|
|
||||||
_ => None,
|
|
||||||
})
|
|
||||||
};
|
|
||||||
($($variant:ident)::+ {$($param:ident),+ $(,)?}) => {
|
|
||||||
$crate::dptree::filter_map(|state| match state {
|
|
||||||
$($variant)::+ { $($param),+ } => Some(($($param),+ ,)),
|
|
||||||
_ => None,
|
|
||||||
})
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use std::ops::ControlFlow;
|
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
|
||||||
enum State {
|
|
||||||
A,
|
|
||||||
B(i32),
|
|
||||||
C(i32, &'static str),
|
|
||||||
D { foo: i32 },
|
|
||||||
E { foo: i32, bar: &'static str },
|
|
||||||
Other,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn handler_empty_variant() {
|
|
||||||
let input = State::A;
|
|
||||||
let h: dptree::Handler<_, _> = handler![State::A].endpoint(|| async move { 123 });
|
|
||||||
|
|
||||||
assert_eq!(h.dispatch(dptree::deps![input]).await, ControlFlow::Break(123));
|
|
||||||
assert!(matches!(h.dispatch(dptree::deps![State::Other]).await, ControlFlow::Continue(_)));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn handler_single_fn_variant() {
|
|
||||||
let input = State::B(42);
|
|
||||||
let h: dptree::Handler<_, _> = handler![State::B(x)].endpoint(|x: i32| async move {
|
|
||||||
assert_eq!(x, 42);
|
|
||||||
123
|
|
||||||
});
|
|
||||||
|
|
||||||
assert_eq!(h.dispatch(dptree::deps![input]).await, ControlFlow::Break(123));
|
|
||||||
assert!(matches!(h.dispatch(dptree::deps![State::Other]).await, ControlFlow::Continue(_)));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn handler_single_fn_variant_trailing_comma() {
|
|
||||||
let input = State::B(42);
|
|
||||||
let h: dptree::Handler<_, _> = handler![State::B(x,)].endpoint(|(x,): (i32,)| async move {
|
|
||||||
assert_eq!(x, 42);
|
|
||||||
123
|
|
||||||
});
|
|
||||||
|
|
||||||
assert_eq!(h.dispatch(dptree::deps![input]).await, ControlFlow::Break(123));
|
|
||||||
assert!(matches!(h.dispatch(dptree::deps![State::Other]).await, ControlFlow::Continue(_)));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn handler_fn_variant() {
|
|
||||||
let input = State::C(42, "abc");
|
|
||||||
let h: dptree::Handler<_, _> =
|
|
||||||
handler![State::C(x, y)].endpoint(|(x, str): (i32, &'static str)| async move {
|
|
||||||
assert_eq!(x, 42);
|
|
||||||
assert_eq!(str, "abc");
|
|
||||||
123
|
|
||||||
});
|
|
||||||
|
|
||||||
assert_eq!(h.dispatch(dptree::deps![input]).await, ControlFlow::Break(123));
|
|
||||||
assert!(matches!(h.dispatch(dptree::deps![State::Other]).await, ControlFlow::Continue(_)));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn handler_single_struct_variant() {
|
|
||||||
let input = State::D { foo: 42 };
|
|
||||||
let h: dptree::Handler<_, _> = handler![State::D { foo }].endpoint(|x: i32| async move {
|
|
||||||
assert_eq!(x, 42);
|
|
||||||
123
|
|
||||||
});
|
|
||||||
|
|
||||||
assert_eq!(h.dispatch(dptree::deps![input]).await, ControlFlow::Break(123));
|
|
||||||
assert!(matches!(h.dispatch(dptree::deps![State::Other]).await, ControlFlow::Continue(_)));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn handler_single_struct_variant_trailing_comma() {
|
|
||||||
let input = State::D { foo: 42 };
|
|
||||||
#[rustfmt::skip] // rustfmt removes the trailing comma from `State::D { foo, }`, but it plays a vital role in this test.
|
|
||||||
let h: dptree::Handler<_, _> = handler![State::D { foo, }].endpoint(|(x,): (i32,)| async move {
|
|
||||||
assert_eq!(x, 42);
|
|
||||||
123
|
|
||||||
});
|
|
||||||
|
|
||||||
assert_eq!(h.dispatch(dptree::deps![input]).await, ControlFlow::Break(123));
|
|
||||||
assert!(matches!(h.dispatch(dptree::deps![State::Other]).await, ControlFlow::Continue(_)));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn handler_struct_variant() {
|
|
||||||
let input = State::E { foo: 42, bar: "abc" };
|
|
||||||
let h: dptree::Handler<_, _> =
|
|
||||||
handler![State::E { foo, bar }].endpoint(|(x, str): (i32, &'static str)| async move {
|
|
||||||
assert_eq!(x, 42);
|
|
||||||
assert_eq!(str, "abc");
|
|
||||||
123
|
|
||||||
});
|
|
||||||
|
|
||||||
assert_eq!(h.dispatch(dptree::deps![input]).await, ControlFlow::Break(123));
|
|
||||||
assert!(matches!(h.dispatch(dptree::deps![State::Other]).await, ControlFlow::Continue(_)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -80,7 +80,7 @@ pub use teloxide_core::*;
|
||||||
pub use teloxide_macros as macros;
|
pub use teloxide_macros as macros;
|
||||||
|
|
||||||
pub use dispatching::filter_command;
|
pub use dispatching::filter_command;
|
||||||
pub use dptree;
|
pub use dptree::{self, case as handler};
|
||||||
|
|
||||||
#[cfg(all(feature = "nightly", doctest))]
|
#[cfg(all(feature = "nightly", doctest))]
|
||||||
#[cfg_attr(feature = "nightly", cfg_attr(feature = "nightly", doc = include_str!("../README.md")))]
|
#[cfg_attr(feature = "nightly", cfg_attr(feature = "nightly", doc = include_str!("../README.md")))]
|
||||||
|
|
Loading…
Reference in a new issue