added DialogueState macro

This commit is contained in:
p0lunin 2021-12-29 13:33:51 +02:00
parent 6c0f1b9fc4
commit 72dfef4963
9 changed files with 104 additions and 66 deletions

View file

@ -73,8 +73,8 @@ full = [
[dependencies]
teloxide-core = { version = "0.3.3", 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 = { version = "1.0", features = ["derive"] }

View file

@ -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):
([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
// 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>
<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
// Imports are omitted...
@ -219,7 +219,7 @@ async fn start(
<details>
<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
// Imports are omitted...
@ -242,7 +242,7 @@ async fn receive_full_name(
<details>
<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
// Imports are omitted...
@ -275,7 +275,7 @@ async fn receive_age_state(
<details>
<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
// Imports are omitted...

View file

@ -15,62 +15,36 @@
// ```
use teloxide::{
dispatching2::dialogue::{serializer::Json, SqliteStorage},
macros::DialogueState,
prelude::*,
};
use teloxide::dispatching2::HandlerFactory;
// FIXME: naming
type MyBot = AutoSend<Bot>;
type Store = SqliteStorage<Json>;
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 {
#[handler(handle_start)]
Start,
#[handler(handle_receive_full_name)]
ReceiveFullName,
#[handler(handle_receive_age)]
ReceiveAge(String),
#[handler(handle_receive_location)]
ReceiveLocation(ReceiveLocation),
}
#[derive(Clone, serde::Serialize, serde::Deserialize)]
pub struct ReceiveLocation { full_name: String, age: u8 }
impl HandlerFactory for State {
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)
)
}
pub struct ReceiveLocation {
full_name: String,
age: u8,
}
impl Default for State {
@ -89,9 +63,7 @@ async fn main() {
Dispatcher::new(bot)
.dependencies(dptree::deps![storage])
.messages_handler(|h| {
h.add_dialogue::<Message, Store, State>().dispatch_by::<State>()
})
.messages_handler(|h| h.add_dialogue::<Message, Store, State>().dispatch_by::<State>())
.dispatch()
.await;
}
@ -134,10 +106,11 @@ async fn handle_receive_location(
bot: MyBot,
mes: Message,
dialogue: BotDialogue,
state: ReceiveLocation
state: ReceiveLocation,
) -> anyhow::Result<()> {
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?;
dialogue.exit().await?;
Ok(())

View file

@ -1,9 +1,5 @@
use crate::{types::Message, utils::command::BotCommand};
use dptree::{
Handler,
};
use crate::dispatching2::HandlerFactory;
use dptree::di::DependencyMap;
use crate::{dispatching2::HandlerFactory, types::Message, utils::command::BotCommand};
use dptree::{di::DependencyMap, Handler};
pub trait HandlerExt<Output> {
fn add_command<C>(self, bot_name: String) -> Self
@ -11,7 +7,8 @@ pub trait HandlerExt<Output> {
C: BotCommand + Send + Sync + 'static;
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>
@ -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())
}
}

View file

@ -1,8 +1,7 @@
use dptree::di::DependencyMap;
use dptree::Handler;
use dptree::{di::DependencyMap, Handler};
pub trait HandlerFactory {
type Out;
fn handler() -> Handler<'static, DependencyMap, Self::Out>;
}
}

View file

@ -6,5 +6,5 @@ mod handler_ext;
mod handler_factory;
pub use dispatcher::Dispatcher;
pub use handler_factory::HandlerFactory;
pub use handler_ext::HandlerExt;
pub use handler_factory::HandlerFactory;

View file

@ -91,6 +91,9 @@ pub use teloxide_core::*;
#[cfg_attr(all(docsrs, feature = "nightly"), doc(cfg(feature = "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(feature = "macros")]
pub use teloxide_macros::teloxide;

View file

@ -17,8 +17,7 @@ pub use crate::dispatching::{
#[cfg(feature = "new-dispatching")]
pub use crate::dispatching2::{
dialogue::{Dialogue, DialogueHandlerExt as _},
HandlerExt as _,
Dispatcher,
Dispatcher, HandlerExt as _,
};
#[cfg_attr(all(docsrs, feature = "nightly"), doc(cfg(feature = "macros")))]

64
tests/bot_dialogue.rs Normal file
View 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(())
}
}