From 798102a7d74f54dfd59f837c7293d2cbcece47d2 Mon Sep 17 00:00:00 2001 From: Maximilian Siling Date: Fri, 13 Mar 2020 00:38:35 +0300 Subject: [PATCH 01/29] Add Redis storage & example bot using it --- Cargo.toml | 9 +- examples/dialogue_bot/src/main.rs | 2 +- examples/dialogue_bot_redis/Cargo.toml | 20 ++ examples/dialogue_bot_redis/src/main.rs | 210 ++++++++++++++++++ src/dispatching/dialogue/mod.rs | 4 +- src/dispatching/dialogue/storage/mod.rs | 9 + .../dialogue/storage/redis_storage.rs | 85 +++++++ .../dialogue/storage/serializer.rs | 53 +++++ 8 files changed, 389 insertions(+), 3 deletions(-) create mode 100644 examples/dialogue_bot_redis/Cargo.toml create mode 100644 examples/dialogue_bot_redis/src/main.rs create mode 100644 src/dispatching/dialogue/storage/redis_storage.rs create mode 100644 src/dispatching/dialogue/storage/serializer.rs diff --git a/Cargo.toml b/Cargo.toml index 7f4a5158..535089a9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,10 @@ authors = [ [badges] maintenance = { status = "actively-developed" } -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[features] +redis-storage = ["redis"] +cbor-serializer = ["serde_cbor"] +bincode-serializer = ["bincode"] [dependencies] serde_json = "1.0.44" @@ -45,6 +48,10 @@ futures = "0.3.1" pin-project = "0.4.6" serde_with_macros = "1.0.1" +redis = { version = "0.15.1", optional = true } +serde_cbor = { version = "0.11.1", optional = true } +bincode = { version = "1.2.1", optional = true } + teloxide-macros = "0.2.1" [dev-dependencies] diff --git a/examples/dialogue_bot/src/main.rs b/examples/dialogue_bot/src/main.rs index 663c503c..b4f1d823 100644 --- a/examples/dialogue_bot/src/main.rs +++ b/examples/dialogue_bot/src/main.rs @@ -41,7 +41,7 @@ enum FavouriteMusic { impl FavouriteMusic { fn markup() -> ReplyKeyboardMarkup { - ReplyKeyboardMarkup::default().append_row(vec![ + ReplyKeyboardMarkup::default().one_time_keyboard(true).append_row(vec![ KeyboardButton::new("Rock"), KeyboardButton::new("Metal"), KeyboardButton::new("Pop"), diff --git a/examples/dialogue_bot_redis/Cargo.toml b/examples/dialogue_bot_redis/Cargo.toml new file mode 100644 index 00000000..5e10af40 --- /dev/null +++ b/examples/dialogue_bot_redis/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "dialogue_bot_redis" +version = "0.1.0" +authors = ["Temirkhan Myrzamadi "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +log = "0.4.8" +tokio = "0.2.9" +pretty_env_logger = "0.4.0" +smart-default = "0.6.0" +parse-display = "0.1.1" +# You can also choose "cbor-serializer" or built-in JSON serializer +teloxide = { path = "../../", features = ["redis-storage", "bincode-serializer"] } +serde = "1.0.104" + +[profile.release] +lto = true diff --git a/examples/dialogue_bot_redis/src/main.rs b/examples/dialogue_bot_redis/src/main.rs new file mode 100644 index 00000000..b1954c98 --- /dev/null +++ b/examples/dialogue_bot_redis/src/main.rs @@ -0,0 +1,210 @@ +// This is a bot that asks your full name, your age, your favourite kind of +// music and sends all the gathered information back. +// +// # Example +// ``` +// - Let's start! First, what's your full name? +// - Luke Skywalker +// - What a wonderful name! Your age? +// - 26 +// - Good. Now choose your favourite music +// *A keyboard of music kinds is displayed* +// *You select Metal* +// - Metal +// - Fine. Your full name: Luke Skywalker, your age: 26, your favourite music: Metal +// ``` + +#![allow(clippy::trivial_regex)] + +#[macro_use] +extern crate smart_default; + +use serde::{Deserialize, Serialize}; +use std::sync::Arc; + +use teloxide::{ + dispatching::dialogue::{RedisStorage, Serializer, Storage}, + prelude::*, + types::{KeyboardButton, ReplyKeyboardMarkup}, +}; + +use parse_display::{Display, FromStr}; + +// ============================================================================ +// [Favourite music kinds] +// ============================================================================ + +#[derive(Copy, Clone, Display, FromStr)] +enum FavouriteMusic { + Rock, + Metal, + Pop, + Other, +} + +impl FavouriteMusic { + fn markup() -> ReplyKeyboardMarkup { + ReplyKeyboardMarkup::default().one_time_keyboard(true).append_row(vec![ + KeyboardButton::new("Rock"), + KeyboardButton::new("Metal"), + KeyboardButton::new("Pop"), + KeyboardButton::new("Other"), + ]) + } +} + +// ============================================================================ +// [A type-safe finite automaton] +// ============================================================================ + +#[derive(Clone, Serialize, Deserialize)] +struct ReceiveAgeState { + full_name: String, +} + +#[derive(Clone, Serialize, Deserialize)] +struct ReceiveFavouriteMusicState { + data: ReceiveAgeState, + age: u8, +} + +#[derive(Display)] +#[display( + "Your full name: {data.data.full_name}, your age: {data.age}, your \ + favourite music: {favourite_music}" +)] +struct ExitState { + data: ReceiveFavouriteMusicState, + favourite_music: FavouriteMusic, +} + +#[derive(SmartDefault, Serialize, Deserialize)] +enum Dialogue { + #[default] + Start, + ReceiveFullName, + ReceiveAge(ReceiveAgeState), + ReceiveFavouriteMusic(ReceiveFavouriteMusicState), +} + +// ============================================================================ +// [Control a dialogue] +// ============================================================================ + +type Cx = DialogueDispatcherHandlerCx< + Message, + State, + >::Error, +>; +type Res = ResponseResult>; + +async fn start(cx: Cx<()>) -> Res { + cx.answer("Let's start! First, what's your full name?").send().await?; + next(Dialogue::ReceiveFullName) +} + +async fn full_name(cx: Cx<()>) -> Res { + match cx.update.text() { + None => { + cx.answer("Please, send me a text message!").send().await?; + next(Dialogue::ReceiveFullName) + } + Some(full_name) => { + cx.answer("What a wonderful name! Your age?").send().await?; + next(Dialogue::ReceiveAge(ReceiveAgeState { + full_name: full_name.to_owned(), + })) + } + } +} + +async fn age(cx: Cx) -> Res { + match cx.update.text().unwrap().parse() { + Ok(age) => { + cx.answer("Good. Now choose your favourite music:") + .reply_markup(FavouriteMusic::markup()) + .send() + .await?; + next(Dialogue::ReceiveFavouriteMusic(ReceiveFavouriteMusicState { + data: cx.dialogue.unwrap(), + age, + })) + } + Err(_) => { + cx.answer("Oh, please, enter a number!").send().await?; + next(Dialogue::ReceiveAge(cx.dialogue.unwrap())) + } + } +} + +async fn favourite_music(cx: Cx) -> Res { + match cx.update.text().unwrap().parse() { + Ok(favourite_music) => { + cx.answer(format!( + "Fine. {}", + ExitState { + data: cx.dialogue.as_ref().unwrap().clone(), + favourite_music + } + )) + .send() + .await?; + exit() + } + Err(_) => { + cx.answer("Oh, please, enter from the keyboard!").send().await?; + next(Dialogue::ReceiveFavouriteMusic(cx.dialogue.unwrap())) + } + } +} + +async fn handle_message(cx: Cx) -> Res { + let DialogueDispatcherHandlerCx { bot, update, dialogue } = cx; + match dialogue.unwrap() { + Dialogue::Start => { + start(DialogueDispatcherHandlerCx::new(bot, update, ())).await + } + Dialogue::ReceiveFullName => { + full_name(DialogueDispatcherHandlerCx::new(bot, update, ())).await + } + Dialogue::ReceiveAge(s) => { + age(DialogueDispatcherHandlerCx::new(bot, update, s)).await + } + Dialogue::ReceiveFavouriteMusic(s) => { + favourite_music(DialogueDispatcherHandlerCx::new(bot, update, s)) + .await + } + } +} + +// ============================================================================ +// [Run!] +// ============================================================================ + +#[tokio::main] +async fn main() { + run().await; +} + +async fn run() { + teloxide::enable_logging!(); + log::info!("Starting dialogue_bot!"); + + let bot = Bot::from_env(); + + Dispatcher::new(bot) + .messages_handler(DialogueDispatcher::with_storage( + |cx| async move { + handle_message(cx).await.expect("Something wrong with the bot!") + }, + Arc::new( + // You can also choose Serializer::JSON or Serializer::Bincode + // All serializer but JSON require enabling feature "serializer-", + // e. g. "serializer-cbor" or "serializer-bincode" + RedisStorage::open("redis://127.0.0.1:6379", Serializer::CBOR) + .unwrap(), + ), + )) + .dispatch() + .await; +} diff --git a/src/dispatching/dialogue/mod.rs b/src/dispatching/dialogue/mod.rs index 092b8d6a..99815671 100644 --- a/src/dispatching/dialogue/mod.rs +++ b/src/dispatching/dialogue/mod.rs @@ -54,4 +54,6 @@ pub use dialogue_dispatcher_handler::DialogueDispatcherHandler; pub use dialogue_dispatcher_handler_cx::DialogueDispatcherHandlerCx; pub use dialogue_stage::{exit, next, DialogueStage}; pub use get_chat_id::GetChatId; -pub use storage::{InMemStorage, Storage}; +#[cfg(feature = "redis-storage")] +pub use storage::RedisStorage; +pub use storage::{InMemStorage, Serializer, Storage}; diff --git a/src/dispatching/dialogue/storage/mod.rs b/src/dispatching/dialogue/storage/mod.rs index acbd9888..6a5ccac0 100644 --- a/src/dispatching/dialogue/storage/mod.rs +++ b/src/dispatching/dialogue/storage/mod.rs @@ -1,7 +1,16 @@ +pub mod serializer; + mod in_mem_storage; +#[cfg(feature = "redis-storage")] +mod redis_storage; + use futures::future::BoxFuture; + pub use in_mem_storage::InMemStorage; +#[cfg(feature = "redis-storage")] +pub use redis_storage::RedisStorage; +pub use serializer::Serializer; use std::sync::Arc; /// A storage of dialogues. diff --git a/src/dispatching/dialogue/storage/redis_storage.rs b/src/dispatching/dialogue/storage/redis_storage.rs new file mode 100644 index 00000000..2dad6871 --- /dev/null +++ b/src/dispatching/dialogue/storage/redis_storage.rs @@ -0,0 +1,85 @@ +use super::{ + serializer::{self, Serializer}, + Storage, +}; +use futures::future::BoxFuture; +use redis::{AsyncCommands, FromRedisValue, IntoConnectionInfo}; +use serde::{de::DeserializeOwned, Serialize}; +use std::sync::Arc; +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum Error { + #[error("{0}")] + SerdeError(#[from] serializer::Error), + #[error("error from Redis: {0}")] + RedisError(#[from] redis::RedisError), +} + +type Result = std::result::Result; + +pub struct RedisStorage { + client: redis::Client, + serializer: Serializer, +} + +impl RedisStorage { + pub fn open( + url: impl IntoConnectionInfo, + serializer: Serializer, + ) -> Result { + Ok(Self { client: redis::Client::open(url)?, serializer }) + } +} + +impl Storage for RedisStorage +where + D: Send + Serialize + DeserializeOwned + 'static, +{ + type Error = Error; + + // `.del().ignore()` is much more readable than `.del()\n.ignore()` + #[rustfmt::skip] + fn remove_dialogue( + self: Arc, + chat_id: i64, + ) -> BoxFuture<'static, Result>> { + Box::pin(async move { + let mut conn = self.client.get_async_connection().await?; + let res = redis::pipe() + .atomic() + .get(chat_id) + .del(chat_id).ignore() + .query_async::<_, redis::Value>(&mut conn) + .await?; + // We're expecting `.pipe()` to return us an exactly one result in bulk, + // so all other branches should be unreachable + match res { + redis::Value::Bulk(bulk) if bulk.len() == 1 => { + Ok( + Option::>::from_redis_value(&bulk[0])? + .map(|v| self.serializer.deserialize(&v)) + .transpose()? + ) + }, + _ => unreachable!() + } + }) + } + + fn update_dialogue( + self: Arc, + chat_id: i64, + dialogue: D, + ) -> BoxFuture<'static, Result>> { + Box::pin(async move { + let mut conn = self.client.get_async_connection().await?; + let dialogue = self.serializer.serialize(&dialogue)?; + Ok(conn + .getset::<_, Vec, Option>>(chat_id, dialogue) + .await? + .map(|d| self.serializer.deserialize(&d)) + .transpose()?) + }) + } +} diff --git a/src/dispatching/dialogue/storage/serializer.rs b/src/dispatching/dialogue/storage/serializer.rs new file mode 100644 index 00000000..5ac63a7b --- /dev/null +++ b/src/dispatching/dialogue/storage/serializer.rs @@ -0,0 +1,53 @@ +use serde::{de::DeserializeOwned, ser::Serialize}; +use thiserror::Error; +use Serializer::*; + +#[derive(Debug, Error)] +pub enum Error { + #[error("failed parsing/serializing JSON: {0}")] + JSONError(#[from] serde_json::Error), + #[cfg(feature = "cbor-serializer")] + #[error("failed parsing/serializing CBOR: {0}")] + CBORError(#[from] serde_cbor::Error), + #[cfg(feature = "bincode-serializer")] + #[error("failed parsing/serializing Bincode: {0}")] + BincodeError(#[from] bincode::Error), +} + +type Result = std::result::Result; + +pub enum Serializer { + JSON, + #[cfg(feature = "cbor-serializer")] + CBOR, + #[cfg(feature = "bincode-serializer")] + Bincode, +} + +impl Serializer { + pub fn serialize(&self, val: &D) -> Result> + where + D: Serialize, + { + Ok(match self { + JSON => serde_json::to_vec(val)?, + #[cfg(feature = "cbor-serializer")] + CBOR => serde_cbor::to_vec(val)?, + #[cfg(feature = "bincode-serializer")] + Bincode => bincode::serialize(val)?, + }) + } + + pub fn deserialize<'de, D>(&self, data: &'de [u8]) -> Result + where + D: DeserializeOwned, + { + Ok(match self { + JSON => serde_json::from_slice(data)?, + #[cfg(feature = "cbor-serializer")] + CBOR => serde_cbor::from_slice(data)?, + #[cfg(feature = "bincode-serializer")] + Bincode => bincode::deserialize(data)?, + }) + } +} From a67d896d26073d24b344979b4451782fd0891913 Mon Sep 17 00:00:00 2001 From: Maximilian Siling Date: Sun, 19 Apr 2020 19:24:24 +0300 Subject: [PATCH 02/29] Don't reconnect to Redis every time --- examples/dialogue_bot_redis/src/main.rs | 10 ++++---- .../dialogue/storage/redis_storage.rs | 23 ++++++++++++------- 2 files changed, 21 insertions(+), 12 deletions(-) diff --git a/examples/dialogue_bot_redis/src/main.rs b/examples/dialogue_bot_redis/src/main.rs index b1954c98..c42aea19 100644 --- a/examples/dialogue_bot_redis/src/main.rs +++ b/examples/dialogue_bot_redis/src/main.rs @@ -198,10 +198,12 @@ async fn run() { handle_message(cx).await.expect("Something wrong with the bot!") }, Arc::new( - // You can also choose Serializer::JSON or Serializer::Bincode - // All serializer but JSON require enabling feature "serializer-", - // e. g. "serializer-cbor" or "serializer-bincode" - RedisStorage::open("redis://127.0.0.1:6379", Serializer::CBOR) + // You can also choose Serializer::JSON or Serializer::CBOR + // All serializer but JSON require enabling feature + // "serializer-", e. g. "serializer-cbor" + // or "serializer-bincode" + RedisStorage::open("redis://127.0.0.1:6379", Serializer::Bincode) + .await .unwrap(), ), )) diff --git a/src/dispatching/dialogue/storage/redis_storage.rs b/src/dispatching/dialogue/storage/redis_storage.rs index 2dad6871..b6f45a3a 100644 --- a/src/dispatching/dialogue/storage/redis_storage.rs +++ b/src/dispatching/dialogue/storage/redis_storage.rs @@ -5,8 +5,9 @@ use super::{ use futures::future::BoxFuture; use redis::{AsyncCommands, FromRedisValue, IntoConnectionInfo}; use serde::{de::DeserializeOwned, Serialize}; -use std::sync::Arc; +use std::{ops::DerefMut, sync::Arc}; use thiserror::Error; +use tokio::sync::Mutex; #[derive(Debug, Error)] pub enum Error { @@ -19,16 +20,21 @@ pub enum Error { type Result = std::result::Result; pub struct RedisStorage { - client: redis::Client, + conn: Mutex, serializer: Serializer, } impl RedisStorage { - pub fn open( + pub async fn open( url: impl IntoConnectionInfo, serializer: Serializer, ) -> Result { - Ok(Self { client: redis::Client::open(url)?, serializer }) + Ok(Self { + conn: Mutex::new( + redis::Client::open(url)?.get_async_connection().await?, + ), + serializer, + }) } } @@ -45,12 +51,11 @@ where chat_id: i64, ) -> BoxFuture<'static, Result>> { Box::pin(async move { - let mut conn = self.client.get_async_connection().await?; let res = redis::pipe() .atomic() .get(chat_id) .del(chat_id).ignore() - .query_async::<_, redis::Value>(&mut conn) + .query_async::<_, redis::Value>(self.conn.lock().await.deref_mut()) .await?; // We're expecting `.pipe()` to return us an exactly one result in bulk, // so all other branches should be unreachable @@ -73,9 +78,11 @@ where dialogue: D, ) -> BoxFuture<'static, Result>> { Box::pin(async move { - let mut conn = self.client.get_async_connection().await?; let dialogue = self.serializer.serialize(&dialogue)?; - Ok(conn + Ok(self + .conn + .lock() + .await .getset::<_, Vec, Option>>(chat_id, dialogue) .await? .map(|d| self.serializer.deserialize(&d)) From 82d0958c9133a20b0cd84ef528ba30d5a2f43c57 Mon Sep 17 00:00:00 2001 From: Maximilian Siling Date: Sun, 19 Apr 2020 20:06:49 +0300 Subject: [PATCH 03/29] Make Serializer a trait, so anyone can implement it --- examples/dialogue_bot_redis/src/main.rs | 6 +- src/dispatching/dialogue/mod.rs | 2 +- .../dialogue/storage/redis_storage.rs | 76 +++++++++------ .../dialogue/storage/serializer.rs | 96 ++++++++++--------- 4 files changed, 102 insertions(+), 78 deletions(-) diff --git a/examples/dialogue_bot_redis/src/main.rs b/examples/dialogue_bot_redis/src/main.rs index c42aea19..cc8bdd59 100644 --- a/examples/dialogue_bot_redis/src/main.rs +++ b/examples/dialogue_bot_redis/src/main.rs @@ -23,7 +23,7 @@ use serde::{Deserialize, Serialize}; use std::sync::Arc; use teloxide::{ - dispatching::dialogue::{RedisStorage, Serializer, Storage}, + dispatching::dialogue::{serializer::Bincode, RedisStorage, Storage}, prelude::*, types::{KeyboardButton, ReplyKeyboardMarkup}, }; @@ -94,7 +94,7 @@ enum Dialogue { type Cx = DialogueDispatcherHandlerCx< Message, State, - >::Error, + as Storage>::Error, >; type Res = ResponseResult>; @@ -202,7 +202,7 @@ async fn run() { // All serializer but JSON require enabling feature // "serializer-", e. g. "serializer-cbor" // or "serializer-bincode" - RedisStorage::open("redis://127.0.0.1:6379", Serializer::Bincode) + RedisStorage::open("redis://127.0.0.1:6379", Bincode) .await .unwrap(), ), diff --git a/src/dispatching/dialogue/mod.rs b/src/dispatching/dialogue/mod.rs index 99815671..39de77e9 100644 --- a/src/dispatching/dialogue/mod.rs +++ b/src/dispatching/dialogue/mod.rs @@ -56,4 +56,4 @@ pub use dialogue_stage::{exit, next, DialogueStage}; pub use get_chat_id::GetChatId; #[cfg(feature = "redis-storage")] pub use storage::RedisStorage; -pub use storage::{InMemStorage, Serializer, Storage}; +pub use storage::{serializer, InMemStorage, Serializer, Storage}; diff --git a/src/dispatching/dialogue/storage/redis_storage.rs b/src/dispatching/dialogue/storage/redis_storage.rs index b6f45a3a..273902de 100644 --- a/src/dispatching/dialogue/storage/redis_storage.rs +++ b/src/dispatching/dialogue/storage/redis_storage.rs @@ -1,34 +1,37 @@ -use super::{ - serializer::{self, Serializer}, - Storage, -}; +use super::{serializer::Serializer, Storage}; use futures::future::BoxFuture; use redis::{AsyncCommands, FromRedisValue, IntoConnectionInfo}; use serde::{de::DeserializeOwned, Serialize}; -use std::{ops::DerefMut, sync::Arc}; +use std::{ + convert::Infallible, + fmt::{Debug, Display}, + ops::DerefMut, + sync::Arc, +}; use thiserror::Error; use tokio::sync::Mutex; #[derive(Debug, Error)] -pub enum Error { - #[error("{0}")] - SerdeError(#[from] serializer::Error), +pub enum Error +where + SE: Debug + Display, +{ + #[error("parsing/serializing error: {0}")] + SerdeError(SE), #[error("error from Redis: {0}")] RedisError(#[from] redis::RedisError), } -type Result = std::result::Result; - -pub struct RedisStorage { +pub struct RedisStorage { conn: Mutex, - serializer: Serializer, + serializer: S, } -impl RedisStorage { +impl RedisStorage { pub async fn open( url: impl IntoConnectionInfo, - serializer: Serializer, - ) -> Result { + serializer: S, + ) -> Result> { Ok(Self { conn: Mutex::new( redis::Client::open(url)?.get_async_connection().await?, @@ -38,36 +41,42 @@ impl RedisStorage { } } -impl Storage for RedisStorage +impl Storage for RedisStorage where + S: Send + Sync + Serializer + 'static, D: Send + Serialize + DeserializeOwned + 'static, + >::Error: Debug + Display, { - type Error = Error; + type Error = Error<>::Error>; // `.del().ignore()` is much more readable than `.del()\n.ignore()` #[rustfmt::skip] fn remove_dialogue( self: Arc, chat_id: i64, - ) -> BoxFuture<'static, Result>> { + ) -> BoxFuture<'static, Result, Self::Error>> { Box::pin(async move { let res = redis::pipe() .atomic() .get(chat_id) .del(chat_id).ignore() - .query_async::<_, redis::Value>(self.conn.lock().await.deref_mut()) + .query_async::<_, redis::Value>( + self.conn.lock().await.deref_mut(), + ) .await?; - // We're expecting `.pipe()` to return us an exactly one result in bulk, - // so all other branches should be unreachable + // We're expecting `.pipe()` to return us an exactly one result in + // bulk, so all other branches should be unreachable match res { redis::Value::Bulk(bulk) if bulk.len() == 1 => { - Ok( - Option::>::from_redis_value(&bulk[0])? - .map(|v| self.serializer.deserialize(&v)) - .transpose()? - ) - }, - _ => unreachable!() + Ok(Option::>::from_redis_value(&bulk[0])? + .map(|v| { + self.serializer + .deserialize(&v) + .map_err(Error::SerdeError) + }) + .transpose()?) + } + _ => unreachable!(), } }) } @@ -76,16 +85,21 @@ where self: Arc, chat_id: i64, dialogue: D, - ) -> BoxFuture<'static, Result>> { + ) -> BoxFuture<'static, Result, Self::Error>> { Box::pin(async move { - let dialogue = self.serializer.serialize(&dialogue)?; + let dialogue = self + .serializer + .serialize(&dialogue) + .map_err(Error::SerdeError)?; Ok(self .conn .lock() .await .getset::<_, Vec, Option>>(chat_id, dialogue) .await? - .map(|d| self.serializer.deserialize(&d)) + .map(|d| { + self.serializer.deserialize(&d).map_err(Error::SerdeError) + }) .transpose()?) }) } diff --git a/src/dispatching/dialogue/storage/serializer.rs b/src/dispatching/dialogue/storage/serializer.rs index 5ac63a7b..ba4925ab 100644 --- a/src/dispatching/dialogue/storage/serializer.rs +++ b/src/dispatching/dialogue/storage/serializer.rs @@ -1,53 +1,63 @@ use serde::{de::DeserializeOwned, ser::Serialize}; -use thiserror::Error; -use Serializer::*; -#[derive(Debug, Error)] -pub enum Error { - #[error("failed parsing/serializing JSON: {0}")] - JSONError(#[from] serde_json::Error), - #[cfg(feature = "cbor-serializer")] - #[error("failed parsing/serializing CBOR: {0}")] - CBORError(#[from] serde_cbor::Error), - #[cfg(feature = "bincode-serializer")] - #[error("failed parsing/serializing Bincode: {0}")] - BincodeError(#[from] bincode::Error), +pub trait Serializer { + type Error; + + fn serialize(&self, val: &D) -> Result, Self::Error>; + fn deserialize(&self, data: &[u8]) -> Result; } -type Result = std::result::Result; +pub struct JSON; -pub enum Serializer { - JSON, - #[cfg(feature = "cbor-serializer")] - CBOR, - #[cfg(feature = "bincode-serializer")] - Bincode, -} +impl Serializer for JSON +where + D: Serialize + DeserializeOwned, +{ + type Error = serde_json::Error; -impl Serializer { - pub fn serialize(&self, val: &D) -> Result> - where - D: Serialize, - { - Ok(match self { - JSON => serde_json::to_vec(val)?, - #[cfg(feature = "cbor-serializer")] - CBOR => serde_cbor::to_vec(val)?, - #[cfg(feature = "bincode-serializer")] - Bincode => bincode::serialize(val)?, - }) + fn serialize(&self, val: &D) -> Result, Self::Error> { + serde_json::to_vec(val) } - pub fn deserialize<'de, D>(&self, data: &'de [u8]) -> Result - where - D: DeserializeOwned, - { - Ok(match self { - JSON => serde_json::from_slice(data)?, - #[cfg(feature = "cbor-serializer")] - CBOR => serde_cbor::from_slice(data)?, - #[cfg(feature = "bincode-serializer")] - Bincode => bincode::deserialize(data)?, - }) + fn deserialize(&self, data: &[u8]) -> Result { + serde_json::from_slice(data) + } +} + +#[cfg(feature = "cbor-serializer")] +pub struct CBOR; + +#[cfg(feature = "cbor-serializer")] +impl Serializer for CBOR +where + D: Serialize + DeserializeOwned, +{ + type Error = serde_cbor::Error; + + fn serialize(&self, val: &D) -> Result, Self::Error> { + serde_cbor::to_vec(val) + } + + fn deserialize(&self, data: &[u8]) -> Result { + serde_cbor::from_slice(data) + } +} + +#[cfg(feature = "bincode-serializer")] +pub struct Bincode; + +#[cfg(feature = "bincode-serializer")] +impl Serializer for Bincode +where + D: Serialize + DeserializeOwned, +{ + type Error = bincode::Error; + + fn serialize(&self, val: &D) -> Result, Self::Error> { + bincode::serialize(val) + } + + fn deserialize(&self, data: &[u8]) -> Result { + bincode::deserialize(data) } } From c58aa22f7bcdb5b82492d18cf1d76525e06d95c9 Mon Sep 17 00:00:00 2001 From: Maximilian Siling Date: Sun, 19 Apr 2020 20:47:12 +0300 Subject: [PATCH 04/29] Simpler example for Redis --- examples/dialogue_bot_redis/Cargo.toml | 20 --- examples/dialogue_bot_redis/src/main.rs | 212 ------------------------ examples/redis_remember_bot/Cargo.toml | 13 ++ examples/redis_remember_bot/src/main.rs | 119 +++++++++++++ 4 files changed, 132 insertions(+), 232 deletions(-) delete mode 100644 examples/dialogue_bot_redis/Cargo.toml delete mode 100644 examples/dialogue_bot_redis/src/main.rs create mode 100644 examples/redis_remember_bot/Cargo.toml create mode 100644 examples/redis_remember_bot/src/main.rs diff --git a/examples/dialogue_bot_redis/Cargo.toml b/examples/dialogue_bot_redis/Cargo.toml deleted file mode 100644 index 5e10af40..00000000 --- a/examples/dialogue_bot_redis/Cargo.toml +++ /dev/null @@ -1,20 +0,0 @@ -[package] -name = "dialogue_bot_redis" -version = "0.1.0" -authors = ["Temirkhan Myrzamadi "] -edition = "2018" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -log = "0.4.8" -tokio = "0.2.9" -pretty_env_logger = "0.4.0" -smart-default = "0.6.0" -parse-display = "0.1.1" -# You can also choose "cbor-serializer" or built-in JSON serializer -teloxide = { path = "../../", features = ["redis-storage", "bincode-serializer"] } -serde = "1.0.104" - -[profile.release] -lto = true diff --git a/examples/dialogue_bot_redis/src/main.rs b/examples/dialogue_bot_redis/src/main.rs deleted file mode 100644 index cc8bdd59..00000000 --- a/examples/dialogue_bot_redis/src/main.rs +++ /dev/null @@ -1,212 +0,0 @@ -// This is a bot that asks your full name, your age, your favourite kind of -// music and sends all the gathered information back. -// -// # Example -// ``` -// - Let's start! First, what's your full name? -// - Luke Skywalker -// - What a wonderful name! Your age? -// - 26 -// - Good. Now choose your favourite music -// *A keyboard of music kinds is displayed* -// *You select Metal* -// - Metal -// - Fine. Your full name: Luke Skywalker, your age: 26, your favourite music: Metal -// ``` - -#![allow(clippy::trivial_regex)] - -#[macro_use] -extern crate smart_default; - -use serde::{Deserialize, Serialize}; -use std::sync::Arc; - -use teloxide::{ - dispatching::dialogue::{serializer::Bincode, RedisStorage, Storage}, - prelude::*, - types::{KeyboardButton, ReplyKeyboardMarkup}, -}; - -use parse_display::{Display, FromStr}; - -// ============================================================================ -// [Favourite music kinds] -// ============================================================================ - -#[derive(Copy, Clone, Display, FromStr)] -enum FavouriteMusic { - Rock, - Metal, - Pop, - Other, -} - -impl FavouriteMusic { - fn markup() -> ReplyKeyboardMarkup { - ReplyKeyboardMarkup::default().one_time_keyboard(true).append_row(vec![ - KeyboardButton::new("Rock"), - KeyboardButton::new("Metal"), - KeyboardButton::new("Pop"), - KeyboardButton::new("Other"), - ]) - } -} - -// ============================================================================ -// [A type-safe finite automaton] -// ============================================================================ - -#[derive(Clone, Serialize, Deserialize)] -struct ReceiveAgeState { - full_name: String, -} - -#[derive(Clone, Serialize, Deserialize)] -struct ReceiveFavouriteMusicState { - data: ReceiveAgeState, - age: u8, -} - -#[derive(Display)] -#[display( - "Your full name: {data.data.full_name}, your age: {data.age}, your \ - favourite music: {favourite_music}" -)] -struct ExitState { - data: ReceiveFavouriteMusicState, - favourite_music: FavouriteMusic, -} - -#[derive(SmartDefault, Serialize, Deserialize)] -enum Dialogue { - #[default] - Start, - ReceiveFullName, - ReceiveAge(ReceiveAgeState), - ReceiveFavouriteMusic(ReceiveFavouriteMusicState), -} - -// ============================================================================ -// [Control a dialogue] -// ============================================================================ - -type Cx = DialogueDispatcherHandlerCx< - Message, - State, - as Storage>::Error, ->; -type Res = ResponseResult>; - -async fn start(cx: Cx<()>) -> Res { - cx.answer("Let's start! First, what's your full name?").send().await?; - next(Dialogue::ReceiveFullName) -} - -async fn full_name(cx: Cx<()>) -> Res { - match cx.update.text() { - None => { - cx.answer("Please, send me a text message!").send().await?; - next(Dialogue::ReceiveFullName) - } - Some(full_name) => { - cx.answer("What a wonderful name! Your age?").send().await?; - next(Dialogue::ReceiveAge(ReceiveAgeState { - full_name: full_name.to_owned(), - })) - } - } -} - -async fn age(cx: Cx) -> Res { - match cx.update.text().unwrap().parse() { - Ok(age) => { - cx.answer("Good. Now choose your favourite music:") - .reply_markup(FavouriteMusic::markup()) - .send() - .await?; - next(Dialogue::ReceiveFavouriteMusic(ReceiveFavouriteMusicState { - data: cx.dialogue.unwrap(), - age, - })) - } - Err(_) => { - cx.answer("Oh, please, enter a number!").send().await?; - next(Dialogue::ReceiveAge(cx.dialogue.unwrap())) - } - } -} - -async fn favourite_music(cx: Cx) -> Res { - match cx.update.text().unwrap().parse() { - Ok(favourite_music) => { - cx.answer(format!( - "Fine. {}", - ExitState { - data: cx.dialogue.as_ref().unwrap().clone(), - favourite_music - } - )) - .send() - .await?; - exit() - } - Err(_) => { - cx.answer("Oh, please, enter from the keyboard!").send().await?; - next(Dialogue::ReceiveFavouriteMusic(cx.dialogue.unwrap())) - } - } -} - -async fn handle_message(cx: Cx) -> Res { - let DialogueDispatcherHandlerCx { bot, update, dialogue } = cx; - match dialogue.unwrap() { - Dialogue::Start => { - start(DialogueDispatcherHandlerCx::new(bot, update, ())).await - } - Dialogue::ReceiveFullName => { - full_name(DialogueDispatcherHandlerCx::new(bot, update, ())).await - } - Dialogue::ReceiveAge(s) => { - age(DialogueDispatcherHandlerCx::new(bot, update, s)).await - } - Dialogue::ReceiveFavouriteMusic(s) => { - favourite_music(DialogueDispatcherHandlerCx::new(bot, update, s)) - .await - } - } -} - -// ============================================================================ -// [Run!] -// ============================================================================ - -#[tokio::main] -async fn main() { - run().await; -} - -async fn run() { - teloxide::enable_logging!(); - log::info!("Starting dialogue_bot!"); - - let bot = Bot::from_env(); - - Dispatcher::new(bot) - .messages_handler(DialogueDispatcher::with_storage( - |cx| async move { - handle_message(cx).await.expect("Something wrong with the bot!") - }, - Arc::new( - // You can also choose Serializer::JSON or Serializer::CBOR - // All serializer but JSON require enabling feature - // "serializer-", e. g. "serializer-cbor" - // or "serializer-bincode" - RedisStorage::open("redis://127.0.0.1:6379", Bincode) - .await - .unwrap(), - ), - )) - .dispatch() - .await; -} diff --git a/examples/redis_remember_bot/Cargo.toml b/examples/redis_remember_bot/Cargo.toml new file mode 100644 index 00000000..a8f6bd3c --- /dev/null +++ b/examples/redis_remember_bot/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "redis_remember_bot" +version = "0.1.0" +authors = ["Maximilian Siling "] +edition = "2018" + +[dependencies] +tokio = "0.2.9" +smart-default = "0.6.0" +# You can also choose "cbor-serializer" or built-in JSON serializer +teloxide = { path = "../../", features = ["redis-storage", "bincode-serializer"] } +serde = "1.0.104" +thiserror = "1.0.15" diff --git a/examples/redis_remember_bot/src/main.rs b/examples/redis_remember_bot/src/main.rs new file mode 100644 index 00000000..0de999e8 --- /dev/null +++ b/examples/redis_remember_bot/src/main.rs @@ -0,0 +1,119 @@ +use serde::{Deserialize, Serialize}; +use smart_default::SmartDefault; +use std::sync::Arc; +use teloxide::{ + dispatching::dialogue::{serializer::Bincode, RedisStorage, Storage}, + prelude::*, +}; +use thiserror::Error; + +#[derive(SmartDefault, Serialize, Deserialize)] +enum Dialogue { + #[default] + Start, + HaveNumber(i32), +} + +type StorageError = as Storage>::Error; + +#[derive(Debug, Error)] +enum Error { + #[error("error from Telegram: {0}")] + TelegramError(#[from] RequestError), + #[error("error from storage: {0}")] + StorageError(#[from] StorageError), +} + +type Cx = DialogueDispatcherHandlerCx; + +type Res = Result, Error>; + +async fn handle_message(cx: Cx) -> Res { + let DialogueDispatcherHandlerCx { bot, update, dialogue } = cx; + let text = match update.text() { + Some(text) => text, + None => { + bot.send_message( + update.chat_id(), + "Please, send me a text message", + ) + .send() + .await?; + return next(Dialogue::Start); + } + }; + + match dialogue? { + Dialogue::Start => { + if let Ok(number) = text.parse() { + bot.send_message( + update.chat_id(), + format!( + "Remembered number {}. Now use /get or /reset", + number + ), + ) + .send() + .await?; + next(Dialogue::HaveNumber(number)) + } else { + bot.send_message(update.chat_id(), "Please, send me a number") + .send() + .await?; + next(Dialogue::Start) + } + } + Dialogue::HaveNumber(num) => { + if text.starts_with("/get") { + bot.send_message( + update.chat_id(), + format!("Here is your number: {}", num), + ) + .send() + .await?; + next(Dialogue::HaveNumber(num)) + } else if text.starts_with("/reset") { + bot.send_message(update.chat_id(), format!("Resetted number")) + .send() + .await?; + next(Dialogue::Start) + } else { + bot.send_message( + update.chat_id(), + "Please, send /get or /reset", + ) + .send() + .await?; + next(Dialogue::HaveNumber(num)) + } + } + } +} + +#[tokio::main] +async fn main() { + run().await; +} + +async fn run() { + let bot = Bot::from_env(); + Dispatcher::new(bot) + .messages_handler(DialogueDispatcher::with_storage( + |cx| async move { + handle_message(cx) + .await + .expect("Something is wrong with the bot!") + }, + Arc::new( + // You can also choose serializer::JSON or serializer::CBOR + // All serializers but JSON require enabling feature + // "serializer-", e. g. "serializer-cbor" + // or "serializer-bincode" + RedisStorage::open("redis://127.0.0.1:6379", Bincode) + .await + .unwrap(), + ), + )) + .dispatch() + .await; +} From ee9166999a0e5a1a5fa980e9b755c18cc103eed8 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Fri, 26 Jun 2020 18:47:42 +0600 Subject: [PATCH 05/29] Remove a redundant import --- examples/redis_remember_bot/src/states.rs | 0 examples/redis_remember_bot/src/transitions.rs | 0 src/dispatching/dialogue/mod.rs | 2 -- 3 files changed, 2 deletions(-) create mode 100644 examples/redis_remember_bot/src/states.rs create mode 100644 examples/redis_remember_bot/src/transitions.rs diff --git a/examples/redis_remember_bot/src/states.rs b/examples/redis_remember_bot/src/states.rs new file mode 100644 index 00000000..e69de29b diff --git a/examples/redis_remember_bot/src/transitions.rs b/examples/redis_remember_bot/src/transitions.rs new file mode 100644 index 00000000..e69de29b diff --git a/src/dispatching/dialogue/mod.rs b/src/dispatching/dialogue/mod.rs index da806612..8b415171 100644 --- a/src/dispatching/dialogue/mod.rs +++ b/src/dispatching/dialogue/mod.rs @@ -60,8 +60,6 @@ pub use get_chat_id::GetChatId; pub use storage::RedisStorage; pub use storage::{serializer, InMemStorage, Serializer, Storage}; -pub use storage::{InMemStorage, Storage}; - /// Dispatches a dialogue state into transition functions. /// /// # Example From 03ab2472620e034f29dfefc2a7bb9d522e63437b Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Wed, 1 Jul 2020 19:25:10 +0600 Subject: [PATCH 06/29] Update redis_remember_bot --- examples/dialogue_bot/src/main.rs | 4 +- examples/redis_remember_bot/Cargo.toml | 5 +- examples/redis_remember_bot/src/main.rs | 84 ++++--------------- examples/redis_remember_bot/src/states.rs | 23 +++++ .../redis_remember_bot/src/transitions.rs | 42 ++++++++++ 5 files changed, 89 insertions(+), 69 deletions(-) diff --git a/examples/dialogue_bot/src/main.rs b/examples/dialogue_bot/src/main.rs index e0447004..f9b9683d 100644 --- a/examples/dialogue_bot/src/main.rs +++ b/examples/dialogue_bot/src/main.rs @@ -45,9 +45,9 @@ async fn run() { Dispatcher::new(bot) .messages_handler(DialogueDispatcher::new( - |cx: DialogueWithCx| async move { + |input: TransitionIn| async move { // Unwrap without panic because of std::convert::Infallible. - dispatch(cx.cx, cx.dialogue.unwrap()) + dispatch(input.cx, input.dialogue.unwrap()) .await .expect("Something wrong with the bot!") }, diff --git a/examples/redis_remember_bot/Cargo.toml b/examples/redis_remember_bot/Cargo.toml index a8f6bd3c..6a91b292 100644 --- a/examples/redis_remember_bot/Cargo.toml +++ b/examples/redis_remember_bot/Cargo.toml @@ -6,8 +6,11 @@ edition = "2018" [dependencies] tokio = "0.2.9" -smart-default = "0.6.0" + # You can also choose "cbor-serializer" or built-in JSON serializer teloxide = { path = "../../", features = ["redis-storage", "bincode-serializer"] } serde = "1.0.104" + thiserror = "1.0.15" +smart-default = "0.6.0" +derive_more = "0.99.9" \ No newline at end of file diff --git a/examples/redis_remember_bot/src/main.rs b/examples/redis_remember_bot/src/main.rs index 0de999e8..2eb8aa03 100644 --- a/examples/redis_remember_bot/src/main.rs +++ b/examples/redis_remember_bot/src/main.rs @@ -1,5 +1,14 @@ -use serde::{Deserialize, Serialize}; -use smart_default::SmartDefault; +#[macro_use] +extern crate smart_default; +#[macro_use] +extern crate derive_more; + +mod states; +mod transitions; + +use states::*; +use transitions::*; + use std::sync::Arc; use teloxide::{ dispatching::dialogue::{serializer::Bincode, RedisStorage, Storage}, @@ -7,13 +16,6 @@ use teloxide::{ }; use thiserror::Error; -#[derive(SmartDefault, Serialize, Deserialize)] -enum Dialogue { - #[default] - Start, - HaveNumber(i32), -} - type StorageError = as Storage>::Error; #[derive(Debug, Error)] @@ -24,70 +26,20 @@ enum Error { StorageError(#[from] StorageError), } -type Cx = DialogueDispatcherHandlerCx; +type In = TransitionIn; -type Res = Result, Error>; +async fn handle_message(input: In) -> Out { + let (cx, dialogue) = input.unpack(); -async fn handle_message(cx: Cx) -> Res { - let DialogueDispatcherHandlerCx { bot, update, dialogue } = cx; - let text = match update.text() { + let text = match cx.update.text_owned() { Some(text) => text, None => { - bot.send_message( - update.chat_id(), - "Please, send me a text message", - ) - .send() - .await?; - return next(Dialogue::Start); + cx.answer_str("Please, send me a text message").await?; + return next(StartState); } }; - match dialogue? { - Dialogue::Start => { - if let Ok(number) = text.parse() { - bot.send_message( - update.chat_id(), - format!( - "Remembered number {}. Now use /get or /reset", - number - ), - ) - .send() - .await?; - next(Dialogue::HaveNumber(number)) - } else { - bot.send_message(update.chat_id(), "Please, send me a number") - .send() - .await?; - next(Dialogue::Start) - } - } - Dialogue::HaveNumber(num) => { - if text.starts_with("/get") { - bot.send_message( - update.chat_id(), - format!("Here is your number: {}", num), - ) - .send() - .await?; - next(Dialogue::HaveNumber(num)) - } else if text.starts_with("/reset") { - bot.send_message(update.chat_id(), format!("Resetted number")) - .send() - .await?; - next(Dialogue::Start) - } else { - bot.send_message( - update.chat_id(), - "Please, send /get or /reset", - ) - .send() - .await?; - next(Dialogue::HaveNumber(num)) - } - } - } + dispatch(cx, dialogue, &text).await } #[tokio::main] diff --git a/examples/redis_remember_bot/src/states.rs b/examples/redis_remember_bot/src/states.rs index e69de29b..142e823a 100644 --- a/examples/redis_remember_bot/src/states.rs +++ b/examples/redis_remember_bot/src/states.rs @@ -0,0 +1,23 @@ +use teloxide::prelude::*; + +use serde::{Deserialize, Serialize}; + +#[derive(Default, Serialize, Deserialize)] +pub struct StartState; + +#[derive(Serialize, Deserialize)] +pub struct HaveNumberState { + rest: StartState, + pub number: i32, +} + +up!( + StartState + [number: i32] -> HaveNumberState, +); + +#[derive(SmartDefault, From, Serialize, Deserialize)] +pub enum Dialogue { + #[default] + Start(StartState), + HaveNumber(HaveNumberState), +} diff --git a/examples/redis_remember_bot/src/transitions.rs b/examples/redis_remember_bot/src/transitions.rs index e69de29b..a30c906a 100644 --- a/examples/redis_remember_bot/src/transitions.rs +++ b/examples/redis_remember_bot/src/transitions.rs @@ -0,0 +1,42 @@ +use teloxide::prelude::*; + +use super::states::*; + +pub type Cx = UpdateWithCx; +pub type Out = TransitionOut; + +async fn start(cx: Cx, state: StartState, text: &str) -> Out { + if let Ok(number) = text.parse() { + cx.answer_str(format!( + "Remembered number {}. Now use /get or /reset", + number + )) + .await?; + next(state.up(number)) + } else { + cx.answer_str("Please, send me a number").await?; + next(state) + } +} + +async fn have_number(cx: Cx, state: HaveNumberState, text: &str) -> Out { + let num = state.number; + + if text.starts_with("/get") { + cx.answer_str(format!("Here is your number: {}", num)).await?; + next(state) + } else if text.starts_with("/reset") { + cx.answer_str(format!("Resetted number")).await?; + next(StartState) + } else { + cx.answer_str("Please, send /get or /reset").await?; + next(state) + } +} + +pub async fn dispatch(cx: Cx, dialogue: Dialogue, text: &str) -> Out { + match dialogue { + Dialogue::Start(state) => start(cx, state, text).await, + Dialogue::HaveNumber(state) => have_number(cx, state, text).await, + } +} From c0582e001d538bd7673e9c91390ac76e6dd46e6b Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Wed, 1 Jul 2020 19:30:10 +0600 Subject: [PATCH 07/29] Remove redundant format! --- examples/redis_remember_bot/src/transitions.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/redis_remember_bot/src/transitions.rs b/examples/redis_remember_bot/src/transitions.rs index a30c906a..594471e1 100644 --- a/examples/redis_remember_bot/src/transitions.rs +++ b/examples/redis_remember_bot/src/transitions.rs @@ -26,7 +26,7 @@ async fn have_number(cx: Cx, state: HaveNumberState, text: &str) -> Out { cx.answer_str(format!("Here is your number: {}", num)).await?; next(state) } else if text.starts_with("/reset") { - cx.answer_str(format!("Resetted number")).await?; + cx.answer_str("Resetted number").await?; next(StartState) } else { cx.answer_str("Please, send /get or /reset").await?; From 9efacfeb595d9141ccc7f2f29e1de34ffcf3d6d4 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Wed, 1 Jul 2020 19:46:28 +0600 Subject: [PATCH 08/29] Prettify redis_remember_bot --- examples/redis_remember_bot/src/main.rs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/examples/redis_remember_bot/src/main.rs b/examples/redis_remember_bot/src/main.rs index 2eb8aa03..8fe7a1ca 100644 --- a/examples/redis_remember_bot/src/main.rs +++ b/examples/redis_remember_bot/src/main.rs @@ -31,15 +31,13 @@ type In = TransitionIn; async fn handle_message(input: In) -> Out { let (cx, dialogue) = input.unpack(); - let text = match cx.update.text_owned() { - Some(text) => text, + match cx.update.text_owned() { + Some(text) => dispatch(cx, dialogue, &text).await, None => { cx.answer_str("Please, send me a text message").await?; - return next(StartState); + next(StartState) } - }; - - dispatch(cx, dialogue, &text).await + } } #[tokio::main] From 7fd32c87748f8d8f4ee953484c6211ee88291895 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Wed, 1 Jul 2020 20:41:00 +0600 Subject: [PATCH 09/29] Add integrational tests for RedisStorage --- tests/redis.rs | 74 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 tests/redis.rs diff --git a/tests/redis.rs b/tests/redis.rs new file mode 100644 index 00000000..df6b45de --- /dev/null +++ b/tests/redis.rs @@ -0,0 +1,74 @@ +use std::{ + fmt::{Debug, Display}, + future::Future, + sync::Arc, +}; +use teloxide::{ + dispatching::dialogue::{ + serializer::{Bincode, CBOR, JSON}, + RedisStorage, Serializer, Storage, + }, + prelude::*, +}; + +#[tokio::test] +async fn test_redis_json() { + test_redis(JSON).await; +} + +#[tokio::test] +async fn test_redis_bincode() { + test_redis(Bincode).await; +} + +#[tokio::test] +async fn test_redis_cbor() { + test_redis(CBOR).await; +} + +type Dialogue = String; + +async fn test_redis(serializer: S) +where + S: Send + Sync + Serializer + 'static, + >::Error: Debug + Display, +{ + let storage = Arc::new( + RedisStorage::open("redis://127.0.0.1:6379", serializer).await.unwrap(), + ); + + check_dialogue( + None, + Arc::clone(&storage).update_dialogue(11, "ABC".to_owned()), + ); + check_dialogue( + None, + Arc::clone(&storage).update_dialogue(256, "DEF".to_owned()), + ); + check_dialogue( + None, + Arc::clone(&storage).update_dialogue(11, "GHI".to_owned()), + ); + + check_dialogue( + "ABC", + Arc::clone(&storage).update_dialogue(1, "JKL".to_owned()), + ); + check_dialogue( + "GHI", + Arc::clone(&storage).update_dialogue(11, "MNO".to_owned()), + ); + + check_dialogue("JKL", Arc::clone(&storage).remove_dialogue(1)); + check_dialogue("DEF", Arc::clone(&storage).remove_dialogue(256)); + check_dialogue("MNO", Arc::clone(&storage).remove_dialogue(11)); +} + +async fn check_dialogue( + expected: impl Into>, + actual: impl Future, E>>, +) where + E: Debug, +{ + assert_eq!(expected.into().map(ToOwned::to_owned), actual.await.unwrap()) +} From 4992801b10de1989194939b6381bbd042fd98251 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Wed, 1 Jul 2020 20:42:27 +0600 Subject: [PATCH 10/29] Setup redis in CI --- .github/workflows/ci.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2c3af7b2..8b859037 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,6 +22,9 @@ jobs: override: true components: rustfmt, clippy + - name: Setup redis + run: sudo apt install redis-server && redis-server + - name: stable/beta build uses: actions-rs/cargo@v1 if: matrix.rust == 'stable' || matrix.rust == 'beta' From 177656c00c325f1b3d3ea1681c00b4cb3e28ee71 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Wed, 1 Jul 2020 20:58:06 +0600 Subject: [PATCH 11/29] Document RedisStorage --- src/dispatching/dialogue/mod.rs | 3 ++- src/dispatching/dialogue/storage/mod.rs | 2 +- .../dialogue/storage/redis_storage.rs | 18 ++++++++++++------ src/dispatching/dialogue/storage/serializer.rs | 5 +++++ 4 files changed, 20 insertions(+), 8 deletions(-) diff --git a/src/dispatching/dialogue/mod.rs b/src/dispatching/dialogue/mod.rs index afc03f44..09d753fd 100644 --- a/src/dispatching/dialogue/mod.rs +++ b/src/dispatching/dialogue/mod.rs @@ -57,7 +57,8 @@ pub use dialogue_with_cx::DialogueWithCx; pub use get_chat_id::GetChatId; #[cfg(feature = "redis-storage")] -pub use storage::RedisStorage; +pub use storage::{RedisStorage, RedisStorageError}; + pub use storage::{serializer, InMemStorage, Serializer, Storage}; /// Generates `.up(field)` methods for dialogue states. diff --git a/src/dispatching/dialogue/storage/mod.rs b/src/dispatching/dialogue/storage/mod.rs index 6a5ccac0..ed5319c8 100644 --- a/src/dispatching/dialogue/storage/mod.rs +++ b/src/dispatching/dialogue/storage/mod.rs @@ -9,7 +9,7 @@ use futures::future::BoxFuture; pub use in_mem_storage::InMemStorage; #[cfg(feature = "redis-storage")] -pub use redis_storage::RedisStorage; +pub use redis_storage::{RedisStorage, RedisStorageError}; pub use serializer::Serializer; use std::sync::Arc; diff --git a/src/dispatching/dialogue/storage/redis_storage.rs b/src/dispatching/dialogue/storage/redis_storage.rs index 273902de..0fba0736 100644 --- a/src/dispatching/dialogue/storage/redis_storage.rs +++ b/src/dispatching/dialogue/storage/redis_storage.rs @@ -11,8 +11,11 @@ use std::{ use thiserror::Error; use tokio::sync::Mutex; +/// An error returned from [`RedisStorage`]. +/// +/// [`RedisStorage`]: struct.RedisStorage.html #[derive(Debug, Error)] -pub enum Error +pub enum RedisStorageError where SE: Debug + Display, { @@ -22,6 +25,7 @@ where RedisError(#[from] redis::RedisError), } +/// A memory storage based on [Redis](https://redis.io/). pub struct RedisStorage { conn: Mutex, serializer: S, @@ -31,7 +35,7 @@ impl RedisStorage { pub async fn open( url: impl IntoConnectionInfo, serializer: S, - ) -> Result> { + ) -> Result> { Ok(Self { conn: Mutex::new( redis::Client::open(url)?.get_async_connection().await?, @@ -47,7 +51,7 @@ where D: Send + Serialize + DeserializeOwned + 'static, >::Error: Debug + Display, { - type Error = Error<>::Error>; + type Error = RedisStorageError<>::Error>; // `.del().ignore()` is much more readable than `.del()\n.ignore()` #[rustfmt::skip] @@ -72,7 +76,7 @@ where .map(|v| { self.serializer .deserialize(&v) - .map_err(Error::SerdeError) + .map_err(RedisStorageError::SerdeError) }) .transpose()?) } @@ -90,7 +94,7 @@ where let dialogue = self .serializer .serialize(&dialogue) - .map_err(Error::SerdeError)?; + .map_err(RedisStorageError::SerdeError)?; Ok(self .conn .lock() @@ -98,7 +102,9 @@ where .getset::<_, Vec, Option>>(chat_id, dialogue) .await? .map(|d| { - self.serializer.deserialize(&d).map_err(Error::SerdeError) + self.serializer + .deserialize(&d) + .map_err(RedisStorageError::SerdeError) }) .transpose()?) }) diff --git a/src/dispatching/dialogue/storage/serializer.rs b/src/dispatching/dialogue/storage/serializer.rs index ba4925ab..f31724a4 100644 --- a/src/dispatching/dialogue/storage/serializer.rs +++ b/src/dispatching/dialogue/storage/serializer.rs @@ -1,5 +1,7 @@ +/// Various serializers for memory storages. use serde::{de::DeserializeOwned, ser::Serialize}; +/// A serializer for memory storages. pub trait Serializer { type Error; @@ -7,6 +9,7 @@ pub trait Serializer { fn deserialize(&self, data: &[u8]) -> Result; } +/// The JSON serializer for memory storages. pub struct JSON; impl Serializer for JSON @@ -24,6 +27,7 @@ where } } +/// The CBOR serializer for memory storages. #[cfg(feature = "cbor-serializer")] pub struct CBOR; @@ -43,6 +47,7 @@ where } } +/// The Bincode serializer for memory storages. #[cfg(feature = "bincode-serializer")] pub struct Bincode; From e338e81b9cd5ef368165ed254d9c8b8ff105d91d Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Wed, 1 Jul 2020 21:01:14 +0600 Subject: [PATCH 12/29] --all-features everywhere in the CI --- .github/workflows/ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8b859037..deed3caf 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -30,7 +30,7 @@ jobs: if: matrix.rust == 'stable' || matrix.rust == 'beta' with: command: build - args: --verbose --features "" + args: --verbose --all-features - name: nightly build uses: actions-rs/cargo@v1 @@ -44,7 +44,7 @@ jobs: if: matrix.rust == 'stable' || matrix.rust == 'beta' with: command: test - args: --verbose --features "" + args: --verbose --all-features - name: nightly test uses: actions-rs/cargo@v1 @@ -58,7 +58,7 @@ jobs: if: matrix.rust == 'stable' || matrix.rust == 'beta' with: command: clippy - args: --all-targets --features "" -- -D warnings + args: --all-targets --all-features -- -D warnings - name: nightly clippy uses: actions-rs/cargo@v1 From 8e596911d53f52fadfe53aa35e28cd0f649aaddb Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Wed, 1 Jul 2020 21:05:16 +0600 Subject: [PATCH 13/29] Update ci.yml --- .github/workflows/ci.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index deed3caf..8490a236 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,9 +22,6 @@ jobs: override: true components: rustfmt, clippy - - name: Setup redis - run: sudo apt install redis-server && redis-server - - name: stable/beta build uses: actions-rs/cargo@v1 if: matrix.rust == 'stable' || matrix.rust == 'beta' From 7e0703b88fb7560c86b51f599c828011e58dc916 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Wed, 1 Jul 2020 21:13:53 +0600 Subject: [PATCH 14/29] Trying to setup Redis... --- .github/workflows/ci.yml | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8490a236..dd110ce6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,6 +3,15 @@ on: [push, pull_request] name: Continuous integration jobs: + vm-job: + name: CI + runs-on: ubuntu-latest + services: + redis: + image: redis + ports: + - 6379/tcp + ci: runs-on: ubuntu-latest strategy: @@ -20,7 +29,7 @@ jobs: profile: minimal toolchain: ${{ matrix.rust }} override: true - components: rustfmt, clippy + components: rustfmt, clippyz - name: stable/beta build uses: actions-rs/cargo@v1 From 9dac96a2286632ddbb479c2157fa5f103bc260bd Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Wed, 1 Jul 2020 21:15:48 +0600 Subject: [PATCH 15/29] Update ci.yml --- .github/workflows/ci.yml | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dd110ce6..065eaae0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,16 +3,12 @@ on: [push, pull_request] name: Continuous integration jobs: - vm-job: - name: CI - runs-on: ubuntu-latest + ci: services: redis: image: redis ports: - 6379/tcp - - ci: runs-on: ubuntu-latest strategy: matrix: From 7ff4cae5e3cac31aa10a2d637c95e45d7ab670d1 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Wed, 1 Jul 2020 21:19:50 +0600 Subject: [PATCH 16/29] Update ci.yml --- .github/workflows/ci.yml | 28 ---------------------------- 1 file changed, 28 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 065eaae0..07ffe550 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -27,20 +27,6 @@ jobs: override: true components: rustfmt, clippyz - - name: stable/beta build - uses: actions-rs/cargo@v1 - if: matrix.rust == 'stable' || matrix.rust == 'beta' - with: - command: build - args: --verbose --all-features - - - name: nightly build - uses: actions-rs/cargo@v1 - if: matrix.rust == 'nightly' - with: - command: build - args: --verbose --all-features - - name: stable/beta test uses: actions-rs/cargo@v1 if: matrix.rust == 'stable' || matrix.rust == 'beta' @@ -48,13 +34,6 @@ jobs: command: test args: --verbose --all-features - - name: nightly test - uses: actions-rs/cargo@v1 - if: matrix.rust == 'nightly' - with: - command: test - args: --verbose --all-features - - name: stable/beta clippy uses: actions-rs/cargo@v1 if: matrix.rust == 'stable' || matrix.rust == 'beta' @@ -62,13 +41,6 @@ jobs: command: clippy args: --all-targets --all-features -- -D warnings - - name: nightly clippy - uses: actions-rs/cargo@v1 - if: matrix.rust == 'nightly' - with: - command: clippy - args: --all-targets --all-features -- -D warnings - - name: Test the examples run: cd examples && bash test_examples.sh From f22a3273de7c33c3013be2a219a5d730568bddb0 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Wed, 1 Jul 2020 21:21:12 +0600 Subject: [PATCH 17/29] Update ci.yml --- .github/workflows/ci.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 07ffe550..3e89c4ce 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,9 +14,7 @@ jobs: matrix: rust: - stable - - beta - - nightly - + steps: - uses: actions/checkout@v1 From 6f6be9f0e391d6532ff557e9d65e6ef84e7c9099 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Wed, 1 Jul 2020 21:22:53 +0600 Subject: [PATCH 18/29] Update ci.yml --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3e89c4ce..b7faf020 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,7 +23,7 @@ jobs: profile: minimal toolchain: ${{ matrix.rust }} override: true - components: rustfmt, clippyz + components: rustfmt - name: stable/beta test uses: actions-rs/cargo@v1 From 66e14671d31c18a8598de23286fdb0bdbe49413a Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Wed, 1 Jul 2020 21:28:08 +0600 Subject: [PATCH 19/29] Update ci.yml --- .github/workflows/ci.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b7faf020..ea8573b6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -25,6 +25,9 @@ jobs: override: true components: rustfmt + - name: Setup redis + run: redis-server + - name: stable/beta test uses: actions-rs/cargo@v1 if: matrix.rust == 'stable' || matrix.rust == 'beta' From a6ff546477a84c52b4761309539b7d7eb1808452 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Wed, 1 Jul 2020 21:29:28 +0600 Subject: [PATCH 20/29] Update ci.yml --- .github/workflows/ci.yml | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ea8573b6..67d6b8bb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,11 +4,6 @@ name: Continuous integration jobs: ci: - services: - redis: - image: redis - ports: - - 6379/tcp runs-on: ubuntu-latest strategy: matrix: @@ -26,7 +21,7 @@ jobs: components: rustfmt - name: Setup redis - run: redis-server + run: sudo apt install redis-server && redis-server - name: stable/beta test uses: actions-rs/cargo@v1 From a7846bb165969f9a37de389b0ddc2010d17da8b6 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Wed, 1 Jul 2020 21:32:30 +0600 Subject: [PATCH 21/29] Connect RedisStorage to 7777 --- tests/redis.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/redis.rs b/tests/redis.rs index df6b45de..86df031c 100644 --- a/tests/redis.rs +++ b/tests/redis.rs @@ -34,7 +34,7 @@ where >::Error: Debug + Display, { let storage = Arc::new( - RedisStorage::open("redis://127.0.0.1:6379", serializer).await.unwrap(), + RedisStorage::open("redis://127.0.0.1:7777", serializer).await.unwrap(), ); check_dialogue( From 7918ed12bf15a64024b5e7bb7d6ee4e31e0f8f70 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Wed, 1 Jul 2020 21:32:58 +0600 Subject: [PATCH 22/29] Update ci.yml --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 67d6b8bb..4ef241ec 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,7 +21,7 @@ jobs: components: rustfmt - name: Setup redis - run: sudo apt install redis-server && redis-server + run: sudo apt install redis-server && redis-server --port 7777 - name: stable/beta test uses: actions-rs/cargo@v1 From 80dcf6d49f84b6cf3103a12ff27d2de9f0cfa0cb Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Wed, 1 Jul 2020 22:14:13 +0600 Subject: [PATCH 23/29] Run redis in background --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4ef241ec..6856de54 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,7 +21,7 @@ jobs: components: rustfmt - name: Setup redis - run: sudo apt install redis-server && redis-server --port 7777 + run: sudo apt install redis-server && redis-server --port 7777 > /dev/null & - name: stable/beta test uses: actions-rs/cargo@v1 From d66ede5ecf6a90640b5cda48ebcdd20919c844a1 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Wed, 1 Jul 2020 22:31:30 +0600 Subject: [PATCH 24/29] Fix tests/redis.rs --- tests/redis.rs | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/tests/redis.rs b/tests/redis.rs index 86df031c..c8aee2f2 100644 --- a/tests/redis.rs +++ b/tests/redis.rs @@ -3,12 +3,9 @@ use std::{ future::Future, sync::Arc, }; -use teloxide::{ - dispatching::dialogue::{ - serializer::{Bincode, CBOR, JSON}, - RedisStorage, Serializer, Storage, - }, - prelude::*, +use teloxide::dispatching::dialogue::{ + serializer::{Bincode, CBOR, JSON}, + RedisStorage, Serializer, Storage, }; #[tokio::test] @@ -40,28 +37,33 @@ where check_dialogue( None, Arc::clone(&storage).update_dialogue(11, "ABC".to_owned()), - ); + ) + .await; check_dialogue( None, Arc::clone(&storage).update_dialogue(256, "DEF".to_owned()), - ); + ) + .await; check_dialogue( None, Arc::clone(&storage).update_dialogue(11, "GHI".to_owned()), - ); + ) + .await; check_dialogue( "ABC", Arc::clone(&storage).update_dialogue(1, "JKL".to_owned()), - ); + ) + .await; check_dialogue( "GHI", Arc::clone(&storage).update_dialogue(11, "MNO".to_owned()), - ); + ) + .await; - check_dialogue("JKL", Arc::clone(&storage).remove_dialogue(1)); - check_dialogue("DEF", Arc::clone(&storage).remove_dialogue(256)); - check_dialogue("MNO", Arc::clone(&storage).remove_dialogue(11)); + check_dialogue("JKL", Arc::clone(&storage).remove_dialogue(1)).await; + check_dialogue("DEF", Arc::clone(&storage).remove_dialogue(256)).await; + check_dialogue("MNO", Arc::clone(&storage).remove_dialogue(11)).await; } async fn check_dialogue( From 9eee923aad194a90790846f2c368209097493fc8 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Sat, 4 Jul 2020 20:54:47 +0600 Subject: [PATCH 25/29] Open separate connections to redis during testing --- tests/redis.rs | 59 +++++++++++++++++++++++++++++--------------------- 1 file changed, 34 insertions(+), 25 deletions(-) diff --git a/tests/redis.rs b/tests/redis.rs index c8aee2f2..a5aadc34 100644 --- a/tests/redis.rs +++ b/tests/redis.rs @@ -10,45 +10,52 @@ use teloxide::dispatching::dialogue::{ #[tokio::test] async fn test_redis_json() { - test_redis(JSON).await; + let storage = Arc::new( + RedisStorage::open("redis://127.0.0.1:7777", JSON).await.unwrap(), + ); + test_redis(storage).await; } #[tokio::test] async fn test_redis_bincode() { - test_redis(Bincode).await; + let storage = Arc::new( + RedisStorage::open("redis://127.0.0.1:7778", Bincode).await.unwrap(), + ); + test_redis(storage).await; } #[tokio::test] async fn test_redis_cbor() { - test_redis(CBOR).await; + let storage = Arc::new( + RedisStorage::open("redis://127.0.0.1:7779", CBOR).await.unwrap(), + ); + test_redis(storage).await; } type Dialogue = String; -async fn test_redis(serializer: S) +async fn test_redis(storage: Arc>) where S: Send + Sync + Serializer + 'static, >::Error: Debug + Display, { - let storage = Arc::new( - RedisStorage::open("redis://127.0.0.1:7777", serializer).await.unwrap(), - ); + check_dialogue( + None, + Arc::clone(&storage).update_dialogue(1, "ABC".to_owned()), + ) + .await; + check_dialogue( + None, + Arc::clone(&storage).update_dialogue(11, "DEF".to_owned()), + ) + .await; + check_dialogue( + None, + Arc::clone(&storage).update_dialogue(256, "GHI".to_owned()), + ) + .await; - check_dialogue( - None, - Arc::clone(&storage).update_dialogue(11, "ABC".to_owned()), - ) - .await; - check_dialogue( - None, - Arc::clone(&storage).update_dialogue(256, "DEF".to_owned()), - ) - .await; - check_dialogue( - None, - Arc::clone(&storage).update_dialogue(11, "GHI".to_owned()), - ) - .await; + // 1 - ABC, 11 - DEF, 256 - GHI check_dialogue( "ABC", @@ -57,13 +64,15 @@ where .await; check_dialogue( "GHI", - Arc::clone(&storage).update_dialogue(11, "MNO".to_owned()), + Arc::clone(&storage).update_dialogue(256, "MNO".to_owned()), ) .await; + // 1 - GKL, 11 - DEF, 256 - MNO + check_dialogue("JKL", Arc::clone(&storage).remove_dialogue(1)).await; - check_dialogue("DEF", Arc::clone(&storage).remove_dialogue(256)).await; - check_dialogue("MNO", Arc::clone(&storage).remove_dialogue(11)).await; + check_dialogue("DEF", Arc::clone(&storage).remove_dialogue(11)).await; + check_dialogue("MNO", Arc::clone(&storage).remove_dialogue(256)).await; } async fn check_dialogue( From 82ade822cf5c5064dec3e2763d2c5a62ca5e24df Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Sat, 4 Jul 2020 20:59:58 +0600 Subject: [PATCH 26/29] Open redis at 7777, 7778, 7779 ports (CI) --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c35406f7..63c86833 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -37,7 +37,7 @@ jobs: toolchain: stable override: true - name: Setup redis - run: sudo apt install redis-server && redis-server --port 7777 > /dev/null & + run: sudo apt install redis-server && redis-server --port 7777 --port 7778 --port 7779 > /dev/null & - name: Cargo test run: cargo test --all-features build-example: From 56dadfbb34f0f8dc873d0379e9f95a0e0672d6dd Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Sat, 4 Jul 2020 21:02:07 +0600 Subject: [PATCH 27/29] Return Arc --- examples/redis_remember_bot/src/main.rs | 17 +++++++---------- .../dialogue/storage/redis_storage.rs | 6 +++--- tests/redis.rs | 15 ++++++--------- 3 files changed, 16 insertions(+), 22 deletions(-) diff --git a/examples/redis_remember_bot/src/main.rs b/examples/redis_remember_bot/src/main.rs index 8fe7a1ca..bffaa8aa 100644 --- a/examples/redis_remember_bot/src/main.rs +++ b/examples/redis_remember_bot/src/main.rs @@ -9,7 +9,6 @@ mod transitions; use states::*; use transitions::*; -use std::sync::Arc; use teloxide::{ dispatching::dialogue::{serializer::Bincode, RedisStorage, Storage}, prelude::*, @@ -54,15 +53,13 @@ async fn run() { .await .expect("Something is wrong with the bot!") }, - Arc::new( - // You can also choose serializer::JSON or serializer::CBOR - // All serializers but JSON require enabling feature - // "serializer-", e. g. "serializer-cbor" - // or "serializer-bincode" - RedisStorage::open("redis://127.0.0.1:6379", Bincode) - .await - .unwrap(), - ), + // You can also choose serializer::JSON or serializer::CBOR + // All serializers but JSON require enabling feature + // "serializer-", e. g. "serializer-cbor" + // or "serializer-bincode" + RedisStorage::open("redis://127.0.0.1:6379", Bincode) + .await + .unwrap(), )) .dispatch() .await; diff --git a/src/dispatching/dialogue/storage/redis_storage.rs b/src/dispatching/dialogue/storage/redis_storage.rs index 0fba0736..37c5fd07 100644 --- a/src/dispatching/dialogue/storage/redis_storage.rs +++ b/src/dispatching/dialogue/storage/redis_storage.rs @@ -35,13 +35,13 @@ impl RedisStorage { pub async fn open( url: impl IntoConnectionInfo, serializer: S, - ) -> Result> { - Ok(Self { + ) -> Result, RedisStorageError> { + Ok(Arc::new(Self { conn: Mutex::new( redis::Client::open(url)?.get_async_connection().await?, ), serializer, - }) + })) } } diff --git a/tests/redis.rs b/tests/redis.rs index a5aadc34..61dd6ad1 100644 --- a/tests/redis.rs +++ b/tests/redis.rs @@ -10,25 +10,22 @@ use teloxide::dispatching::dialogue::{ #[tokio::test] async fn test_redis_json() { - let storage = Arc::new( - RedisStorage::open("redis://127.0.0.1:7777", JSON).await.unwrap(), - ); + let storage = + RedisStorage::open("redis://127.0.0.1:7777", JSON).await.unwrap(); test_redis(storage).await; } #[tokio::test] async fn test_redis_bincode() { - let storage = Arc::new( - RedisStorage::open("redis://127.0.0.1:7778", Bincode).await.unwrap(), - ); + let storage = + RedisStorage::open("redis://127.0.0.1:7778", Bincode).await.unwrap(); test_redis(storage).await; } #[tokio::test] async fn test_redis_cbor() { - let storage = Arc::new( - RedisStorage::open("redis://127.0.0.1:7779", CBOR).await.unwrap(), - ); + let storage = + RedisStorage::open("redis://127.0.0.1:7779", CBOR).await.unwrap(); test_redis(storage).await; } From bbe3a86c846883ee7e95e78111eae0be31db15c5 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Sat, 4 Jul 2020 21:07:50 +0600 Subject: [PATCH 28/29] Update ci.yml --- .github/workflows/ci.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 63c86833..44a12eb5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -37,7 +37,10 @@ jobs: toolchain: stable override: true - name: Setup redis - run: sudo apt install redis-server && redis-server --port 7777 --port 7778 --port 7779 > /dev/null & + run: | + sudo apt install redis-server && redis-server --port 7777 > /dev/null & + sudo apt install redis-server && redis-server --port 7778 > /dev/null & + sudo apt install redis-server && redis-server --port 7779 > /dev/null & - name: Cargo test run: cargo test --all-features build-example: From 2f3652c16eccd46ce016883a82e8a9943f82bab4 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Sat, 4 Jul 2020 21:13:09 +0600 Subject: [PATCH 29/29] Update ci.yml --- .github/workflows/ci.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 44a12eb5..9d67b41f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -38,9 +38,10 @@ jobs: override: true - name: Setup redis run: | - sudo apt install redis-server && redis-server --port 7777 > /dev/null & - sudo apt install redis-server && redis-server --port 7778 > /dev/null & - sudo apt install redis-server && redis-server --port 7779 > /dev/null & + sudo apt install redis-server + redis-server --port 7777 > /dev/null & + redis-server --port 7778 > /dev/null & + redis-server --port 7779 > /dev/null & - name: Cargo test run: cargo test --all-features build-example: