Update explanation of dialogues (README.md)

This commit is contained in:
Temirkhan Myrzamadi 2020-07-08 15:43:54 +06:00
parent 53625c9441
commit ab69b334a1

View file

@ -187,20 +187,18 @@ async fn main() {
} }
``` ```
<div align="center">
<kbd>
<img src=https://github.com/teloxide/teloxide/raw/master/media/SIMPLE_COMMANDS_BOT.png width="500"/>
</kbd>
<br/><br/>
</div>
### Dialogues ### Dialogues
Wanna see more? This is how dialogues management is made in teloxide. A dialogue is described by an enumeration, where each variant is one of possible dialogue's states. There are also _transition functions_, which turn a dialogue from one state to another, thereby forming an [FSM].
[FSM]: https://en.wikipedia.org/wiki/Finite-state_machine
States and transition functions are placed into separated modules. For example:
([dialogue_bot/src/states.rs](https://github.com/teloxide/teloxide/blob/master/examples/dialogue_bot/src/states.rs)) ([dialogue_bot/src/states.rs](https://github.com/teloxide/teloxide/blob/master/examples/dialogue_bot/src/states.rs))
```rust ```rust
// Imports are omitted... // Imports are omitted...
#[derive(Default)]
pub struct StartState; pub struct StartState;
pub struct ReceiveFullNameState { pub struct ReceiveFullNameState {
@ -234,77 +232,83 @@ up!(
ReceiveFavouriteMusicState + [favourite_music: FavouriteMusic] -> ExitState, ReceiveFavouriteMusicState + [favourite_music: FavouriteMusic] -> ExitState,
); );
pub type Dialogue = Coprod!( #[derive(SmartDefault, From)]
StartState, pub enum Dialogue {
ReceiveFullNameState, #[default]
ReceiveAgeState, Start(StartState),
ReceiveFavouriteMusicState, ReceiveFullName(ReceiveFullNameState),
); ReceiveAge(ReceiveAgeState),
ReceiveFavouriteMusic(ReceiveFavouriteMusicState),
}
``` ```
The [`wrap_dialogue!`](https://docs.rs/teloxide/latest/teloxide/macro.wrap_dialogue.html) macro generates a new-type of `Dialogue` with a default implementation. The handy `up!` macro automatically generates functions that complete one state to another by appending a field.
([dialogue_bot/src/transitions.rs](https://github.com/teloxide/teloxide/blob/master/examples/dialogue_bot/src/transitions.rs)) ([dialogue_bot/src/transitions.rs](https://github.com/teloxide/teloxide/blob/master/examples/dialogue_bot/src/transitions.rs))
```rust ```rust
// Imports are omitted... // Imports are omitted...
pub type In<State> = TransitionIn<State, std::convert::Infallible>; pub type Cx = UpdateWithCx<Message>;
pub type Out = TransitionOut<Wrapper>; pub type Out = TransitionOut<Dialogue>;
pub async fn start(cx: In<StartState>) -> Out {
let (cx, dialogue) = cx.unpack();
async fn start(cx: Cx, state: StartState) -> Out {
cx.answer_str("Let's start! First, what's your full name?").await?; cx.answer_str("Let's start! First, what's your full name?").await?;
next(dialogue.up()) next(state.up())
} }
pub async fn receive_full_name(cx: In<ReceiveFullNameState>) -> Out { async fn receive_full_name(cx: Cx, state: ReceiveFullNameState) -> Out {
let (cx, dialogue) = cx.unpack();
match cx.update.text_owned() { match cx.update.text_owned() {
Some(full_name) => { Some(full_name) => {
cx.answer_str("What a wonderful name! Your age?").await?; cx.answer_str("What a wonderful name! Your age?").await?;
next(dialogue.up(full_name)) next(state.up(full_name))
} }
_ => { _ => {
cx.answer_str("Please, enter a text message!").await?; cx.answer_str("Please, enter a text message!").await?;
next(dialogue) next(state)
} }
} }
} }
pub async fn receive_age(cx: In<ReceiveAgeState>) -> Out { async fn receive_age(cx: Cx, state: ReceiveAgeState) -> Out {
let (cx, dialogue) = cx.unpack();
match cx.update.text().map(str::parse) { match cx.update.text().map(str::parse) {
Some(Ok(age)) => { Some(Ok(age)) => {
cx.answer("Good. Now choose your favourite music:") cx.answer("Good. Now choose your favourite music:")
.reply_markup(FavouriteMusic::markup()) .reply_markup(FavouriteMusic::markup())
.send() .send()
.await?; .await?;
next(dialogue.up(age)) next(state.up(age))
} }
_ => { _ => {
cx.answer_str("Please, enter a number!").await?; cx.answer_str("Please, enter a number!").await?;
next(dialogue) next(state)
} }
} }
} }
pub async fn receive_favourite_music( async fn receive_favourite_music(
cx: In<ReceiveFavouriteMusicState>, cx: Cx,
state: ReceiveFavouriteMusicState,
) -> Out { ) -> Out {
let (cx, dialogue) = cx.unpack();
match cx.update.text().map(str::parse) { match cx.update.text().map(str::parse) {
Some(Ok(favourite_music)) => { Some(Ok(favourite_music)) => {
cx.answer_str(format!("Fine. {}", dialogue.up(favourite_music))) cx.answer_str(format!("Fine. {}", state.up(favourite_music)))
.await?; .await?;
exit() exit()
} }
_ => { _ => {
cx.answer_str("Please, enter from the keyboard!").await?; cx.answer_str("Please, enter from the keyboard!").await?;
next(dialogue) next(state)
}
}
}
pub async fn dispatch(cx: Cx, dialogue: Dialogue) -> Out {
match dialogue {
Dialogue::Start(state) => start(cx, state).await,
Dialogue::ReceiveFullName(state) => receive_full_name(cx, state).await,
Dialogue::ReceiveAge(state) => receive_age(cx, state).await,
Dialogue::ReceiveFavouriteMusic(state) => {
receive_favourite_music(cx, state).await
} }
} }
} }
@ -335,6 +339,7 @@ impl FavouriteMusic {
``` ```
([dialogue_bot/src/main.rs](https://github.com/teloxide/teloxide/blob/master/examples/dialogue_bot/src/main.rs)) ([dialogue_bot/src/main.rs](https://github.com/teloxide/teloxide/blob/master/examples/dialogue_bot/src/main.rs))
```rust ```rust
// Imports are omitted... // Imports are omitted...
@ -347,18 +352,14 @@ async fn main() {
let bot = Bot::from_env(); let bot = Bot::from_env();
Dispatcher::new(bot) Dispatcher::new(bot)
.messages_handler(DialogueDispatcher::new(|cx| async move { .messages_handler(DialogueDispatcher::new(
let DialogueWithCx { cx, dialogue } = cx; |input: TransitionIn<Dialogue, Infallible>| async move {
// Unwrap without panic because of std::convert::Infallible.
// Unwrap without panic because of std::convert::Infallible. dispatch(input.cx, input.dialogue.unwrap())
let Wrapper(dialogue) = dialogue.unwrap(); .await
.expect("Something wrong with the bot!")
dispatch!( },
[cx, dialogue] -> ))
[start, receive_full_name, receive_age, receive_favourite_music]
)
.expect("Something wrong with the bot!")
}, || Dialogue::inject(StartState)))
.dispatch() .dispatch()
.await; .await;
} }