diff --git a/CHANGELOG.md b/CHANGELOG.md index bf81ebdf..b9cfdb74 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - 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 diff --git a/Cargo.toml b/Cargo.toml index ab6c0f3e..b77e52ea 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -62,7 +62,7 @@ teloxide-macros = { version = "0.6.1", optional = true } serde_json = "1.0" serde = { version = "1.0", features = ["derive"] } -dptree = "0.2.0" +dptree = "0.2.1" tokio = { version = "1.8", features = ["fs"] } tokio-util = "0.6" diff --git a/README.md b/README.md index e63cd1ab..d086f872 100644 --- a/README.md +++ b/README.md @@ -221,12 +221,11 @@ async fn main() { bot, Update::filter_message() .enter_dialogue::, State>() - .branch(teloxide::handler![State::Start].endpoint(start)) - .branch(teloxide::handler![State::ReceiveFullName].endpoint(receive_full_name)) - .branch(teloxide::handler![State::ReceiveAge { full_name }].endpoint(receive_age)) + .branch(dptree::case![State::Start].endpoint(start)) + .branch(dptree::case![State::ReceiveFullName].endpoint(receive_full_name)) + .branch(dptree::case![State::ReceiveAge { full_name }].endpoint(receive_age)) .branch( - teloxide::handler![State::ReceiveLocation { full_name, age }] - .endpoint(receive_location), + dptree::case![State::ReceiveLocation { full_name, age }].endpoint(receive_location), ), ) .dependencies(dptree::deps![InMemStorage::::new()]) diff --git a/examples/db_remember.rs b/examples/db_remember.rs index 57f3007b..dfac7bb0 100644 --- a/examples/db_remember.rs +++ b/examples/db_remember.rs @@ -50,9 +50,9 @@ async fn main() { let handler = Update::filter_message() .enter_dialogue::, State>() - .branch(teloxide::handler![State::Start].endpoint(start)) + .branch(dptree::case![State::Start].endpoint(start)) .branch( - teloxide::handler![State::GotNumber(n)] + dptree::case![State::GotNumber(n)] .branch(dptree::entry().filter_command::().endpoint(got_number)) .branch(dptree::endpoint(invalid_command)), ); diff --git a/examples/dialogue.rs b/examples/dialogue.rs index 1c75e630..3156b924 100644 --- a/examples/dialogue.rs +++ b/examples/dialogue.rs @@ -43,12 +43,11 @@ async fn main() { bot, Update::filter_message() .enter_dialogue::, State>() - .branch(teloxide::handler![State::Start].endpoint(start)) - .branch(teloxide::handler![State::ReceiveFullName].endpoint(receive_full_name)) - .branch(teloxide::handler![State::ReceiveAge { full_name }].endpoint(receive_age)) + .branch(dptree::case![State::Start].endpoint(start)) + .branch(dptree::case![State::ReceiveFullName].endpoint(receive_full_name)) + .branch(dptree::case![State::ReceiveAge { full_name }].endpoint(receive_age)) .branch( - teloxide::handler![State::ReceiveLocation { full_name, age }] - .endpoint(receive_location), + dptree::case![State::ReceiveLocation { full_name, age }].endpoint(receive_location), ), ) .dependencies(dptree::deps![InMemStorage::::new()]) diff --git a/examples/purchase.rs b/examples/purchase.rs index c547c00a..e8333380 100644 --- a/examples/purchase.rs +++ b/examples/purchase.rs @@ -13,7 +13,10 @@ // ``` use teloxide::{ - dispatching::dialogue::{self, InMemStorage}, + dispatching::{ + dialogue::{self, InMemStorage}, + UpdateHandler, + }, prelude::*, types::{InlineKeyboardButton, InlineKeyboardMarkup}, utils::command::BotCommands, @@ -53,35 +56,36 @@ async fn main() { let bot = Bot::from_env().auto_send(); + Dispatcher::builder(bot, schema()) + .dependencies(dptree::deps![InMemStorage::::new()]) + .build() + .setup_ctrlc_handler() + .dispatch() + .await; +} + +fn schema() -> UpdateHandler> { let command_handler = teloxide::filter_command::() .branch( - teloxide::handler![State::Start] - .branch(teloxide::handler![Command::Help].endpoint(help)) - .branch(teloxide::handler![Command::Start].endpoint(start)), + dptree::case![State::Start] + .branch(dptree::case![Command::Help].endpoint(help)) + .branch(dptree::case![Command::Start].endpoint(start)), ) - .branch(teloxide::handler![Command::Cancel].endpoint(cancel)); + .branch(dptree::case![Command::Cancel].endpoint(cancel)); let message_handler = Update::filter_message() .branch(command_handler) - .branch(teloxide::handler![State::ReceiveFullName].endpoint(receive_full_name)) + .branch(dptree::case![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 }] + dptree::case![State::ReceiveProductChoice { full_name }] .endpoint(receive_product_selection), ); - Dispatcher::builder( - bot, - dialogue::enter::, State, _>() - .branch(message_handler) - .branch(callback_query_handler), - ) - .dependencies(dptree::deps![InMemStorage::::new()]) - .build() - .setup_ctrlc_handler() - .dispatch() - .await; + dialogue::enter::, State, _>() + .branch(message_handler) + .branch(callback_query_handler) } async fn start(bot: AutoSend, msg: Message, dialogue: MyDialogue) -> HandlerResult { diff --git a/src/dispatching/dialogue/mod.rs b/src/dispatching/dialogue/mod.rs index dc68ff40..a6661f1e 100644 --- a/src/dispatching/dialogue/mod.rs +++ b/src/dispatching/dialogue/mod.rs @@ -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(_))); - } -} diff --git a/src/lib.rs b/src/lib.rs index 4c2ba9d8..f25e81a9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -80,7 +80,7 @@ pub use teloxide_core::*; pub use teloxide_macros as macros; pub use dispatching::filter_command; -pub use dptree; +pub use dptree::{self, case as handler}; #[cfg(all(feature = "nightly", doctest))] #[cfg_attr(feature = "nightly", cfg_attr(feature = "nightly", doc = include_str!("../README.md")))]