Improve the docs of teloxide::dispatching

This commit is contained in:
Temirkhan Myrzamadi 2020-02-03 03:03:28 +06:00
parent 32cab96af5
commit a7dee4cab5
7 changed files with 225 additions and 159 deletions

View file

@ -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<Ctx, Output> {
#[must_use]
fn handle<'a>(
&'a self,
ctx: Ctx,
) -> Pin<Box<dyn Future<Output = Output> + 'a>>
where
Ctx: 'a;
}
impl<Ctx, Output, F, Fut> AsyncHandler<Ctx, Output> for F
where
F: Fn(Ctx) -> Fut,
Fut: Future<Output = Output>,
{
fn handle<'a>(
&'a self,
ctx: Ctx,
) -> Pin<Box<dyn Future<Output = Fut::Output> + 'a>>
where
Ctx: 'a,
{
Box::pin(async move { self(ctx).await })
}
}

View file

@ -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<Ctx, Output> {
#[must_use]
fn handle<'a>(
&'a self,
ctx: Ctx,
) -> Pin<Box<dyn Future<Output = Output> + 'a>>
where
Ctx: 'a;
}
impl<Ctx, Output, F, Fut> AsyncHandler<Ctx, Output> for F
where
F: Fn(Ctx) -> Fut,
Fut: Future<Output = Output>,
{
fn handle<'a>(
&'a self,
ctx: Ctx,
) -> Pin<Box<dyn Future<Output = Fut::Output> + '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<Ctx> AsyncHandler<Ctx, ()> for IgnoringHandler {
fn handle<'a>(&'a self, _: Ctx) -> Pin<Box<dyn Future<Output = ()> + '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<String, Infallible> = "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<Infallible, ()> for IgnoringHandlerSafe {
fn handle<'a>(
&'a self,
_: Infallible,
) -> Pin<Box<dyn Future<Output = ()> + '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<T>(text: T) -> Self
where
T: Into<String>,
{
Self { text: text.into() }
}
}
impl<Ctx> AsyncHandler<Ctx, ()> for LoggingHandler
where
Ctx: Debug,
{
fn handle<'a>(&'a self, ctx: Ctx) -> Pin<Box<dyn Future<Output = ()> + 'a>>
where
Ctx: 'a,
{
log::debug!("{text}: {:?}", ctx, text = self.text);
Box::pin(async {})
}
}

View file

@ -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;
}

View file

@ -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<E> AsyncHandler<E, ()> for Ignore {
fn handle<'a>(&'a self, _: E) -> Pin<Box<dyn Future<Output = ()> + '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<String, Infallible> = "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<Infallible, ()> for IgnoreSafe {
fn handle<'a>(
&'a self,
_: Infallible,
) -> Pin<Box<dyn Future<Output = ()> + '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<E> AsyncHandler<E, ()> for Log
where
E: Debug,
{
fn handle<'a>(&'a self, error: E) -> Pin<Box<dyn Future<Output = ()> + 'a>>
where
E: 'a,
{
log::debug!("error: {:?}", error);
Box::pin(async {})
}
}

View file

@ -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<Upd> {
pub bot: Arc<Bot>,
pub update: Upd,

View file

@ -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<Message>| 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;

View file

@ -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<Session>` 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