From 707e69fb27038c9863a869a86c3c6d55cda3ce72 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Wed, 8 Jan 2020 03:32:47 +0600 Subject: [PATCH 01/17] Add the first version --- src/dispatching/dispatcher.rs | 90 +++++ src/dispatching/dispatchers/filter.rs | 342 ------------------ src/dispatching/dispatchers/mod.rs | 1 - src/dispatching/error_handlers.rs | 149 -------- src/dispatching/handler.rs | 34 +- src/dispatching/mod.rs | 12 +- src/dispatching/storage/in_mem_storage.rs | 25 ++ src/dispatching/storage/mod.rs | 19 + .../{updaters.rs => update_listeners.rs} | 29 +- 9 files changed, 175 insertions(+), 526 deletions(-) create mode 100644 src/dispatching/dispatcher.rs delete mode 100644 src/dispatching/dispatchers/filter.rs delete mode 100644 src/dispatching/dispatchers/mod.rs delete mode 100644 src/dispatching/error_handlers.rs create mode 100644 src/dispatching/storage/in_mem_storage.rs create mode 100644 src/dispatching/storage/mod.rs rename src/dispatching/{updaters.rs => update_listeners.rs} (86%) diff --git a/src/dispatching/dispatcher.rs b/src/dispatching/dispatcher.rs new file mode 100644 index 00000000..6dd9f42d --- /dev/null +++ b/src/dispatching/dispatcher.rs @@ -0,0 +1,90 @@ +use super::storage::{InMemStorage, Storage}; +use crate::{ + dispatching::{Handler, SessionState}, + types::{ChatKind, Update, UpdateKind}, +}; + +pub struct Dispatcher<'a, S, H> { + storage: Box + 'a>, + handler: H, +} + +#[derive(Debug, Copy, Clone, Eq, Hash, PartialEq)] +pub enum DispatchResult { + Handled, + Unhandled, +} + +#[macro_use] +mod macros { + #[macro_export] + macro_rules! private_chat_id { + ($msg:expr) => { + match &$msg.chat.kind { + ChatKind::Private { .. } => $msg.chat.id, + _ => return DispatchResult::Unhandled, + } + }; + } +} + +impl<'a, S, H> Dispatcher<'a, S, H> +where + S: Default + 'a, + H: Handler, +{ + pub fn new(handler: H) -> Self { + Self { + storage: Box::new(InMemStorage::default()), + handler, + } + } + + pub fn with_storage(handler: H, storage: Stg) -> Self + where + Stg: Storage + 'a, + { + Self { + storage: Box::new(storage), + handler, + } + } + + pub async fn dispatch(&mut self, update: Update) -> DispatchResult { + let chat_id = match &update.kind { + UpdateKind::Message(msg) => private_chat_id!(msg), + UpdateKind::EditedMessage(msg) => private_chat_id!(msg), + _ => return DispatchResult::Unhandled, + }; + + let session = self + .storage + .remove_session(chat_id) + .await + .unwrap_or_default(); + + if let SessionState::Continue(session) = + self.handler.handle(session, update).await + { + if self + .storage + .update_session(chat_id, session) + .await + .is_some() + { + panic!( + "We previously storage.remove_session() so \ + storage.update_session() must return None" + ); + } + } + + DispatchResult::Handled + } +} + +impl<'a, S, H> Drop for Dispatcher<'a, S, H> { + fn drop(&mut self) { + // TODO: run self.storage.save() + } +} diff --git a/src/dispatching/dispatchers/filter.rs b/src/dispatching/dispatchers/filter.rs deleted file mode 100644 index 3495939b..00000000 --- a/src/dispatching/dispatchers/filter.rs +++ /dev/null @@ -1,342 +0,0 @@ -//! A dispatcher based on filters. - -use futures::StreamExt; - -use crate::{ - dispatching::{filters::Filter, ErrorHandler, Handler, Updater}, - types::{ - CallbackQuery, ChosenInlineResult, InlineQuery, Message, Update, - UpdateKind, - }, -}; -use either::Either; - -type FilterWithHandler<'a, T, E> = - (Box + 'a>, Box + 'a>); -type FiltersWithHandlers<'a, T, E> = Vec>; - -/// A dispatcher based on filters. -/// -/// It consists of: -/// 1. [`ErrorHandler`] than handles errors both from [`Updater`] and -/// [`Handler`]. -/// 2. Filters and handlers. -/// -/// First you call [`FilterDispatcher::new(eh)`] to create this dispatcher. `eh` -/// stands for **E**rror **H**andler, you can simply provide a closure that -/// takes [`Either`]: -/// -/// ``` -/// use either::Either; -/// use std::convert::Infallible; -/// use teloxide::{dispatching::FilterDispatcher, RequestError}; -/// -/// let _ = -/// FilterDispatcher::new(|err: Either| async { -/// dbg!(err); -/// }); -/// ``` -/// -/// Or you can do it even simpler by providing the built-in error handler -/// [`Print`]: -/// -/// ``` -/// use std::convert::Infallible; -/// use teloxide::{ -/// dispatching::{error_handlers::Print, FilterDispatcher}, -/// RequestError, -/// }; -/// -/// let _ = FilterDispatcher::<'_, Infallible, _>::new::(Print); -/// ``` -/// -/// And then you register filters and handlers using the methods defined below, -/// and then you call [`.dispatch(updater)`]. Filters and handlers are executed -/// in order of registering. The following flowchart represents how this -/// dispatcher acts: -/// -///
-/// -///
-/// -/// ## Examples -/// -/// The simplest example: -/// ```no_run -/// # async fn run() { -/// use std::convert::Infallible; -/// -/// use teloxide::{ -/// dispatching::{updaters::polling_default, FilterDispatcher}, -/// types::Message, -/// Bot, -/// }; -/// -/// async fn handle_edited_message(mes: Message) -> Result<(), Infallible> { -/// println!("Edited message: {:?}", mes); -/// Ok(()) -/// } -/// -/// let bot = Bot::new("TOKEN"); -/// -/// // Create a dispatcher which handlers can't fail with the -/// // error handler that just ignores all errors (that can't ever happen). -/// let mut dp = FilterDispatcher::::new(|_| async {}) -/// // Add a handler, which handles all messages sent to the bot. -/// .message_handler(true, |mes: Message| async move { -/// println!("New message: {:?}", mes); -/// Ok(()) -/// }) -/// // Add a handler, which handles all messages edited in a chat -/// // with the bot. -/// .edited_message_handler(true, handle_edited_message); -/// -/// // Start dispatching updates using long polling. -/// dp.dispatch(polling_default(&bot)).await; -/// # } -/// ``` -/// -/// [`std::fmt::Debug`]: std::fmt::Debug -/// [`.dispatch(updater)`]: FilterDispatcher::dispatch -/// [`ErrorHandler`]: crate::dispatching::error_handlers::ErrorHandler -/// [`Updater`]: crate::dispatching::updaters::Updater -/// [`Handler`]: crate::dispatching::Handler -/// [`FilterDispatcher::new(eh)`]: FilterDispatcher::new -/// [`Either`]: either::Either -/// [`Print`]: crate::dispatching::error_handlers::Print -pub struct FilterDispatcher<'a, HandlerE> { - message_handlers: FiltersWithHandlers<'a, Message, HandlerE>, - edited_message_handlers: FiltersWithHandlers<'a, Message, HandlerE>, - channel_post_handlers: FiltersWithHandlers<'a, Message, HandlerE>, - edited_channel_post_handlers: FiltersWithHandlers<'a, Message, HandlerE>, - inline_query_handlers: FiltersWithHandlers<'a, InlineQuery, HandlerE>, - chosen_inline_result_handlers: - FiltersWithHandlers<'a, ChosenInlineResult, HandlerE>, - callback_query_handlers: FiltersWithHandlers<'a, CallbackQuery, HandlerE>, -} - -impl<'a, HandlerE> FilterDispatcher<'a, HandlerE> { - pub fn new() -> Self { - FilterDispatcher { - message_handlers: Vec::new(), - edited_message_handlers: Vec::new(), - channel_post_handlers: Vec::new(), - edited_channel_post_handlers: Vec::new(), - inline_query_handlers: Vec::new(), - chosen_inline_result_handlers: Vec::new(), - callback_query_handlers: Vec::new(), - } - } - - pub fn message_handler(mut self, filter: F, handler: H) -> Self - where - F: Filter + 'a, - H: Handler + 'a, - { - self.message_handlers - .push((Box::new(filter), Box::new(handler))); - self - } - - pub fn edited_message_handler(mut self, filter: F, handler: H) -> Self - where - F: Filter + 'a, - H: Handler + 'a, - { - self.edited_message_handlers - .push((Box::new(filter), Box::new(handler))); - self - } - - pub fn channel_post_handler(mut self, filter: F, handler: H) -> Self - where - F: Filter + 'a, - H: Handler + 'a, - { - self.channel_post_handlers - .push((Box::new(filter), Box::new(handler))); - self - } - - pub fn edited_channel_post_handler( - mut self, - filter: F, - handler: H, - ) -> Self - where - F: Filter + 'a, - H: Handler + 'a, - { - self.edited_channel_post_handlers - .push((Box::new(filter), Box::new(handler))); - self - } - - pub fn inline_query_handler(mut self, filter: F, handler: H) -> Self - where - F: Filter + 'a, - H: Handler + 'a, - { - self.inline_query_handlers - .push((Box::new(filter), Box::new(handler))); - self - } - - pub fn chosen_inline_result_handler( - mut self, - filter: F, - handler: H, - ) -> Self - where - F: Filter + 'a, - H: Handler + 'a, - { - self.chosen_inline_result_handlers - .push((Box::new(filter), Box::new(handler))); - self - } - - pub fn callback_query_handler(mut self, filter: F, handler: H) -> Self - where - F: Filter + 'a, - H: Handler + 'a, - { - self.callback_query_handlers - .push((Box::new(filter), Box::new(handler))); - self - } - - pub async fn dispatch(&mut self, update: Update) -> Result<(), HandlerE> { - let Update { kind, id } = update; - - let res = match kind.clone() { - UpdateKind::Message(mes) => { - Self::handle(mes, &self.message_handlers).await - } - UpdateKind::EditedMessage(mes) => { - Self::handle(mes, &self.edited_message_handlers).await - } - UpdateKind::ChannelPost(post) => { - Self::handle(post, &self.channel_post_handlers).await - } - UpdateKind::EditedChannelPost(post) => { - Self::handle(post, &self.edited_channel_post_handlers).await - } - UpdateKind::InlineQuery(query) => { - Self::handle(query, &self.inline_query_handlers).await - } - UpdateKind::ChosenInlineResult(result) => { - Self::handle(result, &self.chosen_inline_result_handlers).await - } - UpdateKind::CallbackQuery(callback) => { - Self::handle(callback, &self.callback_query_handlers).await - } - }; - - log::debug!("dispatched update#{id:?}: {kind:?}", id = id, kind = kind); - res - } - - async fn handle( - update: T, - handlers: &[FilterWithHandler<'a, T, HandlerE>], - ) -> Result<(), HandlerE> - where - T: std::fmt::Debug, - { - for x in handlers { - if x.0.test(&update) { - return x.1.handle(update).await; - } - } - - log::warn!("unhandled update {:?}", update); - Ok(()) - } -} - -#[cfg(test)] -mod tests { - use std::{ - convert::Infallible, - sync::atomic::{AtomicI32, Ordering}, - }; - - use crate::{ - dispatching::{FilterDispatcher, Updater}, - types::{ - Chat, ChatKind, ForwardKind, MediaKind, Message, MessageKind, - Sender, Update, UpdateKind, User, - }, - }; - - #[tokio::test] - async fn first_handler_executes_1_time() { - let counter = &AtomicI32::new(0); - let counter2 = &AtomicI32::new(0); - - let mut dp = FilterDispatcher::::new(|_| async {}) - .message_handler(true, |_mes: Message| async move { - counter.fetch_add(1, Ordering::SeqCst); - Ok::<_, Infallible>(()) - }) - .message_handler(true, |_mes: Message| async move { - counter2.fetch_add(1, Ordering::SeqCst); - Ok::<_, Infallible>(()) - }); - - dp.dispatch(one_message_updater()).await; - - assert_eq!(counter.load(Ordering::SeqCst), 1); - assert_eq!(counter2.load(Ordering::SeqCst), 0); - } - - fn message() -> Message { - Message { - id: 6534, - date: 1_567_898_953, - chat: Chat { - id: 218_485_655, - photo: None, - kind: ChatKind::Private { - type_: (), - first_name: Some("W".to_string()), - last_name: None, - username: Some("WaffleLapkin".to_string()), - }, - }, - kind: MessageKind::Common { - from: Sender::User(User { - id: 457_569_668, - is_bot: true, - first_name: "BT".to_string(), - last_name: None, - username: Some("BloodyTestBot".to_string()), - language_code: None, - }), - forward_kind: ForwardKind::Origin { - reply_to_message: None, - }, - edit_date: None, - media_kind: MediaKind::Text { - text: "text".to_string(), - entities: vec![], - }, - reply_markup: None, - }, - } - } - - fn message_update() -> Update { - Update { - id: 0, - kind: UpdateKind::Message(message()), - } - } - - fn one_message_updater() -> impl Updater { - use futures::{future::ready, stream}; - - stream::once(ready(Ok(message_update()))) - } -} diff --git a/src/dispatching/dispatchers/mod.rs b/src/dispatching/dispatchers/mod.rs deleted file mode 100644 index 34d102d7..00000000 --- a/src/dispatching/dispatchers/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod filter; diff --git a/src/dispatching/error_handlers.rs b/src/dispatching/error_handlers.rs deleted file mode 100644 index acf8abf9..00000000 --- a/src/dispatching/error_handlers.rs +++ /dev/null @@ -1,149 +0,0 @@ -//! Error handlers. - -// Infallible used here instead of `!` to be compatible with rust <1.41. -use std::{convert::Infallible, fmt::Debug, future::Future, pin::Pin}; - -/// An asynchronous handler of an error. -pub trait ErrorHandler { - #[must_use] - fn handle_error<'a>( - &'a self, - error: E, - ) -> Pin + 'a>> - where - E: 'a; -} - -/// A handler that silently ignores all errors. -/// -/// ## Example -/// ``` -/// # #[tokio::main] -/// # async fn main_() { -/// use teloxide::dispatching::error_handlers::{ErrorHandler, Ignore}; -/// -/// Ignore.handle_error(()).await; -/// Ignore.handle_error(404).await; -/// Ignore.handle_error(String::from("error")).await; -/// # } -/// ``` -pub struct Ignore; - -impl ErrorHandler for Ignore { - #[must_use] - fn handle_error<'a>( - &'a self, - _: E, - ) -> Pin + 'a>> - where - E: 'a, - { - Box::pin(async {}) - } -} - -/// An error handler that silently ignores all errors that can never happen -/// (e.g.: [`!`] or [`Infallible`]). -/// -/// ## Examples -/// ``` -/// # #[tokio::main] -/// # async fn main_() { -/// use std::convert::{Infallible, TryInto}; -/// -/// use teloxide::dispatching::error_handlers::{ErrorHandler, IgnoreSafe}; -/// -/// let result: Result = "str".try_into(); -/// match result { -/// Ok(string) => println!("{}", string), -/// Err(inf) => IgnoreSafe.handle_error(inf).await, -/// } -/// -/// IgnoreSafe.handle_error(return).await; // return type of `return` is `!` (aka never) -/// # } -/// ``` -/// -/// ```compile_fail -/// use teloxide::dispatching::dispatchers::filter::error_policy::{ -/// ErrorPolicy, IgnoreSafe, -/// }; -/// -/// IgnoreSafe.handle_error(0); -/// ``` -/// -/// [`!`]: https://doc.rust-lang.org/std/primitive.never.html -/// [`Infallible`]: std::convert::Infallible -pub struct IgnoreSafe; - -#[allow(unreachable_code)] -impl ErrorHandler for IgnoreSafe { - fn handle_error<'a>( - &'a self, - _: Infallible, - ) -> Pin + 'a>> - where - Infallible: 'a, - { - Box::pin(async {}) - } -} - -/// An error handler that prints all errors passed into it. -/// -/// ## Example -/// ``` -/// # #[tokio::main] -/// # async fn main_() { -/// use teloxide::dispatching::error_handlers::{ErrorHandler, Print}; -/// -/// Print.handle_error(()).await; -/// Print.handle_error(404).await; -/// Print.handle_error(String::from("error")).await; -/// # } -/// ``` -pub struct Print; - -impl ErrorHandler for Print -where - E: Debug, -{ - fn handle_error<'a>( - &'a self, - error: E, - ) -> Pin + 'a>> - where - E: 'a, - { - log::debug!("error: {:?}", error); - Box::pin(async {}) - } -} - -/// The implementation of `ErrorHandler` for `Fn(error) -> Future`. -/// -/// ## Example -/// ``` -/// # #[tokio::main] -/// # async fn main_() { -/// use teloxide::dispatching::error_handlers::ErrorHandler; -/// -/// let mut closure = |e: i32| async move { eprintln!("Error code{}", e) }; -/// -/// closure.handle_error(404).await; -/// # } -/// ``` -impl ErrorHandler for F -where - F: Fn(E) -> Fut, - Fut: Future, -{ - fn handle_error<'a>( - &'a self, - error: E, - ) -> Pin + 'a>> - where - E: 'a, - { - Box::pin(async move { self(error).await }) - } -} diff --git a/src/dispatching/handler.rs b/src/dispatching/handler.rs index c0911be1..47f94b74 100644 --- a/src/dispatching/handler.rs +++ b/src/dispatching/handler.rs @@ -1,30 +1,38 @@ +use crate::types::Update; use std::{future::Future, pin::Pin}; -/// An asynchronous handler of a value. -pub trait Handler { +#[derive(Debug, Copy, Clone, Eq, Hash, PartialEq)] +pub enum SessionState { + Continue(S), + Terminate, +} + +pub trait Handler { #[must_use] fn handle<'a>( &'a self, - value: T, - ) -> Pin> + 'a>> + session: S, + update: Update, + ) -> Pin> + 'a>> where - T: 'a; + S: 'a; } -/// The implementation of `Handler` for `Fn(U) -> Future>`. -impl Handler for F +/// The implementation of `Handler` for `Fn(S, Update) -> Future>`. +impl Handler for F where - F: Fn(T) -> Fut, - Fut: Future>, + F: Fn(S, Update) -> Fut, + Fut: Future>, { fn handle<'a>( &'a self, - value: T, + session: S, + update: Update, ) -> Pin + 'a>> where - T: 'a, + S: 'a, { - Box::pin(async move { self(value).await }) + Box::pin(async move { self(session, update).await }) } } diff --git a/src/dispatching/mod.rs b/src/dispatching/mod.rs index 8cdd83ae..4c72ffe1 100644 --- a/src/dispatching/mod.rs +++ b/src/dispatching/mod.rs @@ -1,13 +1,11 @@ //! Update dispatching. -mod dispatchers; -pub mod error_handlers; +mod dispatcher; pub mod filters; mod handler; -pub mod updaters; +pub mod storage; +pub mod update_listeners; -pub use dispatchers::filter::FilterDispatcher; -pub use error_handlers::ErrorHandler; +pub use dispatcher::*; pub use filters::Filter; -pub use handler::Handler; -pub use updaters::Updater; +pub use handler::*; diff --git a/src/dispatching/storage/in_mem_storage.rs b/src/dispatching/storage/in_mem_storage.rs new file mode 100644 index 00000000..f9b9ba02 --- /dev/null +++ b/src/dispatching/storage/in_mem_storage.rs @@ -0,0 +1,25 @@ +use async_trait::async_trait; + +use crate::dispatching::storage::Storage; +use std::collections::HashMap; + +#[derive(Clone, Debug, Eq, PartialEq, Default)] +pub struct InMemStorage { + map: HashMap, +} + +#[async_trait(?Send)] +#[async_trait] +impl Storage for InMemStorage { + type Session = S; + + async fn remove_session(&mut self, chat_id: i64) -> Option { + self.map.remove(&chat_id) + } + + async fn update_session(&mut self, chat_id: i64, state: S) -> Option { + self.map.insert(chat_id, state) + } + + async fn save(&mut self) {} +} diff --git a/src/dispatching/storage/mod.rs b/src/dispatching/storage/mod.rs new file mode 100644 index 00000000..72aa4286 --- /dev/null +++ b/src/dispatching/storage/mod.rs @@ -0,0 +1,19 @@ +mod in_mem_storage; + +use async_trait::async_trait; +pub use in_mem_storage::InMemStorage; + +#[async_trait(?Send)] +#[async_trait] +pub trait Storage { + type Session; + + async fn remove_session(&mut self, chat_id: i64) -> Option; + async fn update_session( + &mut self, + chat_id: i64, + state: Self::Session, + ) -> Option; + + async fn save(&mut self); +} diff --git a/src/dispatching/updaters.rs b/src/dispatching/update_listeners.rs similarity index 86% rename from src/dispatching/updaters.rs rename to src/dispatching/update_listeners.rs index 56dbe2e9..cae5770f 100644 --- a/src/dispatching/updaters.rs +++ b/src/dispatching/update_listeners.rs @@ -1,12 +1,13 @@ //! Receiving updates from Telegram. //! -//! The key trait here is [`Updater`]. You can get it by these functions: +//! The key trait here is [`UpdateListener`]. You can get it by these functions: //! -//! - [`polling_default`], which returns a default long polling updater. -//! - [`polling`], which returns a long/short polling updater with your +//! - [`polling_default`], which returns a default long polling listener. +//! - [`polling`], which returns a long/short polling listener with your //! configuration. //! -//! And then you can pass it directly to a dispatcher. +//! And then you can extract updates from it and pass them directly to +//! [`Dispatcher::dispatch`]. //! //! Telegram supports two ways of [getting updates]: [long]/[short] polling and //! [webhook]. @@ -90,10 +91,10 @@ //! ^4 `offset = N` means that we've already received //! updates `0..=N`. //! -//! [`Updater`]: Updater +//! [`UpdateListener`]: UpdateListener //! [`polling_default`]: polling_default //! [`polling`]: polling -//! [`Dispatcher`]: crate::dispatching::Dispatcher::dispatch +//! [`Dispatcher::dispatch`]: crate::dispatching::Dispatcher::dispatch //! [`Box::get_updates`]: crate::Bot::get_updates //! [getting updates]: https://core.telegram.org/bots/api#getting-updates //! [long]: https://en.wikipedia.org/wiki/Push_technology#Long_polling @@ -110,22 +111,22 @@ use crate::{ }; use std::{convert::TryInto, time::Duration}; -/// A generic updater. -pub trait Updater: Stream> { +/// A generic update listener. +pub trait UpdateListener: Stream> { // TODO: add some methods here (.shutdown(), etc). } -impl Updater for S where S: Stream> {} +impl UpdateListener for S where S: Stream> {} -/// Returns a long polling updater with the default configuration. +/// Returns a long polling update listener with the default configuration. /// /// See also: [`polling`](polling). -pub fn polling_default(bot: &Bot) -> impl Updater + '_ { +pub fn polling_default(bot: &Bot) -> impl UpdateListener + '_ { polling(bot, None, None, None) } -/// Returns a long/short polling updater with some additional options. +/// Returns a long/short polling update listener with some additional options. /// -/// - `bot`: Using this bot, the returned updater will receive updates. +/// - `bot`: Using this bot, the returned update listener will receive updates. /// - `timeout`: A timeout for polling. /// - `limit`: Limits the number of updates to be retrieved at once. Values /// between 1—100 are accepted. @@ -140,7 +141,7 @@ pub fn polling( timeout: Option, limit: Option, allowed_updates: Option>, -) -> impl Updater + '_ { +) -> impl UpdateListener + '_ { let timeout = timeout.map(|t| t.as_secs().try_into().expect("timeout is too big")); From c69e685e00c99667c7b2af6d3fa69a6c21ab2471 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Wed, 8 Jan 2020 03:39:32 +0600 Subject: [PATCH 02/17] Add Dispatcher::save_storage() --- src/dispatching/dispatcher.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/dispatching/dispatcher.rs b/src/dispatching/dispatcher.rs index 6dd9f42d..62b3a7e4 100644 --- a/src/dispatching/dispatcher.rs +++ b/src/dispatching/dispatcher.rs @@ -50,6 +50,10 @@ where } } + pub async fn save_storage(&mut self) { + self.storage.save().await; + } + pub async fn dispatch(&mut self, update: Update) -> DispatchResult { let chat_id = match &update.kind { UpdateKind::Message(msg) => private_chat_id!(msg), @@ -85,6 +89,6 @@ where impl<'a, S, H> Drop for Dispatcher<'a, S, H> { fn drop(&mut self) { - // TODO: run self.storage.save() + // TODO: run self.save_storage() } } From 7eac9961be574116a6d6b9929913bedbf6e21c74 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Wed, 8 Jan 2020 04:13:12 +0600 Subject: [PATCH 03/17] Remove Storage::save() --- src/dispatching/dispatcher.rs | 10 ---------- src/dispatching/storage/in_mem_storage.rs | 2 -- src/dispatching/storage/mod.rs | 2 -- 3 files changed, 14 deletions(-) diff --git a/src/dispatching/dispatcher.rs b/src/dispatching/dispatcher.rs index 62b3a7e4..4174e5cc 100644 --- a/src/dispatching/dispatcher.rs +++ b/src/dispatching/dispatcher.rs @@ -50,10 +50,6 @@ where } } - pub async fn save_storage(&mut self) { - self.storage.save().await; - } - pub async fn dispatch(&mut self, update: Update) -> DispatchResult { let chat_id = match &update.kind { UpdateKind::Message(msg) => private_chat_id!(msg), @@ -86,9 +82,3 @@ where DispatchResult::Handled } } - -impl<'a, S, H> Drop for Dispatcher<'a, S, H> { - fn drop(&mut self) { - // TODO: run self.save_storage() - } -} diff --git a/src/dispatching/storage/in_mem_storage.rs b/src/dispatching/storage/in_mem_storage.rs index f9b9ba02..b2af31ad 100644 --- a/src/dispatching/storage/in_mem_storage.rs +++ b/src/dispatching/storage/in_mem_storage.rs @@ -20,6 +20,4 @@ impl Storage for InMemStorage { async fn update_session(&mut self, chat_id: i64, state: S) -> Option { self.map.insert(chat_id, state) } - - async fn save(&mut self) {} } diff --git a/src/dispatching/storage/mod.rs b/src/dispatching/storage/mod.rs index 72aa4286..8c5936ff 100644 --- a/src/dispatching/storage/mod.rs +++ b/src/dispatching/storage/mod.rs @@ -14,6 +14,4 @@ pub trait Storage { chat_id: i64, state: Self::Session, ) -> Option; - - async fn save(&mut self); } From c8a1bdce2541aa445cf5f3899afb3f48e00249f8 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Wed, 8 Jan 2020 04:33:22 +0600 Subject: [PATCH 04/17] Create dispatching::private --- src/dispatching/mod.rs | 4 +--- src/dispatching/{ => private}/dispatcher.rs | 0 src/dispatching/private/mod.rs | 5 +++++ src/dispatching/{ => private}/storage/in_mem_storage.rs | 2 +- src/dispatching/{ => private}/storage/mod.rs | 0 5 files changed, 7 insertions(+), 4 deletions(-) rename src/dispatching/{ => private}/dispatcher.rs (100%) create mode 100644 src/dispatching/private/mod.rs rename src/dispatching/{ => private}/storage/in_mem_storage.rs (92%) rename src/dispatching/{ => private}/storage/mod.rs (100%) diff --git a/src/dispatching/mod.rs b/src/dispatching/mod.rs index 4c72ffe1..e28f4250 100644 --- a/src/dispatching/mod.rs +++ b/src/dispatching/mod.rs @@ -1,11 +1,9 @@ //! Update dispatching. -mod dispatcher; pub mod filters; mod handler; -pub mod storage; +pub mod private; pub mod update_listeners; -pub use dispatcher::*; pub use filters::Filter; pub use handler::*; diff --git a/src/dispatching/dispatcher.rs b/src/dispatching/private/dispatcher.rs similarity index 100% rename from src/dispatching/dispatcher.rs rename to src/dispatching/private/dispatcher.rs diff --git a/src/dispatching/private/mod.rs b/src/dispatching/private/mod.rs new file mode 100644 index 00000000..8e213684 --- /dev/null +++ b/src/dispatching/private/mod.rs @@ -0,0 +1,5 @@ +mod dispatcher; +mod storage; + +pub use dispatcher::*; +pub use storage::*; diff --git a/src/dispatching/storage/in_mem_storage.rs b/src/dispatching/private/storage/in_mem_storage.rs similarity index 92% rename from src/dispatching/storage/in_mem_storage.rs rename to src/dispatching/private/storage/in_mem_storage.rs index b2af31ad..539a227b 100644 --- a/src/dispatching/storage/in_mem_storage.rs +++ b/src/dispatching/private/storage/in_mem_storage.rs @@ -1,6 +1,6 @@ use async_trait::async_trait; -use crate::dispatching::storage::Storage; +use super::Storage; use std::collections::HashMap; #[derive(Clone, Debug, Eq, PartialEq, Default)] diff --git a/src/dispatching/storage/mod.rs b/src/dispatching/private/storage/mod.rs similarity index 100% rename from src/dispatching/storage/mod.rs rename to src/dispatching/private/storage/mod.rs From 1547034741f1d49cdbb3297cd0f0591baecc7691 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Wed, 8 Jan 2020 16:51:10 +0600 Subject: [PATCH 05/17] Rename the generics --- src/dispatching/private/dispatcher.rs | 12 ++++++------ .../private/storage/in_mem_storage.rs | 16 +++++++++------- src/dispatching/private/storage/mod.rs | 10 ++++------ 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/src/dispatching/private/dispatcher.rs b/src/dispatching/private/dispatcher.rs index 4174e5cc..41925056 100644 --- a/src/dispatching/private/dispatcher.rs +++ b/src/dispatching/private/dispatcher.rs @@ -4,8 +4,8 @@ use crate::{ types::{ChatKind, Update, UpdateKind}, }; -pub struct Dispatcher<'a, S, H> { - storage: Box + 'a>, +pub struct Dispatcher<'a, Session, H> { + storage: Box + 'a>, handler: H, } @@ -28,10 +28,10 @@ mod macros { } } -impl<'a, S, H> Dispatcher<'a, S, H> +impl<'a, Session, H> Dispatcher<'a, Session, H> where - S: Default + 'a, - H: Handler, + Session: Default + 'a, + H: Handler, { pub fn new(handler: H) -> Self { Self { @@ -42,7 +42,7 @@ where pub fn with_storage(handler: H, storage: Stg) -> Self where - Stg: Storage + 'a, + Stg: Storage + 'a, { Self { storage: Box::new(storage), diff --git a/src/dispatching/private/storage/in_mem_storage.rs b/src/dispatching/private/storage/in_mem_storage.rs index 539a227b..c379453f 100644 --- a/src/dispatching/private/storage/in_mem_storage.rs +++ b/src/dispatching/private/storage/in_mem_storage.rs @@ -4,20 +4,22 @@ use super::Storage; use std::collections::HashMap; #[derive(Clone, Debug, Eq, PartialEq, Default)] -pub struct InMemStorage { - map: HashMap, +pub struct InMemStorage { + map: HashMap, } #[async_trait(?Send)] #[async_trait] -impl Storage for InMemStorage { - type Session = S; - - async fn remove_session(&mut self, chat_id: i64) -> Option { +impl Storage for InMemStorage { + async fn remove_session(&mut self, chat_id: i64) -> Option { self.map.remove(&chat_id) } - async fn update_session(&mut self, chat_id: i64, state: S) -> Option { + async fn update_session( + &mut self, + chat_id: i64, + state: Session, + ) -> Option { self.map.insert(chat_id, state) } } diff --git a/src/dispatching/private/storage/mod.rs b/src/dispatching/private/storage/mod.rs index 8c5936ff..161f8f2a 100644 --- a/src/dispatching/private/storage/mod.rs +++ b/src/dispatching/private/storage/mod.rs @@ -5,13 +5,11 @@ pub use in_mem_storage::InMemStorage; #[async_trait(?Send)] #[async_trait] -pub trait Storage { - type Session; - - async fn remove_session(&mut self, chat_id: i64) -> Option; +pub trait Storage { + async fn remove_session(&mut self, chat_id: i64) -> Option; async fn update_session( &mut self, chat_id: i64, - state: Self::Session, - ) -> Option; + state: Session, + ) -> Option; } From 3351838a3663ebe14f61a4183cce016b7c9f3c91 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Wed, 8 Jan 2020 17:59:30 +0600 Subject: [PATCH 06/17] Write the docs --- src/dispatching/handler.rs | 36 ++++++++++++------- src/dispatching/mod.rs | 7 ++++ src/dispatching/private/dispatcher.rs | 27 ++++++++++---- src/dispatching/private/mod.rs | 10 ++++++ .../private/storage/in_mem_storage.rs | 7 ++++ src/dispatching/private/storage/mod.rs | 19 +++++++++- src/dispatching/update_listeners.rs | 5 ++- 7 files changed, 87 insertions(+), 24 deletions(-) diff --git a/src/dispatching/handler.rs b/src/dispatching/handler.rs index 47f94b74..9a3d1570 100644 --- a/src/dispatching/handler.rs +++ b/src/dispatching/handler.rs @@ -1,37 +1,47 @@ use crate::types::Update; use std::{future::Future, pin::Pin}; +/// Continue or terminate a user session. #[derive(Debug, Copy, Clone, Eq, Hash, PartialEq)] -pub enum SessionState { - Continue(S), +pub enum SessionState { + Continue(Session), Terminate, } -pub trait Handler { +/// A handler of a user session and an update. +/// +/// ## Returns +/// Returns [`SessionState::Continue(session)`] if it wants to be called again +/// after a new update, or [`SessionState::Terminate`] if not. +/// +/// [`SessionState::Continue(session)`]: +/// crate::dispatching::SessionState::Continue +/// [`SessionState::Terminate`]: crate::dispatching::SessionState::Terminate +pub trait Handler { #[must_use] fn handle<'a>( &'a self, - session: S, + session: Session, update: Update, - ) -> Pin> + 'a>> + ) -> Pin> + 'a>> where - S: 'a; + Session: 'a; } -/// The implementation of `Handler` for `Fn(S, Update) -> Future>`. -impl Handler for F +/// The implementation of `Handler` for `Fn(Session, Update) -> Future>`. +impl Handler for F where - F: Fn(S, Update) -> Fut, - Fut: Future>, + F: Fn(Session, Update) -> Fut, + Fut: Future>, { fn handle<'a>( &'a self, - session: S, + session: Session, update: Update, ) -> Pin + 'a>> where - S: 'a, + Session: 'a, { Box::pin(async move { self(session, update).await }) } diff --git a/src/dispatching/mod.rs b/src/dispatching/mod.rs index e28f4250..895a7318 100644 --- a/src/dispatching/mod.rs +++ b/src/dispatching/mod.rs @@ -1,5 +1,12 @@ //! Update dispatching. +/// If an update was handled by a dispatcher or not. +#[derive(Debug, Copy, Clone, Eq, Hash, PartialEq)] +pub enum DispatchResult { + Handled, + Unhandled, +} + pub mod filters; mod handler; pub mod private; diff --git a/src/dispatching/private/dispatcher.rs b/src/dispatching/private/dispatcher.rs index 41925056..62a4c172 100644 --- a/src/dispatching/private/dispatcher.rs +++ b/src/dispatching/private/dispatcher.rs @@ -1,20 +1,18 @@ -use super::storage::{InMemStorage, Storage}; +use super::{ + super::DispatchResult, + storage::{InMemStorage, Storage}, +}; use crate::{ dispatching::{Handler, SessionState}, types::{ChatKind, Update, UpdateKind}, }; +/// A dispatcher that dispatches updates from 1-to-1 chats. pub struct Dispatcher<'a, Session, H> { storage: Box + 'a>, handler: H, } -#[derive(Debug, Copy, Clone, Eq, Hash, PartialEq)] -pub enum DispatchResult { - Handled, - Unhandled, -} - #[macro_use] mod macros { #[macro_export] @@ -33,6 +31,10 @@ where Session: Default + 'a, H: Handler, { + /// Creates a dispatcher with the specified `handler` and [`InMemStorage`] + /// (a default storage). + /// + /// [`InMemStorage`]: crate::dispatching::private::InMemStorage pub fn new(handler: H) -> Self { Self { storage: Box::new(InMemStorage::default()), @@ -40,6 +42,7 @@ where } } + /// Creates a dispatcher with the specified `handler` and `storage`. pub fn with_storage(handler: H, storage: Stg) -> Self where Stg: Storage + 'a, @@ -50,6 +53,16 @@ where } } + /// Dispatches a single `update`. + /// + /// ## Returns + /// Returns [`DispatchResult::Handled`] if `update` was supplied to a + /// handler, and [`DispatchResult::Unhandled`] if it was an update not + /// from a 1-to-1 chat. + /// + /// [`DispatchResult::Handled`]: crate::dispatching::DispatchResult::Handled + /// [`DispatchResult::Unhandled`]: + /// crate::dispatching::DispatchResult::Unhandled pub async fn dispatch(&mut self, update: Update) -> DispatchResult { let chat_id = match &update.kind { UpdateKind::Message(msg) => private_chat_id!(msg), diff --git a/src/dispatching/private/mod.rs b/src/dispatching/private/mod.rs index 8e213684..b4d622d9 100644 --- a/src/dispatching/private/mod.rs +++ b/src/dispatching/private/mod.rs @@ -1,3 +1,13 @@ +//! Dispatching updates from 1-to-1 chats. +//! +//! It's fairly simple: you have a session type and a handler that accepts +//! (session, update) and turns a session into the next state. When a new +//! user sends a message to your bot, the dispatcher creates a default session +//! and supplies it to your handler, but when an old user sends a message, your +//! handler gets the saved session with him. + +// TODO: examples + mod dispatcher; mod storage; diff --git a/src/dispatching/private/storage/in_mem_storage.rs b/src/dispatching/private/storage/in_mem_storage.rs index c379453f..140ee133 100644 --- a/src/dispatching/private/storage/in_mem_storage.rs +++ b/src/dispatching/private/storage/in_mem_storage.rs @@ -3,6 +3,13 @@ use async_trait::async_trait; use super::Storage; use std::collections::HashMap; +/// A memory storage based on a hash map. Stores all the sessions directly in +/// RAM. +/// +/// ## Note +/// All the sessions will be lost after you restart your bot. If you need to +/// store them somewhere on a drive, you need to implement a storage +/// communicating with a DB. #[derive(Clone, Debug, Eq, PartialEq, Default)] pub struct InMemStorage { map: HashMap, diff --git a/src/dispatching/private/storage/mod.rs b/src/dispatching/private/storage/mod.rs index 161f8f2a..1ad0c488 100644 --- a/src/dispatching/private/storage/mod.rs +++ b/src/dispatching/private/storage/mod.rs @@ -3,13 +3,30 @@ mod in_mem_storage; use async_trait::async_trait; pub use in_mem_storage::InMemStorage; +/// A storage of user sessions. +/// +/// You can implement this trait for a structure that communicates with a DB and +/// be sure that after you restart your bot, all the sessions won't be lost. +/// +/// For a storage based on a simple hash map, see [`InMemStorage`]. +/// +/// [`InMemStorage`]: crate::dispatching::private::InMemStorage #[async_trait(?Send)] #[async_trait] pub trait Storage { + /// Removes a session with the specified `chat_id`. + /// + /// Returns `None` if there wasn't such a session, `Some(session)` if a + /// `session` was deleted. async fn remove_session(&mut self, chat_id: i64) -> Option; + + /// Updates a session with the specified `chat_id`. + /// + /// Returns `None` if there wasn't such a session, `Some(session)` if a + /// `session` was updated. async fn update_session( &mut self, chat_id: i64, - state: Session, + session: Session, ) -> Option; } diff --git a/src/dispatching/update_listeners.rs b/src/dispatching/update_listeners.rs index cae5770f..9a0722f6 100644 --- a/src/dispatching/update_listeners.rs +++ b/src/dispatching/update_listeners.rs @@ -6,8 +6,8 @@ //! - [`polling`], which returns a long/short polling listener with your //! configuration. //! -//! And then you can extract updates from it and pass them directly to -//! [`Dispatcher::dispatch`]. +//! And then you can extract updates from it and pass them directly to a +//! dispatcher. //! //! Telegram supports two ways of [getting updates]: [long]/[short] polling and //! [webhook]. @@ -94,7 +94,6 @@ //! [`UpdateListener`]: UpdateListener //! [`polling_default`]: polling_default //! [`polling`]: polling -//! [`Dispatcher::dispatch`]: crate::dispatching::Dispatcher::dispatch //! [`Box::get_updates`]: crate::Bot::get_updates //! [getting updates]: https://core.telegram.org/bots/api#getting-updates //! [long]: https://en.wikipedia.org/wiki/Push_technology#Long_polling From d247320a76b63f0aa64a72e029564acd78c59f1b Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Fri, 10 Jan 2020 20:49:17 +0600 Subject: [PATCH 07/17] Update the docs --- src/dispatching/private/mod.rs | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/src/dispatching/private/mod.rs b/src/dispatching/private/mod.rs index b4d622d9..531859f1 100644 --- a/src/dispatching/private/mod.rs +++ b/src/dispatching/private/mod.rs @@ -1,10 +1,26 @@ //! Dispatching updates from 1-to-1 chats. //! -//! It's fairly simple: you have a session type and a handler that accepts -//! (session, update) and turns a session into the next state. When a new -//! user sends a message to your bot, the dispatcher creates a default session -//! and supplies it to your handler, but when an old user sends a message, your -//! handler gets the saved session with him. +//! There are four main components: +//! +//! 1. Your session type `Session`, which designates a dialogue state at the +//! current moment. +//! 2. A storage that encapsulates all the sessions. +//! 3. Your handler of type `H: async Fn(Session, Update) -> +//! HandleResult` that receives an update and turns your session into +//! the next state. +//! 4. The dispatcher, which encapsulates your handler and [`Storage`], and has +//! the `dispatch(Update) -> DispatchResult` function. +//! +//! Every time you call `.dispatch(update)` on your updater, the following steps +//! are executed: +//! +//! 1. If a supplied update is not from a 1-to-1 chat, return +//! `DispatchResult::Unhandled`. +//! 2. If a storage doesn't contain a session from this chat, supply +//! `Session::default()` into you handler, otherwise, supply the previous +//! session. +//! 3. If a handler has returned `HandleResult::Terminate`, remove the sesion +//! from a storage, otherwise force the storage to update the session. // TODO: examples From 45a032116d5967faf5e2c22f45731186507c74b8 Mon Sep 17 00:00:00 2001 From: Sergey Levitin Date: Sat, 11 Jan 2020 16:02:49 +0300 Subject: [PATCH 08/17] Add ping_bot example --- .gitignore | 4 +++- examples/Cargo.toml | 16 ++++++++++++++++ examples/README.md | 9 +++++++++ examples/ping_bot.rs | 43 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 71 insertions(+), 1 deletion(-) create mode 100644 examples/Cargo.toml create mode 100644 examples/README.md create mode 100644 examples/ping_bot.rs diff --git a/.gitignore b/.gitignore index 0668c27c..7c5b00ed 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ /target **/*.rs.bk Cargo.lock -.idea/ \ No newline at end of file +.idea/ +.vscode/ +examples/target diff --git a/examples/Cargo.toml b/examples/Cargo.toml new file mode 100644 index 00000000..f2ed9e4e --- /dev/null +++ b/examples/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "examples" +version = "0.0.0" +publish = false +edition = "2018" + +[dev-dependencies] +teloxide = { path = "../" } +tokio = "0.2.6" +pretty_env_logger = "0.3" +futures = "0.3.1" +log = "0.4.8" + +[[example]] +name = "ping_bot" +path = "ping_bot.rs" diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 00000000..f7528628 --- /dev/null +++ b/examples/README.md @@ -0,0 +1,9 @@ +# Examples of how to use Teloxide. + +All examples can be executed with: + +``` +cargo run --example $name +``` + +If you've got an example you'd like to see here, please feel free to open an issue. Otherwise if you've got an example you'd like to add, please feel free to make a PR! diff --git a/examples/ping_bot.rs b/examples/ping_bot.rs new file mode 100644 index 00000000..84574be0 --- /dev/null +++ b/examples/ping_bot.rs @@ -0,0 +1,43 @@ +use futures::stream::StreamExt; +use teloxide::{ + dispatching::{ + private::Dispatcher, update_listeners::polling_default, SessionState, + }, + requests::Request, + types::{Message, Update, UpdateKind}, + Bot, +}; + +extern crate pretty_env_logger; +#[macro_use] +extern crate log; + +#[tokio::main] +async fn main() { + pretty_env_logger::init(); + let bot = &Bot::new("1061598315:AAErEDodTsrqD3UxA_EvFyEfXbKA6DT25G0"); + let mut updater = Box::pin(polling_default(bot)); + let handler = |s, upd: Update| async move { + match upd.kind { + UpdateKind::Message(m) => { + if m.text() == "ping" { + let msg = bot.send_message(m.chat.id, "pong"); + } + msg.send().await.unwrap(); + } + _ => {} + } + SessionState::Continue(s) + }; + let mut dp = Dispatcher::<'_, (), _>::new(handler); + info!("Starting the message handler."); + loop { + let u = updater.next().await.unwrap(); + match u { + Err(e) => error!("{}", e), + Ok(u) => { + let _ = dp.dispatch(u).await; + } + } + } +} From b76106d276182f755256c960a729ffea278ffe7a Mon Sep 17 00:00:00 2001 From: Sergey Levitin Date: Sat, 11 Jan 2020 16:23:45 +0300 Subject: [PATCH 09/17] Fix ping_bot example --- examples/README.md | 2 +- examples/ping_bot.rs | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/examples/README.md b/examples/README.md index f7528628..19cab585 100644 --- a/examples/README.md +++ b/examples/README.md @@ -3,7 +3,7 @@ All examples can be executed with: ``` -cargo run --example $name +RUST_LOG=info cargo run --example $name ``` If you've got an example you'd like to see here, please feel free to open an issue. Otherwise if you've got an example you'd like to add, please feel free to make a PR! diff --git a/examples/ping_bot.rs b/examples/ping_bot.rs index 84574be0..bd6a3723 100644 --- a/examples/ping_bot.rs +++ b/examples/ping_bot.rs @@ -4,7 +4,7 @@ use teloxide::{ private::Dispatcher, update_listeners::polling_default, SessionState, }, requests::Request, - types::{Message, Update, UpdateKind}, + types::{Update, UpdateKind}, Bot, }; @@ -20,9 +20,7 @@ async fn main() { let handler = |s, upd: Update| async move { match upd.kind { UpdateKind::Message(m) => { - if m.text() == "ping" { - let msg = bot.send_message(m.chat.id, "pong"); - } + let msg = bot.send_message(m.chat.id, "pong"); msg.send().await.unwrap(); } _ => {} From b70aebc33a3948a856965bed4ebe7f8dfd796c03 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Sat, 11 Jan 2020 20:35:22 +0600 Subject: [PATCH 10/17] Update ping_bot.rs --- examples/ping_bot.rs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/examples/ping_bot.rs b/examples/ping_bot.rs index bd6a3723..865eff07 100644 --- a/examples/ping_bot.rs +++ b/examples/ping_bot.rs @@ -8,13 +8,10 @@ use teloxide::{ Bot, }; -extern crate pretty_env_logger; -#[macro_use] -extern crate log; - #[tokio::main] async fn main() { pretty_env_logger::init(); + let bot = &Bot::new("1061598315:AAErEDodTsrqD3UxA_EvFyEfXbKA6DT25G0"); let mut updater = Box::pin(polling_default(bot)); let handler = |s, upd: Update| async move { @@ -28,11 +25,11 @@ async fn main() { SessionState::Continue(s) }; let mut dp = Dispatcher::<'_, (), _>::new(handler); - info!("Starting the message handler."); + log::info!("Starting the message handler."); loop { let u = updater.next().await.unwrap(); match u { - Err(e) => error!("{}", e), + Err(e) => log::error!("{}", e), Ok(u) => { let _ = dp.dispatch(u).await; } From 5fd6ae3630151ea6f100e63406152e0e24637a4a Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Sat, 11 Jan 2020 21:04:57 +0600 Subject: [PATCH 11/17] Fix the docs --- src/dispatching/private/mod.rs | 22 +++++--- src/errors.rs | 96 +++++++++++++++++----------------- 2 files changed, 63 insertions(+), 55 deletions(-) diff --git a/src/dispatching/private/mod.rs b/src/dispatching/private/mod.rs index 531859f1..0e8fbd49 100644 --- a/src/dispatching/private/mod.rs +++ b/src/dispatching/private/mod.rs @@ -4,23 +4,31 @@ //! //! 1. Your session type `Session`, which designates a dialogue state at the //! current moment. -//! 2. A storage that encapsulates all the sessions. +//! 2. [`Storage`] that encapsulates all the sessions. //! 3. Your handler of type `H: async Fn(Session, Update) -> //! HandleResult` that receives an update and turns your session into //! the next state. -//! 4. The dispatcher, which encapsulates your handler and [`Storage`], and has -//! the `dispatch(Update) -> DispatchResult` function. +//! 4. [`Dispatcher`], which encapsulates your handler and [`Storage`], and has +//! the [`dispatch(Update) -> DispatchResult`] function. //! -//! Every time you call `.dispatch(update)` on your updater, the following steps -//! are executed: +//! Every time you call [`.dispatch(update)`] on your dispatcher, the following +//! steps are executed: //! //! 1. If a supplied update is not from a 1-to-1 chat, return -//! `DispatchResult::Unhandled`. +//! [`DispatchResult::Unhandled`]. //! 2. If a storage doesn't contain a session from this chat, supply //! `Session::default()` into you handler, otherwise, supply the previous //! session. -//! 3. If a handler has returned `HandleResult::Terminate`, remove the sesion +//! 3. If a handler has returned [`SessionState::Terminate`], remove the sesion //! from a storage, otherwise force the storage to update the session. +//! +//! [`Storage`]: crate::dispatching::private::Storage +//! [`Dispatcher`]: crate::dispatching::private::Dispatcher +//! [`dispatch(Update) -> DispatchResult`]: +//! crate::dispatching::private::Dispatcher::dispatch +//! [`.dispatch(update)`]: crate::dispatching::private::Dispatcher::dispatch +//! [`DispatchResult::Unhandled`]: crate::dispatching::DispatchResult::Unhandled +//! [`SessionState::Terminate`]: crate::dispatching::SessionState::Terminate // TODO: examples diff --git a/src/errors.rs b/src/errors.rs index 1805bd98..58c0b987 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -57,7 +57,7 @@ pub enum ApiErrorKind { /// May happen in methods: /// 1. [`EditMessageText`] /// - /// [`EditMessageText`]: crate::requests::payloads::EditMessageText + /// [`EditMessageText`]: crate::requests::EditMessageText #[serde(rename = "Bad Request: message is not modified: specified new \ message content and reply markup are exactly the same \ as a current content and reply markup of the message")] @@ -69,8 +69,8 @@ pub enum ApiErrorKind { /// 1. [`ForwardMessage`] /// 2. [`DeleteMessage`] /// - /// [`ForwardMessage`]: crate::requests::payloads::ForwardMessage - /// [`DeleteMessage`]: crate::requests::payloads::DeleteMessage + /// [`ForwardMessage`]: crate::requests::ForwardMessage + /// [`DeleteMessage`]: crate::requests::DeleteMessage #[serde(rename = "Bad Request: MESSAGE_ID_INVALID")] MessageIdInvalid, @@ -79,7 +79,7 @@ pub enum ApiErrorKind { /// May happen in methods: /// 1. [`ForwardMessage`] /// - /// [`ForwardMessage`]: crate::requests::payloads::ForwardMessage + /// [`ForwardMessage`]: crate::requests::ForwardMessage #[serde(rename = "Bad Request: message to forward not found")] MessageToForwardNotFound, @@ -88,7 +88,7 @@ pub enum ApiErrorKind { /// May happen in methods: /// 1. [`DeleteMessage`] /// - /// [`DeleteMessage`]: crate::requests::payloads::DeleteMessage + /// [`DeleteMessage`]: crate::requests::DeleteMessage #[serde(rename = "Bad Request: message to delete not found")] MessageToDeleteNotFound, @@ -97,7 +97,7 @@ pub enum ApiErrorKind { /// May happen in methods: /// 1. [`SendMessage`] /// - /// [`SendMessage`]: crate::requests::payloads::SendMessage + /// [`SendMessage`]: crate::requests::SendMessage #[serde(rename = "Bad Request: message text is empty")] MessageTextIsEmpty, @@ -106,7 +106,7 @@ pub enum ApiErrorKind { /// May happen in methods: /// 1. [`EditMessageText`] /// - /// [`EditMessageText`]: crate::requests::payloads::EditMessageText + /// [`EditMessageText`]: crate::requests::EditMessageText #[serde(rename = "Bad Request: message can't be edited")] MessageCantBeEdited, @@ -116,7 +116,7 @@ pub enum ApiErrorKind { /// May happen in methods: /// 1. [`DeleteMessage`] /// - /// [`DeleteMessage`]: crate::requests::payloads::DeleteMessage + /// [`DeleteMessage`]: crate::requests::DeleteMessage #[serde(rename = "Bad Request: message can't be deleted")] MessageCantBeDeleted, @@ -125,7 +125,7 @@ pub enum ApiErrorKind { /// May happen in methods: /// 1. [`EditMessageText`] /// - /// [`EditMessageText`]: crate::requests::payloads::EditMessageText + /// [`EditMessageText`]: crate::requests::EditMessageText #[serde(rename = "Bad Request: message to edit not found")] MessageToEditNotFound, @@ -134,7 +134,7 @@ pub enum ApiErrorKind { /// May happen in methods: /// 1. [`SendMessage`] /// - /// [`SendMessage`]: crate::requests::payloads::SendMessage + /// [`SendMessage`]: crate::requests::SendMessage #[serde(rename = "Bad Request: reply message not found")] MessageToReplyNotFound, @@ -148,7 +148,7 @@ pub enum ApiErrorKind { /// May happen in methods: /// 1. [`SendMessage`] /// - /// [`SendMessage`]: crate::requests::payloads::SendMessage + /// [`SendMessage`]: crate::requests::SendMessage #[serde(rename = "Bad Request: message is too long")] MessageIsTooLong, @@ -157,7 +157,7 @@ pub enum ApiErrorKind { /// May happen in methods: /// 1. [`SendMediaGroup`] /// - /// [`SendMediaGroup`]: crate::requests::payloads::SendMediaGroup + /// [`SendMediaGroup`]: crate::requests::SendMediaGroup #[serde(rename = "Bad Request: Too much messages to send as an album")] ToMuchMessages, @@ -166,7 +166,7 @@ pub enum ApiErrorKind { /// May happen in methods: /// 1. [`SendPoll`] /// - /// [`SendPoll`]: crate::requests::payloads::SendPoll + /// [`SendPoll`]: crate::requests::SendPoll #[serde(rename = "Bad Request: poll has already been closed")] PollHasAlreadyClosed, @@ -175,7 +175,7 @@ pub enum ApiErrorKind { /// May happen in methods: /// 1. [`SendPoll`] /// - /// [`SendPoll`]: crate::requests::payloads::SendPoll + /// [`SendPoll`]: crate::requests::SendPoll #[serde(rename = "Bad Request: poll must have at least 2 option")] PollMustHaveMoreOptions, @@ -184,7 +184,7 @@ pub enum ApiErrorKind { /// May happen in methods: /// 1. [`SendPoll`] /// - /// [`SendPoll`]: crate::requests::payloads::SendPoll + /// [`SendPoll`]: crate::requests::SendPoll #[serde(rename = "Bad Request: poll can't have more than 10 options")] PollCantHaveMoreOptions, @@ -193,7 +193,7 @@ pub enum ApiErrorKind { /// May happen in methods: /// 1. [`SendPoll`] /// - /// [`SendPoll`]: crate::requests::payloads::SendPoll + /// [`SendPoll`]: crate::requests::SendPoll #[serde(rename = "Bad Request: poll options must be non-empty")] PollOptionsMustBeNonEmpty, @@ -202,7 +202,7 @@ pub enum ApiErrorKind { /// May happen in methods: /// 1. [`SendPoll`] /// - /// [`SendPoll`]: crate::requests::payloads::SendPoll + /// [`SendPoll`]: crate::requests::SendPoll #[serde(rename = "Bad Request: poll question must be non-empty")] PollQuestionMustBeNonEmpty, @@ -212,7 +212,7 @@ pub enum ApiErrorKind { /// May happen in methods: /// 1. [`SendPoll`] /// - /// [`SendPoll`]: crate::requests::payloads::SendPoll + /// [`SendPoll`]: crate::requests::SendPoll #[serde(rename = "Bad Request: poll options length must not exceed 100")] PollOptionsLengthTooLong, @@ -222,7 +222,7 @@ pub enum ApiErrorKind { /// May happen in methods: /// 1. [`SendPoll`] /// - /// [`SendPoll`]: crate::requests::payloads::SendPoll + /// [`SendPoll`]: crate::requests::SendPoll #[serde(rename = "Bad Request: poll question length must not exceed 255")] PollQuestionLengthTooLong, @@ -231,7 +231,7 @@ pub enum ApiErrorKind { /// May happen in methods: /// 1. [`StopPoll`] /// - /// [`StopPoll`]: crate::requests::payloads::StopPoll + /// [`StopPoll`]: crate::requests::StopPoll #[serde(rename = "Bad Request: message with poll to stop not found")] MessageWithPollNotFound, @@ -240,7 +240,7 @@ pub enum ApiErrorKind { /// May happen in methods: /// 1. [`StopPoll`] /// - /// [`StopPoll`]: crate::requests::payloads::StopPoll + /// [`StopPoll`]: crate::requests::StopPoll #[serde(rename = "Bad Request: message is not a poll")] MessageIsNotAPoll, @@ -250,7 +250,7 @@ pub enum ApiErrorKind { /// May happen in methods: /// 1. [`SendMessage`] /// - /// [`SendMessage`]: crate::requests::payloads::SendMessage + /// [`SendMessage`]: crate::requests::SendMessage #[serde(rename = "Bad Request: chat not found")] ChatNotFound, @@ -260,7 +260,7 @@ pub enum ApiErrorKind { /// 1. [`getUserProfilePhotos`] /// /// [`getUserProfilePhotos`]: - /// crate::requests::payloads::getUserProfilePhotos + /// crate::requests::GetUserProfilePhotos #[serde(rename = "Bad Request: user not found")] UserNotFound, @@ -270,7 +270,7 @@ pub enum ApiErrorKind { /// May happen in methods: /// 1. [`SetChatDescription`] /// - /// [`SetChatDescription`]: crate::requests::payloads::SetChatDescription + /// [`SetChatDescription`]: crate::requests::SetChatDescription #[serde(rename = "Bad Request: chat description is not modified")] ChatDescriptionIsNotModified, @@ -279,7 +279,7 @@ pub enum ApiErrorKind { /// May happen in methods: /// 1. [`AnswerCallbackQuery`] /// - /// [`AnswerCallbackQuery`]: crate::requests::payloads::AnswerCallbackQuery + /// [`AnswerCallbackQuery`]: crate::requests::AnswerCallbackQuery #[serde(rename = "Bad Request: query is too old and response timeout \ expired or query id is invalid")] InvalidQueryID, @@ -290,7 +290,7 @@ pub enum ApiErrorKind { /// May happen in methods: /// 1. [`SendMessage`] /// - /// [`SendMessage`]: crate::requests::payloads::SendMessage + /// [`SendMessage`]: crate::requests::SendMessage #[serde(rename = "Bad Request: BUTTON_URL_INVALID")] ButtonURLInvalid, @@ -299,7 +299,7 @@ pub enum ApiErrorKind { /// May happen in methods: /// 1. [`SendMessage`] /// - /// [`SendMessage`]: crate::requests::payloads::SendMessage + /// [`SendMessage`]: crate::requests::SendMessage #[serde(rename = "Bad Request: BUTTON_DATA_INVALID")] ButtonDataInvalid, @@ -308,7 +308,7 @@ pub enum ApiErrorKind { /// May happen in methods: /// 1. [`SendMessage`] /// - /// [`SendMessage`]: crate::requests::payloads::SendMessage + /// [`SendMessage`]: crate::requests::SendMessage #[serde(rename = "Bad Request: can't parse inline keyboard button: Text \ buttons are unallowed in the inline keyboard")] TextButtonsAreUnallowed, @@ -318,7 +318,7 @@ pub enum ApiErrorKind { /// May happen in methods: /// 1. [`GetFile`] /// - /// [`GetFile`]: crate::requests::payloads::GetFile + /// [`GetFile`]: crate::requests::GetFile #[serde(rename = "Bad Request: wrong file id")] WrongFileID, @@ -331,7 +331,7 @@ pub enum ApiErrorKind { /// May happen in methods: /// 1. [`SetChatPhoto`] /// - /// [`SetChatPhoto`]: crate::requests::payloads::SetChatPhoto + /// [`SetChatPhoto`]: crate::requests::SetChatPhoto #[serde(rename = "Bad Request: Photo should be uploaded as an InputFile")] PhotoAsInputFileRequired, @@ -340,7 +340,7 @@ pub enum ApiErrorKind { /// May happen in methods: /// 1. [`AddStickerToSet`] /// - /// [`AddStickerToSet`]: crate::requests::payloads::AddStickerToSet + /// [`AddStickerToSet`]: crate::requests::AddStickerToSet #[serde(rename = "Bad Request: STICKERSET_INVALID")] InvalidStickersSet, @@ -350,7 +350,7 @@ pub enum ApiErrorKind { /// May happen in methods: /// 1. [`PinMessage`] /// - /// [`PinMessage`]: crate::requests::payloads::PinMessage + /// [`PinMessage`]: crate::requests::PinMessage #[serde(rename = "Bad Request: not enough rights to pin a message")] NotEnoughRightsToPinMessage, @@ -365,7 +365,7 @@ pub enum ApiErrorKind { /// May happen in methods: /// 1. [`PromoteChatMember`] /// - /// [`PromoteChatMember`]: crate::requests::payloads::PromoteChatMember + /// [`PromoteChatMember`]: crate::requests::PromoteChatMember #[serde(rename = "Bad Request: can't demote chat creator")] CantDemoteChatCreator, @@ -374,7 +374,7 @@ pub enum ApiErrorKind { /// May happen in methods: /// 1. [`RestrictChatMember`] /// - /// [`RestrictChatMember`]: crate::requests::payloads::RestrictChatMember + /// [`RestrictChatMember`]: crate::requests::RestrictChatMember #[serde(rename = "Bad Request: can't restrict self")] CantRestrictSelf, @@ -384,7 +384,7 @@ pub enum ApiErrorKind { /// May happen in methods: /// 1. [`RestrictChatMember`] /// - /// [`RestrictChatMember`]: crate::requests::payloads::RestrictChatMember + /// [`RestrictChatMember`]: crate::requests::RestrictChatMember #[serde(rename = "Bad Request: not enough rights to restrict/unrestrict \ chat member")] NotEnoughRightsToRestrict, @@ -394,7 +394,7 @@ pub enum ApiErrorKind { /// May happen in methods: /// 1. [`SetWebhook`] /// - /// [`SetWebhook`]: crate::requests::payloads::SetWebhook + /// [`SetWebhook`]: crate::requests::SetWebhook #[serde(rename = "Bad Request: bad webhook: HTTPS url must be provided \ for webhook")] WebhookRequireHTTPS, @@ -405,7 +405,7 @@ pub enum ApiErrorKind { /// May happen in methods: /// 1. [`SetWebhook`] /// - /// [`SetWebhook`]: crate::requests::payloads::SetWebhook + /// [`SetWebhook`]: crate::requests::SetWebhook #[serde(rename = "Bad Request: bad webhook: Webhook can be set up only \ on ports 80, 88, 443 or 8443")] BadWebhookPort, @@ -415,7 +415,7 @@ pub enum ApiErrorKind { /// May happen in methods: /// 1. [`SetWebhook`] /// - /// [`SetWebhook`]: crate::requests::payloads::SetWebhook + /// [`SetWebhook`]: crate::requests::SetWebhook #[serde(rename = "Bad Request: bad webhook: Failed to resolve host: \ Name or service not known")] UnknownHost, @@ -425,7 +425,7 @@ pub enum ApiErrorKind { /// May happen in methods: /// 1. [`SetWebhook`] /// - /// [`SetWebhook`]: crate::requests::payloads::SetWebhook + /// [`SetWebhook`]: crate::requests::SetWebhook #[serde(rename = "Bad Request: can't parse URL")] CantParseUrl, @@ -434,7 +434,7 @@ pub enum ApiErrorKind { /// May happen in methods: /// 1. [`SendMessage`] /// - /// [`SendMessage`]: crate::requests::payloads::SendMessage + /// [`SendMessage`]: crate::requests::SendMessage #[serde(rename = "Bad Request: can't parse entities")] CantParseEntities, @@ -443,7 +443,7 @@ pub enum ApiErrorKind { /// May happen in methods: /// 1. [`GetUpdates`] /// - /// [`GetUpdates`]: crate::requests::payloads::GetUpdates + /// [`GetUpdates`]: crate::requests::GetUpdates #[serde(rename = "can't use getUpdates method while webhook is active")] CantGetUpdates, @@ -452,7 +452,7 @@ pub enum ApiErrorKind { /// May happen in methods: /// 1. [`SendMessage`] /// - /// [`SendMessage`]: crate::requests::payloads::SendMessage + /// [`SendMessage`]: crate::requests::SendMessage #[serde(rename = "Unauthorized: bot was kicked from a chat")] BotKicked, @@ -461,7 +461,7 @@ pub enum ApiErrorKind { /// May happen in methods: /// 1. [`SendMessage`] /// - /// [`SendMessage`]: crate::requests::payloads::SendMessage + /// [`SendMessage`]: crate::requests::SendMessage #[serde(rename = "Unauthorized: user is deactivated")] UserDeactivated, @@ -470,7 +470,7 @@ pub enum ApiErrorKind { /// May happen in methods: /// 1. [`SendMessage`] /// - /// [`SendMessage`]: crate::requests::payloads::SendMessage + /// [`SendMessage`]: crate::requests::SendMessage #[serde( rename = "Unauthorized: bot can't initiate conversation with a user" )] @@ -481,7 +481,7 @@ pub enum ApiErrorKind { /// May happen in methods: /// 1. [`SendMessage`] /// - /// [`SendMessage`]: crate::requests::payloads::SendMessage + /// [`SendMessage`]: crate::requests::SendMessage #[serde(rename = "Unauthorized: bot can't send messages to bots")] CantTalkWithBots, @@ -490,7 +490,7 @@ pub enum ApiErrorKind { /// May happen in methods: /// 1. [`SendMessage`] /// - /// [`SendMessage`]: crate::requests::payloads::SendMessage + /// [`SendMessage`]: crate::requests::SendMessage #[serde(rename = "Bad Request: wrong HTTP URL")] WrongHTTPurl, @@ -500,7 +500,7 @@ pub enum ApiErrorKind { /// May happen in methods: /// 1. [`GetUpdates`] /// - /// [`GetUpdates`]: crate::requests::payloads::GetUpdates + /// [`GetUpdates`]: crate::requests::GetUpdates #[serde(rename = "Conflict: terminated by other getUpdates request; \ make sure that only one bot instance is running")] TerminatedByOtherGetUpdates, @@ -510,7 +510,7 @@ pub enum ApiErrorKind { /// May happen in methods: /// 1. [`GetFile`] /// - /// [`GetFile`]: crate::requests::payloads::GetFile + /// [`GetFile`]: crate::requests::GetFile #[serde(rename = "Bad Request: invalid file id")] FileIdInvalid, From dedc86152360a468b800649bfd008baa69ab3b3f Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Wed, 15 Jan 2020 03:46:16 +0600 Subject: [PATCH 12/17] Implement chat::Dispatcher --- examples/Cargo.toml | 4 +- examples/ping_bot.rs | 10 ++--- src/dispatching/chat/chat_update.rs | 18 ++++++++ .../{private => chat}/dispatcher.rs | 42 +++++++++---------- src/dispatching/{private => chat}/mod.rs | 10 +++-- .../storage/in_mem_storage.rs | 0 .../{private => chat}/storage/mod.rs | 2 +- src/dispatching/handler.rs | 15 +++---- src/dispatching/mod.rs | 2 +- src/types/reply_markup.rs | 4 +- 10 files changed, 63 insertions(+), 44 deletions(-) create mode 100644 src/dispatching/chat/chat_update.rs rename src/dispatching/{private => chat}/dispatcher.rs (65%) rename src/dispatching/{private => chat}/mod.rs (86%) rename src/dispatching/{private => chat}/storage/in_mem_storage.rs (100%) rename src/dispatching/{private => chat}/storage/mod.rs (96%) diff --git a/examples/Cargo.toml b/examples/Cargo.toml index f2ed9e4e..42c5f87a 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -4,10 +4,10 @@ version = "0.0.0" publish = false edition = "2018" -[dev-dependencies] +[dependencies] teloxide = { path = "../" } tokio = "0.2.6" -pretty_env_logger = "0.3" +pretty_env_logger = "0.3.1" futures = "0.3.1" log = "0.4.8" diff --git a/examples/ping_bot.rs b/examples/ping_bot.rs index 865eff07..3865e07e 100644 --- a/examples/ping_bot.rs +++ b/examples/ping_bot.rs @@ -1,22 +1,22 @@ use futures::stream::StreamExt; use teloxide::{ dispatching::{ - private::Dispatcher, update_listeners::polling_default, SessionState, + chat::Dispatcher, update_listeners::polling_default, SessionState, }, requests::Request, - types::{Update, UpdateKind}, Bot, }; +use teloxide::dispatching::chat::{ChatUpdate, ChatUpdateKind}; #[tokio::main] async fn main() { pretty_env_logger::init(); - + let bot = &Bot::new("1061598315:AAErEDodTsrqD3UxA_EvFyEfXbKA6DT25G0"); let mut updater = Box::pin(polling_default(bot)); - let handler = |s, upd: Update| async move { + let handler = |s, upd: ChatUpdate| async move { match upd.kind { - UpdateKind::Message(m) => { + ChatUpdateKind::Message(m) => { let msg = bot.send_message(m.chat.id, "pong"); msg.send().await.unwrap(); } diff --git a/src/dispatching/chat/chat_update.rs b/src/dispatching/chat/chat_update.rs new file mode 100644 index 00000000..32242b49 --- /dev/null +++ b/src/dispatching/chat/chat_update.rs @@ -0,0 +1,18 @@ +use serde::{Deserialize, Serialize}; + +use crate::types::{CallbackQuery, Message}; + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct ChatUpdate { + pub id: i32, + + pub kind: ChatUpdateKind, +} + +#[allow(clippy::large_enum_variant)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub enum ChatUpdateKind { + Message(Message), + EditedMessage(Message), + CallbackQuery(CallbackQuery), +} diff --git a/src/dispatching/private/dispatcher.rs b/src/dispatching/chat/dispatcher.rs similarity index 65% rename from src/dispatching/private/dispatcher.rs rename to src/dispatching/chat/dispatcher.rs index 62a4c172..fff84595 100644 --- a/src/dispatching/private/dispatcher.rs +++ b/src/dispatching/chat/dispatcher.rs @@ -3,33 +3,21 @@ use super::{ storage::{InMemStorage, Storage}, }; use crate::{ - dispatching::{Handler, SessionState}, - types::{ChatKind, Update, UpdateKind}, + dispatching::{chat::ChatUpdate, Handler, SessionState}, + types::{Update, UpdateKind}, }; +use crate::dispatching::chat::ChatUpdateKind; -/// A dispatcher that dispatches updates from 1-to-1 chats. +/// A dispatcher that dispatches updates from chats. pub struct Dispatcher<'a, Session, H> { storage: Box + 'a>, handler: H, } -#[macro_use] -mod macros { - #[macro_export] - macro_rules! private_chat_id { - ($msg:expr) => { - match &$msg.chat.kind { - ChatKind::Private { .. } => $msg.chat.id, - _ => return DispatchResult::Unhandled, - } - }; - } -} - impl<'a, Session, H> Dispatcher<'a, Session, H> where Session: Default + 'a, - H: Handler, + H: Handler, { /// Creates a dispatcher with the specified `handler` and [`InMemStorage`] /// (a default storage). @@ -58,18 +46,28 @@ where /// ## Returns /// Returns [`DispatchResult::Handled`] if `update` was supplied to a /// handler, and [`DispatchResult::Unhandled`] if it was an update not - /// from a 1-to-1 chat. + /// from a chat. /// /// [`DispatchResult::Handled`]: crate::dispatching::DispatchResult::Handled /// [`DispatchResult::Unhandled`]: /// crate::dispatching::DispatchResult::Unhandled pub async fn dispatch(&mut self, update: Update) -> DispatchResult { - let chat_id = match &update.kind { - UpdateKind::Message(msg) => private_chat_id!(msg), - UpdateKind::EditedMessage(msg) => private_chat_id!(msg), + let chat_update = match update.kind { + UpdateKind::Message(msg) => ChatUpdate { id: update.id, kind: ChatUpdateKind::Message(msg) }, + UpdateKind::EditedMessage(msg) => ChatUpdate { id: update.id, kind: ChatUpdateKind::EditedMessage(msg) }, + UpdateKind::CallbackQuery(query) => ChatUpdate { id: update.id, kind: ChatUpdateKind::CallbackQuery(query) }, _ => return DispatchResult::Unhandled, }; + let chat_id = match &chat_update.kind { + ChatUpdateKind::Message(msg) => msg.chat.id, + ChatUpdateKind::EditedMessage(msg) => msg.chat.id, + ChatUpdateKind::CallbackQuery(query) => match &query.message { + None => return DispatchResult::Unhandled, + Some(msg) => msg.chat.id, + }, + }; + let session = self .storage .remove_session(chat_id) @@ -77,7 +75,7 @@ where .unwrap_or_default(); if let SessionState::Continue(session) = - self.handler.handle(session, update).await + self.handler.handle(session, chat_update).await { if self .storage diff --git a/src/dispatching/private/mod.rs b/src/dispatching/chat/mod.rs similarity index 86% rename from src/dispatching/private/mod.rs rename to src/dispatching/chat/mod.rs index 0e8fbd49..7accabc6 100644 --- a/src/dispatching/private/mod.rs +++ b/src/dispatching/chat/mod.rs @@ -1,4 +1,4 @@ -//! Dispatching updates from 1-to-1 chats. +//! Dispatching updates from chats. //! //! There are four main components: //! @@ -14,13 +14,13 @@ //! Every time you call [`.dispatch(update)`] on your dispatcher, the following //! steps are executed: //! -//! 1. If a supplied update is not from a 1-to-1 chat, return +//! 1. If a supplied update is not from a chat, return //! [`DispatchResult::Unhandled`]. //! 2. If a storage doesn't contain a session from this chat, supply //! `Session::default()` into you handler, otherwise, supply the previous //! session. -//! 3. If a handler has returned [`SessionState::Terminate`], remove the sesion -//! from a storage, otherwise force the storage to update the session. +//! 3. If a handler has returned [`SessionState::Terminate`], remove the +//! session from a storage, otherwise force the storage to update the session. //! //! [`Storage`]: crate::dispatching::private::Storage //! [`Dispatcher`]: crate::dispatching::private::Dispatcher @@ -32,8 +32,10 @@ // TODO: examples +mod chat_update; mod dispatcher; mod storage; +pub use chat_update::*; pub use dispatcher::*; pub use storage::*; diff --git a/src/dispatching/private/storage/in_mem_storage.rs b/src/dispatching/chat/storage/in_mem_storage.rs similarity index 100% rename from src/dispatching/private/storage/in_mem_storage.rs rename to src/dispatching/chat/storage/in_mem_storage.rs diff --git a/src/dispatching/private/storage/mod.rs b/src/dispatching/chat/storage/mod.rs similarity index 96% rename from src/dispatching/private/storage/mod.rs rename to src/dispatching/chat/storage/mod.rs index 1ad0c488..e3e12bbf 100644 --- a/src/dispatching/private/storage/mod.rs +++ b/src/dispatching/chat/storage/mod.rs @@ -3,7 +3,7 @@ mod in_mem_storage; use async_trait::async_trait; pub use in_mem_storage::InMemStorage; -/// A storage of user sessions. +/// A storage of sessions. /// /// You can implement this trait for a structure that communicates with a DB and /// be sure that after you restart your bot, all the sessions won't be lost. diff --git a/src/dispatching/handler.rs b/src/dispatching/handler.rs index 9a3d1570..945e530e 100644 --- a/src/dispatching/handler.rs +++ b/src/dispatching/handler.rs @@ -1,4 +1,3 @@ -use crate::types::Update; use std::{future::Future, pin::Pin}; /// Continue or terminate a user session. @@ -17,31 +16,33 @@ pub enum SessionState { /// [`SessionState::Continue(session)`]: /// crate::dispatching::SessionState::Continue /// [`SessionState::Terminate`]: crate::dispatching::SessionState::Terminate -pub trait Handler { +pub trait Handler { #[must_use] fn handle<'a>( &'a self, session: Session, - update: Update, + update: U, ) -> Pin> + 'a>> where - Session: 'a; + Session: 'a, + U: 'a; } /// The implementation of `Handler` for `Fn(Session, Update) -> Future>`. -impl Handler for F +impl Handler for F where - F: Fn(Session, Update) -> Fut, + F: Fn(Session, U) -> Fut, Fut: Future>, { fn handle<'a>( &'a self, session: Session, - update: Update, + update: U, ) -> Pin + 'a>> where Session: 'a, + U: 'a, { Box::pin(async move { self(session, update).await }) } diff --git a/src/dispatching/mod.rs b/src/dispatching/mod.rs index 895a7318..70d1e0c4 100644 --- a/src/dispatching/mod.rs +++ b/src/dispatching/mod.rs @@ -7,9 +7,9 @@ pub enum DispatchResult { Unhandled, } +pub mod chat; pub mod filters; mod handler; -pub mod private; pub mod update_listeners; pub use filters::Filter; diff --git a/src/types/reply_markup.rs b/src/types/reply_markup.rs index 87845395..57840d48 100644 --- a/src/types/reply_markup.rs +++ b/src/types/reply_markup.rs @@ -1,5 +1,5 @@ -use serde::{Deserialize, Serialize}; use derive_more::From; +use serde::{Deserialize, Serialize}; use crate::types::{ ForceReply, InlineKeyboardMarkup, ReplyKeyboardMarkup, ReplyKeyboardRemove, @@ -25,4 +25,4 @@ mod tests { let actual: ReplyMarkup = data.into(); assert_eq!(actual, expected) } -} \ No newline at end of file +} From 510e5d863081deb888c20712952cf58063017132 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Fri, 17 Jan 2020 20:13:53 +0600 Subject: [PATCH 13/17] Temporary remove examples/ because of strange errors --- examples/Cargo.toml | 16 ---------------- examples/README.md | 9 --------- examples/ping_bot.rs | 38 -------------------------------------- 3 files changed, 63 deletions(-) delete mode 100644 examples/Cargo.toml delete mode 100644 examples/README.md delete mode 100644 examples/ping_bot.rs diff --git a/examples/Cargo.toml b/examples/Cargo.toml deleted file mode 100644 index 42c5f87a..00000000 --- a/examples/Cargo.toml +++ /dev/null @@ -1,16 +0,0 @@ -[package] -name = "examples" -version = "0.0.0" -publish = false -edition = "2018" - -[dependencies] -teloxide = { path = "../" } -tokio = "0.2.6" -pretty_env_logger = "0.3.1" -futures = "0.3.1" -log = "0.4.8" - -[[example]] -name = "ping_bot" -path = "ping_bot.rs" diff --git a/examples/README.md b/examples/README.md deleted file mode 100644 index 19cab585..00000000 --- a/examples/README.md +++ /dev/null @@ -1,9 +0,0 @@ -# Examples of how to use Teloxide. - -All examples can be executed with: - -``` -RUST_LOG=info cargo run --example $name -``` - -If you've got an example you'd like to see here, please feel free to open an issue. Otherwise if you've got an example you'd like to add, please feel free to make a PR! diff --git a/examples/ping_bot.rs b/examples/ping_bot.rs deleted file mode 100644 index 3865e07e..00000000 --- a/examples/ping_bot.rs +++ /dev/null @@ -1,38 +0,0 @@ -use futures::stream::StreamExt; -use teloxide::{ - dispatching::{ - chat::Dispatcher, update_listeners::polling_default, SessionState, - }, - requests::Request, - Bot, -}; -use teloxide::dispatching::chat::{ChatUpdate, ChatUpdateKind}; - -#[tokio::main] -async fn main() { - pretty_env_logger::init(); - - let bot = &Bot::new("1061598315:AAErEDodTsrqD3UxA_EvFyEfXbKA6DT25G0"); - let mut updater = Box::pin(polling_default(bot)); - let handler = |s, upd: ChatUpdate| async move { - match upd.kind { - ChatUpdateKind::Message(m) => { - let msg = bot.send_message(m.chat.id, "pong"); - msg.send().await.unwrap(); - } - _ => {} - } - SessionState::Continue(s) - }; - let mut dp = Dispatcher::<'_, (), _>::new(handler); - log::info!("Starting the message handler."); - loop { - let u = updater.next().await.unwrap(); - match u { - Err(e) => log::error!("{}", e), - Ok(u) => { - let _ = dp.dispatch(u).await; - } - } - } -} From c0f83a4999407ebf4da599f4cc3d1d0e9fd1b63f Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Fri, 17 Jan 2020 20:19:13 +0600 Subject: [PATCH 14/17] Fmt --- src/dispatching/chat/dispatcher.rs | 21 ++++++++++++++++----- src/dispatching/handler.rs | 4 ++-- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/src/dispatching/chat/dispatcher.rs b/src/dispatching/chat/dispatcher.rs index fff84595..14505ed9 100644 --- a/src/dispatching/chat/dispatcher.rs +++ b/src/dispatching/chat/dispatcher.rs @@ -3,10 +3,12 @@ use super::{ storage::{InMemStorage, Storage}, }; use crate::{ - dispatching::{chat::ChatUpdate, Handler, SessionState}, + dispatching::{ + chat::{ChatUpdate, ChatUpdateKind}, + Handler, SessionState, + }, types::{Update, UpdateKind}, }; -use crate::dispatching::chat::ChatUpdateKind; /// A dispatcher that dispatches updates from chats. pub struct Dispatcher<'a, Session, H> { @@ -53,9 +55,18 @@ where /// crate::dispatching::DispatchResult::Unhandled pub async fn dispatch(&mut self, update: Update) -> DispatchResult { let chat_update = match update.kind { - UpdateKind::Message(msg) => ChatUpdate { id: update.id, kind: ChatUpdateKind::Message(msg) }, - UpdateKind::EditedMessage(msg) => ChatUpdate { id: update.id, kind: ChatUpdateKind::EditedMessage(msg) }, - UpdateKind::CallbackQuery(query) => ChatUpdate { id: update.id, kind: ChatUpdateKind::CallbackQuery(query) }, + UpdateKind::Message(msg) => ChatUpdate { + id: update.id, + kind: ChatUpdateKind::Message(msg), + }, + UpdateKind::EditedMessage(msg) => ChatUpdate { + id: update.id, + kind: ChatUpdateKind::EditedMessage(msg), + }, + UpdateKind::CallbackQuery(query) => ChatUpdate { + id: update.id, + kind: ChatUpdateKind::CallbackQuery(query), + }, _ => return DispatchResult::Unhandled, }; diff --git a/src/dispatching/handler.rs b/src/dispatching/handler.rs index 945e530e..b40ac794 100644 --- a/src/dispatching/handler.rs +++ b/src/dispatching/handler.rs @@ -25,7 +25,7 @@ pub trait Handler { ) -> Pin> + 'a>> where Session: 'a, - U: 'a; + U: 'a; } /// The implementation of `Handler` for `Fn(Session, Update) -> Future Pin + 'a>> where Session: 'a, - U: 'a, + U: 'a, { Box::pin(async move { self(session, update).await }) } From e3235adf2f5a36b6ce044647134cb56750ee5319 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Sat, 18 Jan 2020 05:40:01 +0600 Subject: [PATCH 15/17] Fix UpdateKind --- src/network/download.rs | 12 +++++------- src/types/message_entity.rs | 10 ++++++---- src/types/update.rs | 19 ++++++++++++++++--- 3 files changed, 27 insertions(+), 14 deletions(-) diff --git a/src/network/download.rs b/src/network/download.rs index 0e68414f..05f08f5d 100644 --- a/src/network/download.rs +++ b/src/network/download.rs @@ -39,13 +39,11 @@ pub async fn download_file_stream( .await? .error_for_status()?; - Ok(futures::stream::unfold(res, |mut res| { - async { - match res.chunk().await { - Err(err) => Some((Err(err), res)), - Ok(Some(c)) => Some((Ok(c), res)), - Ok(None) => None, - } + Ok(futures::stream::unfold(res, |mut res| async { + match res.chunk().await { + Err(err) => Some((Err(err), res)), + Ok(Some(c)) => Some((Ok(c), res)), + Ok(None) => None, } })) } diff --git a/src/types/message_entity.rs b/src/types/message_entity.rs index c87471d6..9a649176 100644 --- a/src/types/message_entity.rs +++ b/src/types/message_entity.rs @@ -1,6 +1,6 @@ use serde::{Deserialize, Serialize}; -use crate::types::{User, Message}; +use crate::types::{Message, User}; /// This object represents one special entity in a text message. For example, /// hashtags, usernames, URLs, etc. @@ -42,14 +42,16 @@ pub enum MessageEntityKind { impl MessageEntity { pub fn text_from(&self, message: &Message) -> Option { let text = message.text(); - Some(String::from(&text?[self.offset..self.offset+self.length])) + Some(String::from(&text?[self.offset..self.offset + self.length])) } } #[cfg(test)] mod tests { use super::*; - use crate::types::{Chat, ChatKind, MessageKind, Sender, ForwardKind, MediaKind}; + use crate::types::{ + Chat, ChatKind, ForwardKind, MediaKind, MessageKind, Sender, + }; #[test] fn recursive_kind() { @@ -111,7 +113,7 @@ mod tests { entities: vec![MessageEntity { kind: MessageEntityKind::Mention, offset: 3, - length: 3 + length: 3, }], }, reply_markup: None, diff --git a/src/types/update.rs b/src/types/update.rs index 3dfb2270..b740afe1 100644 --- a/src/types/update.rs +++ b/src/types/update.rs @@ -2,7 +2,10 @@ use serde::{Deserialize, Serialize}; -use crate::types::{CallbackQuery, ChosenInlineResult, InlineQuery, Message}; +use crate::types::{ + CallbackQuery, ChosenInlineResult, InlineQuery, Message, Poll, + PreCheckoutQuery, ShippingQuery, +}; /// This [object] represents an incoming update. /// @@ -57,7 +60,17 @@ pub enum UpdateKind { /// New incoming callback query. CallbackQuery(CallbackQuery), - // TODO: Add more variants + + /// New incoming shipping query. Only for invoices with flexible price. + ShippingQuery(ShippingQuery), + + /// New incoming pre-checkout query. Contains full information about + /// checkout. + PreCheckoutQuery(PreCheckoutQuery), + + /// New poll state. Bots receive only updates about stopped polls and + /// polls, which are sent by the bot. + Poll(Poll), } #[cfg(test)] @@ -92,7 +105,7 @@ mod test { } }"#; - let expected: Update = Update { + let expected = Update { id: 892_252_934, kind: UpdateKind::Message(Message { id: 6557, From 2b77087aac2ba48cf76cb72118d5071162b78a77 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Sun, 19 Jan 2020 19:45:48 +0600 Subject: [PATCH 16/17] Return examples/ping_pong_bot.rs back --- Cargo.toml | 2 +- examples/ping_pong_bot.rs | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 examples/ping_pong_bot.rs diff --git a/Cargo.toml b/Cargo.toml index d921bb1b..ec037e6c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,7 @@ serde = { version = "1.0.101", features = ["derive"] } tokio = { version = "0.2.6", features = ["full"] } tokio-util = { version = "0.2.0", features = ["full"] } -reqwest = { version = "0.10", features = ["json", "stream"] } +reqwest = { version = "0.10", features = ["json", "stream", "native-tls-vendored"] } log = "0.4.8" bytes = "0.5.3" diff --git a/examples/ping_pong_bot.rs b/examples/ping_pong_bot.rs new file mode 100644 index 00000000..cfc4e4f7 --- /dev/null +++ b/examples/ping_pong_bot.rs @@ -0,0 +1,37 @@ +use futures::stream::StreamExt; +use teloxide::{ + dispatching::{ + chat::{ChatUpdate, ChatUpdateKind, Dispatcher}, + update_listeners::polling_default, + SessionState, + }, + requests::Request, + Bot, +}; + +#[tokio::main] +async fn main() { + let bot = &Bot::new("1061598315:AAErEDodTsrqD3UxA_EvFyEfXbKA6DT25G0"); + let mut updater = Box::pin(polling_default(bot)); + let handler = |s, upd: ChatUpdate| async move { + match upd.kind { + ChatUpdateKind::Message(m) => { + let msg = bot.send_message(m.chat.id, "pong"); + msg.send().await.unwrap(); + } + _ => {} + } + SessionState::Continue(s) + }; + let mut dp = Dispatcher::<'_, (), _>::new(handler); + println!("Starting the message handler."); + loop { + let u = updater.next().await.unwrap(); + match u { + Err(e) => eprintln!("Error: {}", e), + Ok(u) => { + let _ = dp.dispatch(u).await; + } + } + } +} From adbd17efaf4c49f4116273f336b5af7e00cf3a97 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Sun, 19 Jan 2020 19:54:45 +0600 Subject: [PATCH 17/17] Fix Clippy --- examples/ping_pong_bot.rs | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/examples/ping_pong_bot.rs b/examples/ping_pong_bot.rs index cfc4e4f7..8948dada 100644 --- a/examples/ping_pong_bot.rs +++ b/examples/ping_pong_bot.rs @@ -13,15 +13,12 @@ use teloxide::{ async fn main() { let bot = &Bot::new("1061598315:AAErEDodTsrqD3UxA_EvFyEfXbKA6DT25G0"); let mut updater = Box::pin(polling_default(bot)); - let handler = |s, upd: ChatUpdate| async move { - match upd.kind { - ChatUpdateKind::Message(m) => { - let msg = bot.send_message(m.chat.id, "pong"); - msg.send().await.unwrap(); - } - _ => {} + let handler = |_, upd: ChatUpdate| async move { + if let ChatUpdateKind::Message(m) = upd.kind { + let msg = bot.send_message(m.chat.id, "pong"); + msg.send().await.unwrap(); } - SessionState::Continue(s) + SessionState::Continue(()) }; let mut dp = Dispatcher::<'_, (), _>::new(handler); println!("Starting the message handler.");