diff --git a/src/dispatching/async_handler.rs b/src/dispatching/async_handler.rs deleted file mode 100644 index c29f2d7a..00000000 --- a/src/dispatching/async_handler.rs +++ /dev/null @@ -1,31 +0,0 @@ -use std::{future::Future, pin::Pin}; - -/// An asynchronous polymorphic handler of a context. -/// -/// Note that `AsyncHandler` is implemented for asynchronous `Fn`s, that consume -/// `Ctx` and return `Output`. -pub trait AsyncHandler { - #[must_use] - fn handle<'a>( - &'a self, - ctx: Ctx, - ) -> Pin + 'a>> - where - Ctx: 'a; -} - -impl AsyncHandler for F -where - F: Fn(Ctx) -> Fut, - Fut: Future, -{ - fn handle<'a>( - &'a self, - ctx: Ctx, - ) -> Pin + 'a>> - where - Ctx: 'a, - { - Box::pin(async move { self(ctx).await }) - } -} diff --git a/src/dispatching/async_handlers.rs b/src/dispatching/async_handlers.rs new file mode 100644 index 00000000..c3e5c0ea --- /dev/null +++ b/src/dispatching/async_handlers.rs @@ -0,0 +1,145 @@ +use std::{convert::Infallible, fmt::Debug, future::Future, pin::Pin}; + +/// An asynchronous polymorphic handler of a context. +/// +/// Note that `AsyncHandler` is implemented for asynchronous `Fn`s, that consume +/// `Ctx` and return `Output`. +pub trait AsyncHandler { + #[must_use] + fn handle<'a>( + &'a self, + ctx: Ctx, + ) -> Pin + 'a>> + where + Ctx: 'a; +} + +impl AsyncHandler for F +where + F: Fn(Ctx) -> Fut, + Fut: Future, +{ + fn handle<'a>( + &'a self, + ctx: Ctx, + ) -> Pin + 'a>> + where + Ctx: 'a, + { + Box::pin(async move { self(ctx).await }) + } +} + +/// A handler that silently ignores all values. +/// +/// ## Example +/// ``` +/// # #[tokio::main] +/// # async fn main_() { +/// use teloxide::dispatching::{AsyncHandler, IgnoringHandler}; +/// +/// IgnoringHandler.handle(()).await; +/// IgnoringHandler.handle(404).await; +/// IgnoringHandler.handle("error").await; +/// # } +/// ``` +pub struct IgnoringHandler; + +impl AsyncHandler for IgnoringHandler { + fn handle<'a>(&'a self, _: Ctx) -> Pin + 'a>> + where + Ctx: 'a, + { + Box::pin(async {}) + } +} + +/// A handler that silently ignores all values that can never happen (e.g.: +/// [`!`] or [`Infallible`]). +/// +/// ## Examples +/// ``` +/// # #[tokio::main] +/// # async fn main_() { +/// use std::convert::{Infallible, TryInto}; +/// +/// use teloxide::dispatching::{AsyncHandler, IgnoringHandlerSafe}; +/// +/// let result: Result = "str".try_into(); +/// match result { +/// Ok(string) => println!("{}", string), +/// Err(inf) => IgnoringHandlerSafe.handle(inf).await, +/// } +/// +/// IgnoringHandlerSafe.handle(return).await; // return type of `return` is `!` (aka never) +/// # } +/// ``` +/// +/// ```compile_fail +/// use teloxide::dispatching::{AsyncHandler, IgnoringHandlerSafe}; +/// +/// IgnoringHandlerSafe.handle(0); +/// ``` +/// +/// [`!`]: https://doc.rust-lang.org/std/primitive.never.html +/// [`Infallible`]: std::convert::Infallible +pub struct IgnoringHandlerSafe; + +#[allow(unreachable_code)] +impl AsyncHandler for IgnoringHandlerSafe { + fn handle<'a>( + &'a self, + _: Infallible, + ) -> Pin + 'a>> + where + Infallible: 'a, + { + Box::pin(async {}) + } +} + +/// A handler that log all values passed into it. +/// +/// ## Example +/// ``` +/// # #[tokio::main] +/// # async fn main_() { +/// use teloxide::dispatching::{AsyncHandler, LoggingHandler}; +/// +/// LoggingHandler::default().handle(()).await; +/// LoggingHandler::new("error").handle(404).await; +/// LoggingHandler::new("error") +/// .handle("Invalid data type!") +/// .await; +/// # } +/// ``` +#[derive(Default)] +pub struct LoggingHandler { + text: String, +} + +impl LoggingHandler { + /// Creates `LoggingHandler` with a meta text before a log. + /// + /// The logs will be printed in this format: `{text}: {:?}`. + #[must_use] + pub fn new(text: T) -> Self + where + T: Into, + { + Self { text: text.into() } + } +} + +impl AsyncHandler for LoggingHandler +where + Ctx: Debug, +{ + fn handle<'a>(&'a self, ctx: Ctx) -> Pin + 'a>> + where + Ctx: 'a, + { + log::debug!("{text}: {:?}", ctx, text = self.text); + Box::pin(async {}) + } +} diff --git a/src/dispatching/dispatcher.rs b/src/dispatching/dispatcher.rs index d44928fa..2c480f1f 100644 --- a/src/dispatching/dispatcher.rs +++ b/src/dispatching/dispatcher.rs @@ -1,7 +1,7 @@ use crate::{ dispatching::{ - error_handlers, update_listeners, update_listeners::UpdateListener, - AsyncHandler, HandlerCtx, + update_listeners, update_listeners::UpdateListener, AsyncHandler, + HandlerCtx, LoggingHandler, }, types::{ CallbackQuery, ChosenInlineResult, InlineQuery, Message, Poll, @@ -42,7 +42,9 @@ where pub fn new(bot: Bot) -> Self { Self { bot: Arc::new(bot), - handlers_error_handler: Box::new(error_handlers::Log), + handlers_error_handler: Box::new(LoggingHandler::new( + "An error from a Dispatcher's handler", + )), message_handler: None, edited_message_handler: None, channel_post_handler: None, @@ -165,7 +167,7 @@ where pub async fn dispatch(&'a self) { self.dispatch_with_listener( update_listeners::polling_default(Arc::clone(&self.bot)), - &error_handlers::Log, + &LoggingHandler::new("An error from the update listener"), ) .await; } diff --git a/src/dispatching/error_handlers.rs b/src/dispatching/error_handlers.rs deleted file mode 100644 index 879f87dd..00000000 --- a/src/dispatching/error_handlers.rs +++ /dev/null @@ -1,102 +0,0 @@ -//! Commonly used handlers of errors. - -use crate::dispatching::AsyncHandler; -use std::{convert::Infallible, fmt::Debug, future::Future, pin::Pin}; - -/// A handler that silently ignores all errors. -/// -/// ## Example -/// ``` -/// # #[tokio::main] -/// # async fn main_() { -/// use teloxide::dispatching::{error_handlers::Ignore, AsyncHandler}; -/// -/// Ignore.handle(()).await; -/// Ignore.handle(404).await; -/// Ignore.handle(String::from("error")).await; -/// # } -/// ``` -pub struct Ignore; - -impl AsyncHandler for Ignore { - fn handle<'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::{AsyncHandler, error_handlers::IgnoreSafe}; -/// -/// let result: Result = "str".try_into(); -/// match result { -/// Ok(string) => println!("{}", string), -/// Err(inf) => IgnoreSafe.handle(inf).await, -/// } -/// -/// IgnoreSafe.handle(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 AsyncHandler for IgnoreSafe { - fn handle<'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::Log, AsyncHandler}; -/// -/// Log.handle(()).await; -/// Log.handle(404).await; -/// Log.handle(String::from("error")).await; -/// # } -/// ``` -pub struct Log; - -impl AsyncHandler for Log -where - E: Debug, -{ - fn handle<'a>(&'a self, error: E) -> Pin + 'a>> - where - E: 'a, - { - log::debug!("error: {:?}", error); - Box::pin(async {}) - } -} diff --git a/src/dispatching/handler_ctx.rs b/src/dispatching/handler_ctx.rs index 5b487575..355c131e 100644 --- a/src/dispatching/handler_ctx.rs +++ b/src/dispatching/handler_ctx.rs @@ -6,10 +6,12 @@ use crate::{ }; use std::sync::Arc; -/// A dispatcher's handler's context of a bot and an update. +/// A [`Dispatcher`]'s handler's context of a bot and an update. /// /// See [the module-level documentation for the design -/// overview](teloxide::dispatching). +/// overview](crate::dispatching). +/// +/// [`Dispatcher`]: crate::dispatching::Dispatcher pub struct HandlerCtx { pub bot: Arc, pub update: Upd, diff --git a/src/dispatching/mod.rs b/src/dispatching/mod.rs index d19dde82..3559dcff 100644 --- a/src/dispatching/mod.rs +++ b/src/dispatching/mod.rs @@ -1,12 +1,60 @@ //! Update dispatching. //! -mod async_handler; +//! The key type here is [`Dispatcher`]. It encapsulates [`UpdateListener`], a +//! handler of errors, and handlers for [10 update kinds]. When [`Update`] is +//! received from Telegram, it is supplied to an appropriate handler, and if a +//! handler has returned an error, the error is supplied into an error handler. +//! That's simple! +//! +//! All the handlers are of type [`AsyncHandler`]. It's like a [first-class +//! construction] in this module, because: +//! 1. It is implemented for [`SessionDispatcher`], which itself accepts +//! [`AsyncHandler`]. +//! 2. It is implemented for [`LoggingHandler`], [`IgnoringHandler`], and +//! [`IgnoringHandlerSafe`]. +//! 3. It is implemented even [for asynchronous functions]. +//! 4. You can use [`AsyncHandler`]s as error handlers. +//! 5. More... +//! +//! ## Examples +//! The ping-pong bot ([full](https://github.com/teloxide/teloxide/blob/dev/examples/ping_pong_bot/)): +//! +//! ``` +//! # #[tokio::main] +//! # async fn main_() { +//! use teloxide::prelude::*; +//! +//! // Setup logging here... +//! +//! Dispatcher::new(Bot::new("MyAwesomeToken")) +//! .message_handler(|ctx: HandlerCtx| async move { +//! ctx.reply("pong").await +//! }) +//! .dispatch() +//! .await; +//! # } +//! ``` +//! +//! [first-class construction]: https://stackoverflow.com/questions/646794/what-is-a-first-class-programming-construct +//! [`Dispatcher`]: crate::dispatching::Dispatcher +//! [`UpdateListener`]: crate::dispatching::update_listeners::UpdateListener +//! [10 update kinds]: crate::types::UpdateKind +//! [`Update`]: crate::types::Update +//! [`AsyncHandler`]: crate::dispatching::AsyncHandler +//! [`LoggingHandler`]: crate::dispatching::LoggingHandler +//! [`IgnoringHandler`]: crate::dispatching::IgnoringHandler +//! [`IgnoringHandlerSafe`]: crate::dispatching::IgnoringHandlerSafe +//! [for asynchronous functions]: crate::dispatching::AsyncHandler +//! [`SessionDispatcher`]: crate::dispatching::session::SessionDispatcher + +mod async_handlers; mod dispatcher; -pub mod error_handlers; mod handler_ctx; pub mod session; pub mod update_listeners; -pub use async_handler::AsyncHandler; +pub use async_handlers::{ + AsyncHandler, IgnoringHandler, IgnoringHandlerSafe, LoggingHandler, +}; pub use dispatcher::Dispatcher; pub use handler_ctx::HandlerCtx; diff --git a/src/dispatching/session/mod.rs b/src/dispatching/session/mod.rs index a64b7161..7d2f6151 100644 --- a/src/dispatching/session/mod.rs +++ b/src/dispatching/session/mod.rs @@ -1,31 +1,33 @@ -//! Dispatching user sessions. +//! Dealing with dialogues. //! //! There are four main components: //! //! 1. Your session type `Session`, which designates a dialogue state at the //! current moment. -//! 2. [`Storage`] that encapsulates all the sessions. -//! 3. Your handler of type `H: async Fn(Session, Update) -> -//! SessionState` that receives an update and turns your session into -//! the next state. -//! 4. [`SessionDispatcher`], which encapsulates your handler and -//! [`Storage`], and has the [`dispatch(Bot, Upd)`] function. +//! 2. [`Storage`], which encapsulates all the sessions. +//! 3. Your handler, which receives an update and turns your session into the +//! next state. +//! 4. [`SessionDispatcher`], which encapsulates your handler, [`Storage`], and +//! implements [`AsyncHandler`]. //! -//! Every time you call `.dispatch(bot, update)` on your dispatcher, the -//! following steps are executed: +//! You supply [`SessionDispatcher`] into [`Dispatcher`]. Every time +//! [`Dispatcher`] calls `SessionDispatcher::handle(...)`, the following steps +//! are executed: //! //! 1. 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 -//! session from a storage, otherwise force the storage to update the session. +//! `Session::default()` into you handler, otherwise, supply the saved session +//! from this chat. +//! 3. If a handler has returned [`SessionState::Exit`], remove the session +//! from the storage, otherwise ([`SessionState::Next`]) force the storage to +//! update the session. //! //! [`Storage`]: crate::dispatching::session::Storage //! [`SessionDispatcher`]: crate::dispatching::session::SessionDispatcher -//! [`dispatch(Bot, Upd)`]: -//! crate::dispatching::session::SessionDispatcher::dispatch -//! [`SessionState::Terminate`]: -//! crate::dispatching::session::SessionState::Terminate +//! [`SessionState::Exit`]: +//! crate::dispatching::session::SessionState::Exit +//! [`SessionState::Next`]: crate::dispatching::session::SessionState::Next +//! [`AsyncHandler`]: crate::dispatching::AsyncHandler +//! [`Dispatcher`]: crate::dispatching::Dispatcher // TODO: examples