mirror of
https://github.com/teloxide/teloxide.git
synced 2025-03-24 23:57:38 +01:00
added DialogueState macro
This commit is contained in:
parent
6c0f1b9fc4
commit
72dfef4963
9 changed files with 104 additions and 66 deletions
|
@ -73,8 +73,8 @@ full = [
|
||||||
[dependencies]
|
[dependencies]
|
||||||
teloxide-core = { version = "0.3.3", default-features = false }
|
teloxide-core = { version = "0.3.3", default-features = false }
|
||||||
#teloxide-core = { git = "https://github.com/teloxide/teloxide-core.git", rev = "...", default-features = false }
|
#teloxide-core = { git = "https://github.com/teloxide/teloxide-core.git", rev = "...", default-features = false }
|
||||||
teloxide-macros = { version = "0.4", optional = true }
|
#teloxide-macros = { version = "0.4", optional = true }
|
||||||
|
teloxide-macros = { path = "../teloxide-macros", optional = true }
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
|
|
||||||
|
|
10
README.md
10
README.md
|
@ -173,7 +173,7 @@ A dialogue is described by an enumeration where each variant is one of possible
|
||||||
|
|
||||||
Below is a bot that asks you three questions and then sends the answers back to you. First, let's start with an enumeration (a collection of our dialogue's states):
|
Below is a bot that asks you three questions and then sends the answers back to you. First, let's start with an enumeration (a collection of our dialogue's states):
|
||||||
|
|
||||||
([dialogue_bot/src/dialogue/mod.rs](./examples/dialogue_bot/src/dialogue/mod.rs))
|
([dialogue_bot/src/dialogue/mod.rs](examples/dialogue_bot/src/state/mod.rs))
|
||||||
```rust,ignore
|
```rust,ignore
|
||||||
// Imports are omitted...
|
// Imports are omitted...
|
||||||
|
|
||||||
|
@ -197,7 +197,7 @@ When a user sends a message to our bot and such a dialogue does not exist yet, a
|
||||||
<details>
|
<details>
|
||||||
<summary>Dialogue::Start</summary>
|
<summary>Dialogue::Start</summary>
|
||||||
|
|
||||||
([dialogue_bot/src/dialogue/states/start.rs](./examples/dialogue_bot/src/dialogue/states/start.rs))
|
([dialogue_bot/src/dialogue/states/start.rs](examples/dialogue_bot/src/state/states/start.rs))
|
||||||
```rust,ignore
|
```rust,ignore
|
||||||
// Imports are omitted...
|
// Imports are omitted...
|
||||||
|
|
||||||
|
@ -219,7 +219,7 @@ async fn start(
|
||||||
<details>
|
<details>
|
||||||
<summary>Dialogue::ReceiveFullName</summary>
|
<summary>Dialogue::ReceiveFullName</summary>
|
||||||
|
|
||||||
([dialogue_bot/src/dialogue/states/receive_full_name.rs](./examples/dialogue_bot/src/dialogue/states/receive_full_name.rs))
|
([dialogue_bot/src/dialogue/states/receive_full_name.rs](examples/dialogue_bot/src/state/states/receive_full_name.rs))
|
||||||
```rust,ignore
|
```rust,ignore
|
||||||
// Imports are omitted...
|
// Imports are omitted...
|
||||||
|
|
||||||
|
@ -242,7 +242,7 @@ async fn receive_full_name(
|
||||||
<details>
|
<details>
|
||||||
<summary>Dialogue::ReceiveAge</summary>
|
<summary>Dialogue::ReceiveAge</summary>
|
||||||
|
|
||||||
([dialogue_bot/src/dialogue/states/receive_age.rs](./examples/dialogue_bot/src/dialogue/states/receive_age.rs))
|
([dialogue_bot/src/dialogue/states/receive_age.rs](examples/dialogue_bot/src/state/states/receive_age.rs))
|
||||||
```rust,ignore
|
```rust,ignore
|
||||||
// Imports are omitted...
|
// Imports are omitted...
|
||||||
|
|
||||||
|
@ -275,7 +275,7 @@ async fn receive_age_state(
|
||||||
<details>
|
<details>
|
||||||
<summary>Dialogue::ReceiveLocation</summary>
|
<summary>Dialogue::ReceiveLocation</summary>
|
||||||
|
|
||||||
([dialogue_bot/src/dialogue/states/receive_location.rs](./examples/dialogue_bot/src/dialogue/states/receive_location.rs))
|
([dialogue_bot/src/dialogue/states/receive_location.rs](examples/dialogue_bot/src/state/states/receive_location.rs))
|
||||||
```rust,ignore
|
```rust,ignore
|
||||||
// Imports are omitted...
|
// Imports are omitted...
|
||||||
|
|
||||||
|
|
|
@ -15,62 +15,36 @@
|
||||||
// ```
|
// ```
|
||||||
use teloxide::{
|
use teloxide::{
|
||||||
dispatching2::dialogue::{serializer::Json, SqliteStorage},
|
dispatching2::dialogue::{serializer::Json, SqliteStorage},
|
||||||
|
macros::DialogueState,
|
||||||
prelude::*,
|
prelude::*,
|
||||||
};
|
};
|
||||||
use teloxide::dispatching2::HandlerFactory;
|
|
||||||
|
|
||||||
// FIXME: naming
|
// FIXME: naming
|
||||||
type MyBot = AutoSend<Bot>;
|
type MyBot = AutoSend<Bot>;
|
||||||
type Store = SqliteStorage<Json>;
|
type Store = SqliteStorage<Json>;
|
||||||
type BotDialogue = Dialogue<State, Store>;
|
type BotDialogue = Dialogue<State, Store>;
|
||||||
|
|
||||||
#[derive(Clone, serde::Serialize, serde::Deserialize)]
|
#[derive(DialogueState, Clone, serde::Serialize, serde::Deserialize)]
|
||||||
|
#[out(anyhow::Result<()>)]
|
||||||
|
#[store(SqliteStorage<Json>)]
|
||||||
pub enum State {
|
pub enum State {
|
||||||
|
#[handler(handle_start)]
|
||||||
Start,
|
Start,
|
||||||
|
|
||||||
|
#[handler(handle_receive_full_name)]
|
||||||
ReceiveFullName,
|
ReceiveFullName,
|
||||||
|
|
||||||
|
#[handler(handle_receive_age)]
|
||||||
ReceiveAge(String),
|
ReceiveAge(String),
|
||||||
|
|
||||||
|
#[handler(handle_receive_location)]
|
||||||
ReceiveLocation(ReceiveLocation),
|
ReceiveLocation(ReceiveLocation),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, serde::Serialize, serde::Deserialize)]
|
#[derive(Clone, serde::Serialize, serde::Deserialize)]
|
||||||
pub struct ReceiveLocation { full_name: String, age: u8 }
|
pub struct ReceiveLocation {
|
||||||
|
full_name: String,
|
||||||
impl HandlerFactory for State {
|
age: u8,
|
||||||
type Out = anyhow::Result<()>;
|
|
||||||
|
|
||||||
fn handler() -> dptree::Handler<'static, DependencyMap, anyhow::Result<()>> {
|
|
||||||
dptree::entry()
|
|
||||||
.branch(
|
|
||||||
dptree::filter(|dialogue: Dialogue<State, Store>| async move {
|
|
||||||
let state = match dialogue.current_state_or_default().await {
|
|
||||||
Ok(state) => state,
|
|
||||||
Err(_) => return false,
|
|
||||||
};
|
|
||||||
match state { State::Start => true, _ => false }
|
|
||||||
}).endpoint(handle_start)
|
|
||||||
)
|
|
||||||
.branch(
|
|
||||||
dptree::filter(|dialogue: Dialogue<State, Store>| async move {
|
|
||||||
let state = match dialogue.current_state_or_default().await {
|
|
||||||
Ok(state) => state,
|
|
||||||
Err(_) => return false,
|
|
||||||
};
|
|
||||||
match state { State::ReceiveFullName => true, _ => false }
|
|
||||||
}).endpoint(handle_receive_full_name)
|
|
||||||
)
|
|
||||||
.branch(
|
|
||||||
dptree::filter_map(|dialogue: Dialogue<State, Store>| async move {
|
|
||||||
let state = dialogue.current_state_or_default().await.ok()?;
|
|
||||||
match state { State::ReceiveAge(arg) => Some(arg), _ => None }
|
|
||||||
}).endpoint(handle_receive_age)
|
|
||||||
)
|
|
||||||
.branch(
|
|
||||||
dptree::filter_map(|dialogue: Dialogue<State, Store>| async move {
|
|
||||||
let state = dialogue.current_state_or_default().await.ok()?;
|
|
||||||
match state { State::ReceiveLocation(arg) => Some(arg), _ => None }
|
|
||||||
}).endpoint(handle_receive_location)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for State {
|
impl Default for State {
|
||||||
|
@ -89,9 +63,7 @@ async fn main() {
|
||||||
|
|
||||||
Dispatcher::new(bot)
|
Dispatcher::new(bot)
|
||||||
.dependencies(dptree::deps![storage])
|
.dependencies(dptree::deps![storage])
|
||||||
.messages_handler(|h| {
|
.messages_handler(|h| h.add_dialogue::<Message, Store, State>().dispatch_by::<State>())
|
||||||
h.add_dialogue::<Message, Store, State>().dispatch_by::<State>()
|
|
||||||
})
|
|
||||||
.dispatch()
|
.dispatch()
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
@ -134,10 +106,11 @@ async fn handle_receive_location(
|
||||||
bot: MyBot,
|
bot: MyBot,
|
||||||
mes: Message,
|
mes: Message,
|
||||||
dialogue: BotDialogue,
|
dialogue: BotDialogue,
|
||||||
state: ReceiveLocation
|
state: ReceiveLocation,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
let location = mes.text().unwrap();
|
let location = mes.text().unwrap();
|
||||||
let message = format!("Full name: {}\nAge: {}\nLocation: {}", state.full_name, state.age, location);
|
let message =
|
||||||
|
format!("Full name: {}\nAge: {}\nLocation: {}", state.full_name, state.age, location);
|
||||||
bot.send_message(mes.chat_id(), message).await?;
|
bot.send_message(mes.chat_id(), message).await?;
|
||||||
dialogue.exit().await?;
|
dialogue.exit().await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -1,9 +1,5 @@
|
||||||
use crate::{types::Message, utils::command::BotCommand};
|
use crate::{dispatching2::HandlerFactory, types::Message, utils::command::BotCommand};
|
||||||
use dptree::{
|
use dptree::{di::DependencyMap, Handler};
|
||||||
Handler,
|
|
||||||
};
|
|
||||||
use crate::dispatching2::HandlerFactory;
|
|
||||||
use dptree::di::DependencyMap;
|
|
||||||
|
|
||||||
pub trait HandlerExt<Output> {
|
pub trait HandlerExt<Output> {
|
||||||
fn add_command<C>(self, bot_name: String) -> Self
|
fn add_command<C>(self, bot_name: String) -> Self
|
||||||
|
@ -11,7 +7,8 @@ pub trait HandlerExt<Output> {
|
||||||
C: BotCommand + Send + Sync + 'static;
|
C: BotCommand + Send + Sync + 'static;
|
||||||
|
|
||||||
fn dispatch_by<F>(self) -> Self
|
fn dispatch_by<F>(self) -> Self
|
||||||
where F: HandlerFactory<Out = Output>;
|
where
|
||||||
|
F: HandlerFactory<Out = Output>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Output> HandlerExt<Output> for Handler<'static, DependencyMap, Output>
|
impl<Output> HandlerExt<Output> for Handler<'static, DependencyMap, Output>
|
||||||
|
@ -28,7 +25,10 @@ where
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn dispatch_by<F>(self) -> Self where F: HandlerFactory<Out = Output> {
|
fn dispatch_by<F>(self) -> Self
|
||||||
|
where
|
||||||
|
F: HandlerFactory<Out = Output>,
|
||||||
|
{
|
||||||
self.chain(F::handler())
|
self.chain(F::handler())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
use dptree::di::DependencyMap;
|
use dptree::{di::DependencyMap, Handler};
|
||||||
use dptree::Handler;
|
|
||||||
|
|
||||||
pub trait HandlerFactory {
|
pub trait HandlerFactory {
|
||||||
type Out;
|
type Out;
|
||||||
|
|
||||||
fn handler() -> Handler<'static, DependencyMap, Self::Out>;
|
fn handler() -> Handler<'static, DependencyMap, Self::Out>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,5 +6,5 @@ mod handler_ext;
|
||||||
mod handler_factory;
|
mod handler_factory;
|
||||||
|
|
||||||
pub use dispatcher::Dispatcher;
|
pub use dispatcher::Dispatcher;
|
||||||
pub use handler_factory::HandlerFactory;
|
|
||||||
pub use handler_ext::HandlerExt;
|
pub use handler_ext::HandlerExt;
|
||||||
|
pub use handler_factory::HandlerFactory;
|
||||||
|
|
|
@ -91,6 +91,9 @@ pub use teloxide_core::*;
|
||||||
#[cfg_attr(all(docsrs, feature = "nightly"), doc(cfg(feature = "macros")))]
|
#[cfg_attr(all(docsrs, feature = "nightly"), doc(cfg(feature = "macros")))]
|
||||||
pub use teloxide_macros as macros;
|
pub use teloxide_macros as macros;
|
||||||
|
|
||||||
|
#[cfg_attr(all(docsrs, feature = "nightly"), doc(cfg(feature = "new-dispatching")))]
|
||||||
|
#[cfg(feature = "new-dispatching")]
|
||||||
|
pub use dptree;
|
||||||
#[cfg_attr(all(docsrs, feature = "nightly"), doc(cfg(feature = "macros")))]
|
#[cfg_attr(all(docsrs, feature = "nightly"), doc(cfg(feature = "macros")))]
|
||||||
#[cfg(feature = "macros")]
|
#[cfg(feature = "macros")]
|
||||||
pub use teloxide_macros::teloxide;
|
pub use teloxide_macros::teloxide;
|
||||||
|
|
|
@ -17,8 +17,7 @@ pub use crate::dispatching::{
|
||||||
#[cfg(feature = "new-dispatching")]
|
#[cfg(feature = "new-dispatching")]
|
||||||
pub use crate::dispatching2::{
|
pub use crate::dispatching2::{
|
||||||
dialogue::{Dialogue, DialogueHandlerExt as _},
|
dialogue::{Dialogue, DialogueHandlerExt as _},
|
||||||
HandlerExt as _,
|
Dispatcher, HandlerExt as _,
|
||||||
Dispatcher,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg_attr(all(docsrs, feature = "nightly"), doc(cfg(feature = "macros")))]
|
#[cfg_attr(all(docsrs, feature = "nightly"), doc(cfg(feature = "macros")))]
|
||||||
|
|
64
tests/bot_dialogue.rs
Normal file
64
tests/bot_dialogue.rs
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
#[cfg(feature = "macros")]
|
||||||
|
use teloxide::macros::DialogueState;
|
||||||
|
// We put tests here because macro expand in unit tests in the crate was a
|
||||||
|
// failure
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(feature = "macros")]
|
||||||
|
fn compile_test() {
|
||||||
|
#[allow(dead_code)]
|
||||||
|
#[derive(DialogueState, Clone)]
|
||||||
|
#[out(Result<(), teloxide::RequestError>)]
|
||||||
|
#[store(teloxide::dispatching2::dialogue::InMemStorage<State>)]
|
||||||
|
enum State {
|
||||||
|
#[handler(handle_start)]
|
||||||
|
Start,
|
||||||
|
|
||||||
|
#[handler(handle_have_data)]
|
||||||
|
HaveData(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for State {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::Start
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn handle_start() -> Result<(), teloxide::RequestError> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn handle_have_data() -> Result<(), teloxide::RequestError> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(feature = "macros")]
|
||||||
|
fn compile_test_generics() {
|
||||||
|
#[allow(dead_code)]
|
||||||
|
#[derive(DialogueState, Clone)]
|
||||||
|
#[out(Result<(), teloxide::RequestError>)]
|
||||||
|
#[store(teloxide::dispatching2::dialogue::InMemStorage<State<X>>)]
|
||||||
|
enum State<X: Clone + Send + Sync + 'static> {
|
||||||
|
#[handler(handle_start)]
|
||||||
|
Start,
|
||||||
|
|
||||||
|
#[handler(handle_have_data)]
|
||||||
|
HaveData(X),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<X: Clone + Send + Sync + 'static> Default for State<X> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::Start
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn handle_start() -> Result<(), teloxide::RequestError> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn handle_have_data() -> Result<(), teloxide::RequestError> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue