This commit is contained in:
Temirkhan Myrzamadi 2020-02-02 22:32:27 +06:00
parent d7d97ef136
commit 61d002b8d4
10 changed files with 206 additions and 171 deletions

View file

@ -12,4 +12,7 @@ log = "0.4.8"
tokio = "0.2.9"
strum = "0.17.1"
strum_macros = "0.17.1"
teloxide = { path = "../../" }
teloxide = { path = "../../" }
[profile.release]
lto = true

View file

@ -99,13 +99,13 @@ type Res = Result<SessionState<User>, RequestError>;
async fn start(ctx: Ctx) -> Res {
reply!(ctx, "Let's start! First, what's your full name?");
Ok(SessionState::Continue(ctx.session))
Ok(SessionState::Next(ctx.session))
}
async fn full_name(mut ctx: Ctx) -> Res {
reply!(ctx, "What a wonderful name! Your age?");
ctx.session.full_name = Some(ctx.update.text().unwrap().to_owned());
Ok(SessionState::Continue(ctx.session))
Ok(SessionState::Next(ctx.session))
}
async fn age(mut ctx: Ctx) -> Res {
@ -117,7 +117,7 @@ async fn age(mut ctx: Ctx) -> Res {
Err(_) => reply!(ctx, "Oh, please, enter a number!"),
}
Ok(SessionState::Continue(ctx.session))
Ok(SessionState::Next(ctx.session))
}
async fn favourite_music(mut ctx: Ctx) -> Res {
@ -125,11 +125,11 @@ async fn favourite_music(mut ctx: Ctx) -> Res {
Ok(ok) => {
ctx.session.favourite_music = Some(ok);
reply!(ctx, format!("Fine. {}", ctx.session));
Ok(SessionState::Terminate)
Ok(SessionState::Exit)
}
Err(_) => {
reply!(ctx, "Oh, please, enter from the keyboard!");
Ok(SessionState::Continue(ctx.session))
Ok(SessionState::Next(ctx.session))
}
}
}
@ -147,7 +147,7 @@ async fn handle_message(ctx: Ctx) -> Res {
return favourite_music(ctx).await;
}
Ok(SessionState::Terminate)
Ok(SessionState::Exit)
}
// ============================================================================

View file

@ -1,9 +1,8 @@
use crate::{
dispatching::{
error_handlers, update_listeners, update_listeners::UpdateListener,
AsyncHandler,
AsyncHandler, HandlerCtx,
},
requests::{Request, ResponseResult},
types::{
CallbackQuery, ChosenInlineResult, InlineQuery, Message, Poll,
PreCheckoutQuery, ShippingQuery, UpdateKind,
@ -13,32 +12,6 @@ use crate::{
use futures::StreamExt;
use std::{fmt::Debug, sync::Arc};
/// A dispatcher's handler's context of a bot and an update.
///
/// See [the module-level documentation for the design
/// overview](teloxide::dispatching).
pub struct HandlerCtx<Upd> {
pub bot: Arc<Bot>,
pub update: Upd,
}
impl HandlerCtx<Message> {
pub fn chat_id(&self) -> i64 {
self.update.chat_id()
}
pub async fn reply<T>(self, text: T) -> ResponseResult<()>
where
T: Into<String>,
{
self.bot
.send_message(self.chat_id(), text)
.send()
.await
.map(|_| ())
}
}
type H<'a, Upd, HandlerE> =
Option<Box<dyn AsyncHandler<HandlerCtx<Upd>, Result<(), HandlerE>> + 'a>>;

View file

@ -0,0 +1,38 @@
use crate::{
dispatching::session::GetChatId,
requests::{Request, ResponseResult},
types::Message,
Bot,
};
use std::sync::Arc;
/// A dispatcher's handler's context of a bot and an update.
///
/// See [the module-level documentation for the design
/// overview](teloxide::dispatching).
pub struct HandlerCtx<Upd> {
pub bot: Arc<Bot>,
pub update: Upd,
}
impl<Upd> GetChatId for HandlerCtx<Upd>
where
Upd: GetChatId,
{
fn chat_id(&self) -> i64 {
self.update.chat_id()
}
}
impl HandlerCtx<Message> {
pub async fn reply<T>(&self, text: T) -> ResponseResult<()>
where
T: Into<String>,
{
self.bot
.send_message(self.chat_id(), text)
.send()
.await
.map(|_| ())
}
}

View file

@ -1,12 +1,12 @@
//! Update dispatching.
//!
//!
mod async_handler;
mod dispatcher;
pub mod error_handlers;
mod handler_ctx;
pub mod session;
pub mod update_listeners;
pub use async_handler::*;
pub use dispatcher::*;
pub use async_handler::AsyncHandler;
pub use dispatcher::Dispatcher;
pub use handler_ctx::HandlerCtx;

View file

@ -30,136 +30,13 @@
// TODO: examples
mod get_chat_id;
mod session_dispatcher;
mod session_handler_ctx;
mod session_state;
mod storage;
use crate::{
dispatching::{AsyncHandler, HandlerCtx},
requests::{Request, ResponseResult},
types::Message,
Bot,
};
pub use get_chat_id::*;
use std::{future::Future, pin::Pin, sync::Arc};
pub use storage::*;
/// A context of a private message handler.
pub struct SessionHandlerCtx<Upd, Session> {
pub bot: Arc<Bot>,
pub update: Upd,
pub session: Session,
}
impl<Session> SessionHandlerCtx<Message, Session> {
pub fn chat_id(&self) -> i64 {
self.update.chat_id()
}
pub async fn reply<T>(&self, text: T) -> ResponseResult<()>
where
T: Into<String>,
{
self.bot
.send_message(self.chat_id(), text)
.send()
.await
.map(|_| ())
}
}
/// Continue or terminate a user session.
#[derive(Debug, Copy, Clone, Eq, Hash, PartialEq)]
pub enum SessionState<Session> {
Continue(Session),
Terminate,
}
/// A dispatcher of user sessions.
///
/// Note that `SessionDispatcher` implements `AsyncHandler`, so you can just put
/// an instance of this dispatcher into the [`Dispatcher`]'s methods.
///
/// [`Dispatcher`]: crate::dispatching::Dispatcher
pub struct SessionDispatcher<'a, Session, H> {
storage: Box<dyn Storage<Session> + 'a>,
handler: H,
}
impl<'a, Session, H> SessionDispatcher<'a, Session, H>
where
Session: Default + 'a,
{
/// Creates a dispatcher with the specified `handler` and [`InMemStorage`]
/// (a default storage).
///
/// [`InMemStorage`]: crate::dispatching::session::InMemStorage
#[must_use]
pub fn new(handler: H) -> Self {
Self {
storage: Box::new(InMemStorage::default()),
handler,
}
}
/// Creates a dispatcher with the specified `handler` and `storage`.
#[must_use]
pub fn with_storage<Stg>(handler: H, storage: Stg) -> Self
where
Stg: Storage<Session> + 'a,
{
Self {
storage: Box::new(storage),
handler,
}
}
}
impl<'a, Session, H, Upd> AsyncHandler<HandlerCtx<Upd>, Result<(), ()>>
for SessionDispatcher<'a, Session, H>
where
H: AsyncHandler<SessionHandlerCtx<Upd, Session>, SessionState<Session>>,
Upd: GetChatId,
Session: Default,
{
/// Dispatches a single `message` from a private chat.
fn handle<'b>(
&'b self,
ctx: HandlerCtx<Upd>,
) -> Pin<Box<dyn Future<Output = Result<(), ()>> + 'b>>
where
Upd: 'b,
{
Box::pin(async move {
let chat_id = ctx.update.chat_id();
let session = self
.storage
.remove_session(chat_id)
.await
.unwrap_or_default();
if let SessionState::Continue(new_session) = self
.handler
.handle(SessionHandlerCtx {
bot: ctx.bot,
update: ctx.update,
session,
})
.await
{
if self
.storage
.update_session(chat_id, new_session)
.await
.is_some()
{
panic!(
"We previously storage.remove_session() so \
storage.update_session() must return None"
);
}
}
Ok(())
})
}
}
pub use get_chat_id::GetChatId;
pub use session_dispatcher::SessionDispatcher;
pub use session_handler_ctx::SessionHandlerCtx;
pub use session_state::SessionState;
pub use storage::{InMemStorage, Storage};

View file

@ -0,0 +1,98 @@
use crate::dispatching::{
session::{
GetChatId, InMemStorage, SessionHandlerCtx, SessionState, Storage,
},
AsyncHandler, HandlerCtx,
};
use std::{future::Future, pin::Pin};
/// A dispatcher of user sessions.
///
/// Note that `SessionDispatcher` implements `AsyncHandler`, so you can just put
/// an instance of this dispatcher into the [`Dispatcher`]'s methods.
///
/// [`Dispatcher`]: crate::dispatching::Dispatcher
pub struct SessionDispatcher<'a, Session, H> {
storage: Box<dyn Storage<Session> + 'a>,
handler: H,
}
impl<'a, Session, H> SessionDispatcher<'a, Session, H>
where
Session: Default + 'a,
{
/// Creates a dispatcher with the specified `handler` and [`InMemStorage`]
/// (a default storage).
///
/// [`InMemStorage`]: crate::dispatching::session::InMemStorage
#[must_use]
pub fn new(handler: H) -> Self {
Self {
storage: Box::new(InMemStorage::default()),
handler,
}
}
/// Creates a dispatcher with the specified `handler` and `storage`.
#[must_use]
pub fn with_storage<Stg>(handler: H, storage: Stg) -> Self
where
Stg: Storage<Session> + 'a,
{
Self {
storage: Box::new(storage),
handler,
}
}
}
impl<'a, Session, H, Upd> AsyncHandler<HandlerCtx<Upd>, Result<(), ()>>
for SessionDispatcher<'a, Session, H>
where
H: AsyncHandler<SessionHandlerCtx<Upd, Session>, SessionState<Session>>,
Upd: GetChatId,
Session: Default,
{
/// Dispatches a single `message` from a private chat.
fn handle<'b>(
&'b self,
ctx: HandlerCtx<Upd>,
) -> Pin<Box<dyn Future<Output = Result<(), ()>> + 'b>>
where
Upd: 'b,
{
Box::pin(async move {
let chat_id = ctx.update.chat_id();
let session = self
.storage
.remove_session(chat_id)
.await
.unwrap_or_default();
if let SessionState::Next(new_session) = self
.handler
.handle(SessionHandlerCtx {
bot: ctx.bot,
update: ctx.update,
session,
})
.await
{
if self
.storage
.update_session(chat_id, new_session)
.await
.is_some()
{
panic!(
"We previously storage.remove_session() so \
storage.update_session() must return None"
);
}
}
Ok(())
})
}
}

View file

@ -0,0 +1,38 @@
use crate::{
dispatching::session::GetChatId,
requests::{Request, ResponseResult},
types::Message,
Bot,
};
use std::sync::Arc;
/// A context of a [`SessionDispatcher`]'s message handler.
///
/// [`SessionDispatcher`]: crate::dispatching::session::SessionDispatcher
pub struct SessionHandlerCtx<Upd, Session> {
pub bot: Arc<Bot>,
pub update: Upd,
pub session: Session,
}
impl<Upd, Session> GetChatId for SessionHandlerCtx<Upd, Session>
where
Upd: GetChatId,
{
fn chat_id(&self) -> i64 {
self.update.chat_id()
}
}
impl<Session> SessionHandlerCtx<Message, Session> {
pub async fn reply<T>(&self, text: T) -> ResponseResult<()>
where
T: Into<String>,
{
self.bot
.send_message(self.chat_id(), text)
.send()
.await
.map(|_| ())
}
}

View file

@ -0,0 +1,6 @@
/// Continue or terminate a user session.
#[derive(Debug, Copy, Clone, Eq, Hash, PartialEq)]
pub enum SessionState<Session> {
Next(Session),
Exit,
}

View file

@ -2,7 +2,9 @@
pub use crate::{
dispatching::{
session::{SessionDispatcher, SessionHandlerCtx, SessionState},
session::{
GetChatId, SessionDispatcher, SessionHandlerCtx, SessionState,
},
Dispatcher, HandlerCtx,
},
requests::{Request, ResponseResult},