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