diff --git a/README.md b/README.md index 21449ef2..01d6edbf 100644 --- a/README.md +++ b/README.md @@ -187,20 +187,18 @@ async fn main() { } ``` -
- - - -

-
- ### 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)) ```rust // Imports are omitted... +#[derive(Default)] pub struct StartState; pub struct ReceiveFullNameState { @@ -234,77 +232,83 @@ up!( ReceiveFavouriteMusicState + [favourite_music: FavouriteMusic] -> ExitState, ); -pub type Dialogue = Coprod!( - StartState, - ReceiveFullNameState, - ReceiveAgeState, - ReceiveFavouriteMusicState, -); +#[derive(SmartDefault, From)] +pub enum Dialogue { + #[default] + Start(StartState), + 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)) ```rust // Imports are omitted... -pub type In = TransitionIn; -pub type Out = TransitionOut; - -pub async fn start(cx: In) -> Out { - let (cx, dialogue) = cx.unpack(); +pub type Cx = UpdateWithCx; +pub type Out = TransitionOut; +async fn start(cx: Cx, state: StartState) -> Out { 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) -> Out { - let (cx, dialogue) = cx.unpack(); - +async fn receive_full_name(cx: Cx, state: ReceiveFullNameState) -> Out { match cx.update.text_owned() { Some(full_name) => { 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?; - next(dialogue) + next(state) } } } -pub async fn receive_age(cx: In) -> Out { - let (cx, dialogue) = cx.unpack(); - +async fn receive_age(cx: Cx, state: ReceiveAgeState) -> Out { match cx.update.text().map(str::parse) { Some(Ok(age)) => { cx.answer("Good. Now choose your favourite music:") .reply_markup(FavouriteMusic::markup()) .send() .await?; - next(dialogue.up(age)) + next(state.up(age)) } _ => { cx.answer_str("Please, enter a number!").await?; - next(dialogue) + next(state) } } } -pub async fn receive_favourite_music( - cx: In, +async fn receive_favourite_music( + cx: Cx, + state: ReceiveFavouriteMusicState, ) -> Out { - let (cx, dialogue) = cx.unpack(); - match cx.update.text().map(str::parse) { Some(Ok(favourite_music)) => { - cx.answer_str(format!("Fine. {}", dialogue.up(favourite_music))) + cx.answer_str(format!("Fine. {}", state.up(favourite_music))) .await?; exit() } _ => { 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)) ```rust // Imports are omitted... @@ -347,18 +352,14 @@ async fn main() { let bot = Bot::from_env(); Dispatcher::new(bot) - .messages_handler(DialogueDispatcher::new(|cx| async move { - let DialogueWithCx { cx, dialogue } = cx; - - // Unwrap without panic because of std::convert::Infallible. - let Wrapper(dialogue) = dialogue.unwrap(); - - dispatch!( - [cx, dialogue] -> - [start, receive_full_name, receive_age, receive_favourite_music] - ) - .expect("Something wrong with the bot!") - }, || Dialogue::inject(StartState))) + .messages_handler(DialogueDispatcher::new( + |input: TransitionIn| async move { + // Unwrap without panic because of std::convert::Infallible. + dispatch(input.cx, input.dialogue.unwrap()) + .await + .expect("Something wrong with the bot!") + }, + )) .dispatch() .await; }