Write the docs

This commit is contained in:
Temirkhan Myrzamadi 2020-01-08 17:59:30 +06:00
parent 1547034741
commit 3351838a36
7 changed files with 87 additions and 24 deletions

View file

@ -1,37 +1,47 @@
use crate::types::Update; use crate::types::Update;
use std::{future::Future, pin::Pin}; use std::{future::Future, pin::Pin};
/// Continue or terminate a user session.
#[derive(Debug, Copy, Clone, Eq, Hash, PartialEq)] #[derive(Debug, Copy, Clone, Eq, Hash, PartialEq)]
pub enum SessionState<S> { pub enum SessionState<Session> {
Continue(S), Continue(Session),
Terminate, Terminate,
} }
pub trait Handler<S> { /// 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<Session> {
#[must_use] #[must_use]
fn handle<'a>( fn handle<'a>(
&'a self, &'a self,
session: S, session: Session,
update: Update, update: Update,
) -> Pin<Box<dyn Future<Output = SessionState<S>> + 'a>> ) -> Pin<Box<dyn Future<Output = SessionState<Session>> + 'a>>
where where
S: 'a; Session: 'a;
} }
/// The implementation of `Handler` for `Fn(S, Update) -> Future<Output = /// The implementation of `Handler` for `Fn(Session, Update) -> Future<Output =
/// SessionState<S>>`. /// SessionState<Session>>`.
impl<S, F, Fut> Handler<S> for F impl<Session, F, Fut> Handler<Session> for F
where where
F: Fn(S, Update) -> Fut, F: Fn(Session, Update) -> Fut,
Fut: Future<Output = SessionState<S>>, Fut: Future<Output = SessionState<Session>>,
{ {
fn handle<'a>( fn handle<'a>(
&'a self, &'a self,
session: S, session: Session,
update: Update, update: Update,
) -> Pin<Box<dyn Future<Output = Fut::Output> + 'a>> ) -> Pin<Box<dyn Future<Output = Fut::Output> + 'a>>
where where
S: 'a, Session: 'a,
{ {
Box::pin(async move { self(session, update).await }) Box::pin(async move { self(session, update).await })
} }

View file

@ -1,5 +1,12 @@
//! Update dispatching. //! 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; pub mod filters;
mod handler; mod handler;
pub mod private; pub mod private;

View file

@ -1,20 +1,18 @@
use super::storage::{InMemStorage, Storage}; use super::{
super::DispatchResult,
storage::{InMemStorage, Storage},
};
use crate::{ use crate::{
dispatching::{Handler, SessionState}, dispatching::{Handler, SessionState},
types::{ChatKind, Update, UpdateKind}, types::{ChatKind, Update, UpdateKind},
}; };
/// A dispatcher that dispatches updates from 1-to-1 chats.
pub struct Dispatcher<'a, Session, H> { pub struct Dispatcher<'a, Session, H> {
storage: Box<dyn Storage<Session> + 'a>, storage: Box<dyn Storage<Session> + 'a>,
handler: H, handler: H,
} }
#[derive(Debug, Copy, Clone, Eq, Hash, PartialEq)]
pub enum DispatchResult {
Handled,
Unhandled,
}
#[macro_use] #[macro_use]
mod macros { mod macros {
#[macro_export] #[macro_export]
@ -33,6 +31,10 @@ where
Session: Default + 'a, Session: Default + 'a,
H: Handler<Session>, H: Handler<Session>,
{ {
/// Creates a dispatcher with the specified `handler` and [`InMemStorage`]
/// (a default storage).
///
/// [`InMemStorage`]: crate::dispatching::private::InMemStorage
pub fn new(handler: H) -> Self { pub fn new(handler: H) -> Self {
Self { Self {
storage: Box::new(InMemStorage::default()), storage: Box::new(InMemStorage::default()),
@ -40,6 +42,7 @@ where
} }
} }
/// Creates a dispatcher with the specified `handler` and `storage`.
pub fn with_storage<Stg>(handler: H, storage: Stg) -> Self pub fn with_storage<Stg>(handler: H, storage: Stg) -> Self
where where
Stg: Storage<Session> + 'a, Stg: Storage<Session> + '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 { pub async fn dispatch(&mut self, update: Update) -> DispatchResult {
let chat_id = match &update.kind { let chat_id = match &update.kind {
UpdateKind::Message(msg) => private_chat_id!(msg), UpdateKind::Message(msg) => private_chat_id!(msg),

View file

@ -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 dispatcher;
mod storage; mod storage;

View file

@ -3,6 +3,13 @@ use async_trait::async_trait;
use super::Storage; use super::Storage;
use std::collections::HashMap; 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)] #[derive(Clone, Debug, Eq, PartialEq, Default)]
pub struct InMemStorage<Session> { pub struct InMemStorage<Session> {
map: HashMap<i64, Session>, map: HashMap<i64, Session>,

View file

@ -3,13 +3,30 @@ mod in_mem_storage;
use async_trait::async_trait; use async_trait::async_trait;
pub use in_mem_storage::InMemStorage; 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(?Send)]
#[async_trait] #[async_trait]
pub trait Storage<Session> { pub trait Storage<Session> {
/// 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<Session>; async fn remove_session(&mut self, chat_id: i64) -> Option<Session>;
/// 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( async fn update_session(
&mut self, &mut self,
chat_id: i64, chat_id: i64,
state: Session, session: Session,
) -> Option<Session>; ) -> Option<Session>;
} }

View file

@ -6,8 +6,8 @@
//! - [`polling`], which returns a long/short polling listener with your //! - [`polling`], which returns a long/short polling listener with your
//! configuration. //! configuration.
//! //!
//! And then you can extract updates from it and pass them directly to //! And then you can extract updates from it and pass them directly to a
//! [`Dispatcher::dispatch`]. //! dispatcher.
//! //!
//! Telegram supports two ways of [getting updates]: [long]/[short] polling and //! Telegram supports two ways of [getting updates]: [long]/[short] polling and
//! [webhook]. //! [webhook].
@ -94,7 +94,6 @@
//! [`UpdateListener`]: UpdateListener //! [`UpdateListener`]: UpdateListener
//! [`polling_default`]: polling_default //! [`polling_default`]: polling_default
//! [`polling`]: polling //! [`polling`]: polling
//! [`Dispatcher::dispatch`]: crate::dispatching::Dispatcher::dispatch
//! [`Box::get_updates`]: crate::Bot::get_updates //! [`Box::get_updates`]: crate::Bot::get_updates
//! [getting updates]: https://core.telegram.org/bots/api#getting-updates //! [getting updates]: https://core.telegram.org/bots/api#getting-updates
//! [long]: https://en.wikipedia.org/wiki/Push_technology#Long_polling //! [long]: https://en.wikipedia.org/wiki/Push_technology#Long_polling