From 798102a7d74f54dfd59f837c7293d2cbcece47d2 Mon Sep 17 00:00:00 2001
From: Maximilian Siling
Date: Fri, 13 Mar 2020 00:38:35 +0300
Subject: [PATCH 01/46] 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
+
Strongly typed bot commands
+
+You can describe bot commands as enumerations, and then they'll be automatically constructed from strings. Just like you describe JSON structures in serde-json and command-line arguments in structopt.
+
-All the API types and methods are hand-written, with heavy use of ADTs (algebraic data types) to enforce type safety and tight integration with IDEs. As few Options as possible.
+teloxide has functional reactive design, allowing you to declaratively manipulate streams of updates from Telegram using filters, maps, folds, zips, and a lot of other adaptors.
From 56bcaa7a46ed3e896f496510a70a86f33a40128b Mon Sep 17 00:00:00 2001
From: Temirkhan Myrzamadi
Date: Sat, 4 Jul 2020 19:52:16 +0600
Subject: [PATCH 31/46] Update README.md
---
README.md | 2 ++
1 file changed, 2 insertions(+)
diff --git a/README.md b/README.md
index cc1a744a..0fcdab98 100644
--- a/README.md
+++ b/README.md
@@ -54,6 +54,8 @@ All the API
Dialogues management is independent of how/where they are stored: just replace one line and make them persistent (for example, store on a disk, transmit through a network), without affecting the actual FSM algorithm. By default, teloxide stores all user dialogues in RAM. Default database implementations are coming!
+
+
Strongly typed bot commands
You can describe bot commands as enumerations, and then they'll be automatically constructed from strings. Just like you describe JSON structures in serde-json and command-line arguments in structopt.
From e1932bc68bf38d53d5eb2008c399eef274a75644 Mon Sep 17 00:00:00 2001
From: p0lunin
Date: Sat, 4 Jul 2020 17:28:46 +0300
Subject: [PATCH 32/46] removed unwrap() by propagate error to user code
---
src/requests/all/add_sticker_to_set.rs | 12 ++++++------
src/requests/all/create_new_sticker_set.rs | 13 +++++++------
src/requests/all/send_animation.rs | 15 ++++++++-------
src/requests/all/send_audio.rs | 15 ++++++++-------
src/requests/all/send_document.rs | 15 ++++++++-------
src/requests/all/send_photo.rs | 13 +++++++------
src/requests/all/send_sticker.rs | 13 +++++++------
src/requests/all/send_video.rs | 15 ++++++++-------
src/requests/all/send_video_note.rs | 15 ++++++++-------
src/requests/all/send_voice.rs | 13 +++++++------
src/requests/form_builder.rs | 16 ++++++++--------
src/requests/mod.rs | 11 +++++++++++
src/requests/utils.rs | 11 +++++------
13 files changed, 98 insertions(+), 79 deletions(-)
diff --git a/src/requests/all/add_sticker_to_set.rs b/src/requests/all/add_sticker_to_set.rs
index f76a6856..7544992a 100644
--- a/src/requests/all/add_sticker_to_set.rs
+++ b/src/requests/all/add_sticker_to_set.rs
@@ -5,7 +5,7 @@ use crate::{
Bot,
};
-use crate::requests::{Request, ResponseResult};
+use crate::requests::{ResponseResult, RequestFile};
use std::sync::Arc;
/// Use this method to add a new sticker to a set created by the bot.
@@ -22,11 +22,11 @@ pub struct AddStickerToSet {
}
#[async_trait::async_trait]
-impl Request for AddStickerToSet {
+impl RequestFile for AddStickerToSet {
type Output = True;
- async fn send(&self) -> ResponseResult {
- net::request_multipart(
+ async fn send(&self) -> tokio::io::Result> {
+ Ok(net::request_multipart(
self.bot.client(),
self.bot.token(),
"addStickerToSet",
@@ -34,12 +34,12 @@ impl Request for AddStickerToSet {
.add_text("user_id", &self.user_id)
.add_text("name", &self.name)
.add_input_file("png_sticker", &self.png_sticker)
- .await
+ .await?
.add_text("emojis", &self.emojis)
.add_text("mask_position", &self.mask_position)
.build(),
)
- .await
+ .await)
}
}
diff --git a/src/requests/all/create_new_sticker_set.rs b/src/requests/all/create_new_sticker_set.rs
index 8cffc8ae..a5b72bbb 100644
--- a/src/requests/all/create_new_sticker_set.rs
+++ b/src/requests/all/create_new_sticker_set.rs
@@ -1,10 +1,11 @@
use crate::{
net,
- requests::{form_builder::FormBuilder, Request, ResponseResult},
+ requests::{form_builder::FormBuilder, ResponseResult},
types::{InputFile, MaskPosition, True},
Bot,
};
use std::sync::Arc;
+use crate::requests::RequestFile;
/// Use this method to create new sticker set owned by a user. The bot will be
/// able to edit the created sticker set.
@@ -23,11 +24,11 @@ pub struct CreateNewStickerSet {
}
#[async_trait::async_trait]
-impl Request for CreateNewStickerSet {
+impl RequestFile for CreateNewStickerSet {
type Output = True;
- async fn send(&self) -> ResponseResult {
- net::request_multipart(
+ async fn send(&self) -> tokio::io::Result> {
+ Ok(net::request_multipart(
self.bot.client(),
self.bot.token(),
"createNewStickerSet",
@@ -36,13 +37,13 @@ impl Request for CreateNewStickerSet {
.add_text("name", &self.name)
.add_text("title", &self.title)
.add_input_file("png_sticker", &self.png_sticker)
- .await
+ .await?
.add_text("emojis", &self.emojis)
.add_text("contains_masks", &self.contains_masks)
.add_text("mask_position", &self.mask_position)
.build(),
)
- .await
+ .await)
}
}
diff --git a/src/requests/all/send_animation.rs b/src/requests/all/send_animation.rs
index f79c2c0b..942be539 100644
--- a/src/requests/all/send_animation.rs
+++ b/src/requests/all/send_animation.rs
@@ -1,10 +1,11 @@
use crate::{
net,
- requests::{form_builder::FormBuilder, Request, ResponseResult},
+ requests::{form_builder::FormBuilder, ResponseResult},
types::{ChatId, InputFile, Message, ParseMode, ReplyMarkup},
Bot,
};
use std::sync::Arc;
+use crate::requests::RequestFile;
/// Use this method to send animation files (GIF or H.264/MPEG-4 AVC video
/// without sound).
@@ -30,15 +31,15 @@ pub struct SendAnimation {
}
#[async_trait::async_trait]
-impl Request for SendAnimation {
+impl RequestFile for SendAnimation {
type Output = Message;
- async fn send(&self) -> ResponseResult {
+ async fn send(&self) -> tokio::io::Result> {
let mut builder =
FormBuilder::new()
.add_text("chat_id", &self.chat_id)
.add_input_file("animation", &self.animation)
- .await
+ .await?
.add_text("duration", &self.duration)
.add_text("width", &self.width)
.add_text("height", &self.height)
@@ -48,15 +49,15 @@ impl Request for SendAnimation {
.add_text("reply_to_message_id", &self.reply_to_message_id)
.add_text("reply_markup", &self.reply_markup);
if let Some(thumb) = self.thumb.as_ref() {
- builder = builder.add_input_file("thumb", thumb).await;
+ builder = builder.add_input_file("thumb", thumb).await?;
}
- net::request_multipart(
+ Ok(net::request_multipart(
self.bot.client(),
self.bot.token(),
"sendAnimation",
builder.build(),
)
- .await
+ .await)
}
}
diff --git a/src/requests/all/send_audio.rs b/src/requests/all/send_audio.rs
index 85add351..00b9803c 100644
--- a/src/requests/all/send_audio.rs
+++ b/src/requests/all/send_audio.rs
@@ -1,10 +1,11 @@
use crate::{
net,
- requests::{form_builder::FormBuilder, Request, ResponseResult},
+ requests::{form_builder::FormBuilder, ResponseResult},
types::{ChatId, InputFile, Message, ParseMode, ReplyMarkup},
Bot,
};
use std::sync::Arc;
+use crate::requests::RequestFile;
/// Use this method to send audio files, if you want Telegram clients to display
/// them in the music player.
@@ -34,14 +35,14 @@ pub struct SendAudio {
}
#[async_trait::async_trait]
-impl Request for SendAudio {
+impl RequestFile for SendAudio {
type Output = Message;
- async fn send(&self) -> ResponseResult {
+ async fn send(&self) -> tokio::io::Result> {
let mut builder = FormBuilder::new()
.add_text("chat_id", &self.chat_id)
.add_input_file("audio", &self.audio)
- .await
+ .await?
.add_text("caption", &self.caption)
.add_text("parse_mode", &self.parse_mode)
.add_text("duration", &self.duration)
@@ -51,15 +52,15 @@ impl Request for SendAudio {
.add_text("reply_to_message_id", &self.reply_to_message_id)
.add_text("reply_markup", &self.reply_markup);
if let Some(thumb) = self.thumb.as_ref() {
- builder = builder.add_input_file("thumb", thumb).await;
+ builder = builder.add_input_file("thumb", thumb).await?;
}
- net::request_multipart(
+ Ok(net::request_multipart(
self.bot.client(),
self.bot.token(),
"sendAudio",
builder.build()
)
- .await
+ .await)
}
}
diff --git a/src/requests/all/send_document.rs b/src/requests/all/send_document.rs
index 482f4a69..1b69df31 100644
--- a/src/requests/all/send_document.rs
+++ b/src/requests/all/send_document.rs
@@ -1,10 +1,11 @@
use crate::{
net,
- requests::{form_builder::FormBuilder, Request, ResponseResult},
+ requests::{form_builder::FormBuilder, ResponseResult},
types::{ChatId, InputFile, Message, ParseMode, ReplyMarkup},
Bot,
};
use std::sync::Arc;
+use crate::requests::RequestFile;
/// Use this method to send general files.
///
@@ -26,14 +27,14 @@ pub struct SendDocument {
}
#[async_trait::async_trait]
-impl Request for SendDocument {
+impl RequestFile for SendDocument {
type Output = Message;
- async fn send(&self) -> ResponseResult {
+ async fn send(&self) -> tokio::io::Result> {
let mut builder = FormBuilder::new()
.add_text("chat_id", &self.chat_id)
.add_input_file("document", &self.document)
- .await
+ .await?
.add_text("caption", &self.caption)
.add_text("parse_mode", &self.parse_mode)
.add_text("disable_notification", &self.disable_notification)
@@ -41,15 +42,15 @@ impl Request for SendDocument {
.add_text("reply_markup", &self.reply_markup);
if let Some(thumb) = self.thumb.as_ref() {
builder = builder.add_input_file("thumb", thumb)
- .await;
+ .await?;
}
- net::request_multipart(
+ Ok(net::request_multipart(
self.bot.client(),
self.bot.token(),
"sendDocument",
builder.build()
)
- .await
+ .await)
}
}
diff --git a/src/requests/all/send_photo.rs b/src/requests/all/send_photo.rs
index 3191373c..7c7c54aa 100644
--- a/src/requests/all/send_photo.rs
+++ b/src/requests/all/send_photo.rs
@@ -1,10 +1,11 @@
use crate::{
net,
- requests::{form_builder::FormBuilder, Request, ResponseResult},
+ requests::{form_builder::FormBuilder, ResponseResult},
types::{ChatId, InputFile, Message, ParseMode, ReplyMarkup},
Bot,
};
use std::sync::Arc;
+use crate::requests::RequestFile;
/// Use this method to send photos.
///
@@ -22,18 +23,18 @@ pub struct SendPhoto {
}
#[async_trait::async_trait]
-impl Request for SendPhoto {
+impl RequestFile for SendPhoto {
type Output = Message;
- async fn send(&self) -> ResponseResult {
- net::request_multipart(
+ async fn send(&self) -> tokio::io::Result> {
+ Ok(net::request_multipart(
self.bot.client(),
self.bot.token(),
"sendPhoto",
FormBuilder::new()
.add_text("chat_id", &self.chat_id)
.add_input_file("photo", &self.photo)
- .await
+ .await?
.add_text("caption", &self.caption)
.add_text("parse_mode", &self.parse_mode)
.add_text("disable_notification", &self.disable_notification)
@@ -41,7 +42,7 @@ impl Request for SendPhoto {
.add_text("reply_markup", &self.reply_markup)
.build(),
)
- .await
+ .await)
}
}
diff --git a/src/requests/all/send_sticker.rs b/src/requests/all/send_sticker.rs
index ae5c898f..a3caef2e 100644
--- a/src/requests/all/send_sticker.rs
+++ b/src/requests/all/send_sticker.rs
@@ -1,10 +1,11 @@
use crate::{
net,
- requests::{form_builder::FormBuilder, Request, ResponseResult},
+ requests::{form_builder::FormBuilder, ResponseResult},
types::{ChatId, InputFile, Message, ReplyMarkup},
Bot,
};
use std::sync::Arc;
+use crate::requests::RequestFile;
/// Use this method to send static .WEBP or [animated] .TGS stickers.
///
@@ -22,24 +23,24 @@ pub struct SendSticker {
}
#[async_trait::async_trait]
-impl Request for SendSticker {
+impl RequestFile for SendSticker {
type Output = Message;
- async fn send(&self) -> ResponseResult {
- net::request_multipart(
+ async fn send(&self) -> tokio::io::Result> {
+ Ok(net::request_multipart(
self.bot.client(),
self.bot.token(),
"sendSticker",
FormBuilder::new()
.add_text("chat_id", &self.chat_id)
.add_input_file("sticker", &self.sticker)
- .await
+ .await?
.add_text("disable_notification", &self.disable_notification)
.add_text("reply_to_message_id", &self.reply_to_message_id)
.add_text("reply_markup", &self.reply_markup)
.build(),
)
- .await
+ .await)
}
}
diff --git a/src/requests/all/send_video.rs b/src/requests/all/send_video.rs
index 450fd224..3e78fd05 100644
--- a/src/requests/all/send_video.rs
+++ b/src/requests/all/send_video.rs
@@ -1,10 +1,11 @@
use crate::{
net,
- requests::{form_builder::FormBuilder, Request, ResponseResult},
+ requests::{form_builder::FormBuilder, ResponseResult},
types::{ChatId, InputFile, Message, ParseMode, ReplyMarkup},
Bot,
};
use std::sync::Arc;
+use crate::requests::RequestFile;
/// Use this method to send video files, Telegram clients support mp4 videos
/// (other formats may be sent as Document).
@@ -31,14 +32,14 @@ pub struct SendVideo {
}
#[async_trait::async_trait]
-impl Request for SendVideo {
+impl RequestFile for SendVideo {
type Output = Message;
- async fn send(&self) -> ResponseResult {
+ async fn send(&self) -> tokio::io::Result> {
let mut builder = FormBuilder::new()
.add_text("chat_id", &self.chat_id)
.add_input_file("video", &self.video)
- .await
+ .await?
.add_text("duration", &self.duration)
.add_text("width", &self.width)
.add_text("height", &self.height)
@@ -49,15 +50,15 @@ impl Request for SendVideo {
.add_text("reply_to_message_id", &self.reply_to_message_id)
.add_text("reply_markup", &self.reply_markup);
if let Some(thumb) = self.thumb.as_ref() {
- builder = builder.add_input_file("thumb", thumb).await;
+ builder = builder.add_input_file("thumb", thumb).await?;
}
- net::request_multipart(
+ Ok(net::request_multipart(
self.bot.client(),
self.bot.token(),
"sendVideo",
builder.build(),
)
- .await
+ .await)
}
}
diff --git a/src/requests/all/send_video_note.rs b/src/requests/all/send_video_note.rs
index cf32b467..cb063f53 100644
--- a/src/requests/all/send_video_note.rs
+++ b/src/requests/all/send_video_note.rs
@@ -1,10 +1,11 @@
use crate::{
net,
- requests::{form_builder::FormBuilder, Request, ResponseResult},
+ requests::{form_builder::FormBuilder, ResponseResult},
types::{ChatId, InputFile, Message, ReplyMarkup},
Bot,
};
use std::sync::Arc;
+use crate::requests::RequestFile;
/// As of [v.4.0], Telegram clients support rounded square mp4 videos of up to 1
/// minute long. Use this method to send video messages.
@@ -26,29 +27,29 @@ pub struct SendVideoNote {
}
#[async_trait::async_trait]
-impl Request for SendVideoNote {
+impl RequestFile for SendVideoNote {
type Output = Message;
- async fn send(&self) -> ResponseResult {
+ async fn send(&self) -> tokio::io::Result> {
let mut builder = FormBuilder::new()
.add_text("chat_id", &self.chat_id)
.add_input_file("video_note", &self.video_note)
- .await
+ .await?
.add_text("duration", &self.duration)
.add_text("length", &self.length)
.add_text("disable_notification", &self.disable_notification)
.add_text("reply_to_message_id", &self.reply_to_message_id)
.add_text("reply_markup", &self.reply_markup);
if let Some(thumb) = self.thumb.as_ref() {
- builder = builder.add_input_file("thumb", thumb).await;
+ builder = builder.add_input_file("thumb", thumb).await?;
}
- net::request_multipart(
+ Ok(net::request_multipart(
self.bot.client(),
self.bot.token(),
"sendVideoNote",
builder.build(),
)
- .await
+ .await)
}
}
diff --git a/src/requests/all/send_voice.rs b/src/requests/all/send_voice.rs
index 71b042c3..d230f40e 100644
--- a/src/requests/all/send_voice.rs
+++ b/src/requests/all/send_voice.rs
@@ -1,10 +1,11 @@
use crate::{
net,
- requests::{form_builder::FormBuilder, Request, ResponseResult},
+ requests::{form_builder::FormBuilder, ResponseResult},
types::{ChatId, InputFile, Message, ParseMode, ReplyMarkup},
Bot,
};
use std::sync::Arc;
+use crate::requests::RequestFile;
/// Use this method to send audio files, if you want Telegram clients to display
/// the file as a playable voice message.
@@ -32,18 +33,18 @@ pub struct SendVoice {
}
#[async_trait::async_trait]
-impl Request for SendVoice {
+impl RequestFile for SendVoice {
type Output = Message;
- async fn send(&self) -> ResponseResult {
- net::request_multipart(
+ async fn send(&self) -> tokio::io::Result> {
+ Ok(net::request_multipart(
self.bot.client(),
self.bot.token(),
"sendVoice",
FormBuilder::new()
.add_text("chat_id", &self.chat_id)
.add_input_file("voice", &self.voice)
- .await
+ .await?
.add_text("caption", &self.caption)
.add_text("parse_mode", &self.parse_mode)
.add_text("duration", &self.duration)
@@ -52,7 +53,7 @@ impl Request for SendVoice {
.add_text("reply_markup", &self.reply_markup)
.build(),
)
- .await
+ .await)
}
}
diff --git a/src/requests/form_builder.rs b/src/requests/form_builder.rs
index 3d0e48ad..f6ad156f 100644
--- a/src/requests/form_builder.rs
+++ b/src/requests/form_builder.rs
@@ -32,12 +32,12 @@ impl FormBuilder {
}
}
- pub async fn add_input_file<'a, N>(self, name: N, value: &InputFile) -> Self
+ pub async fn add_input_file<'a, N>(self, name: N, value: &InputFile) -> tokio::io::Result
where
N: Into>,
{
- match value {
- InputFile::File(path) => self.add_file(name, path.clone()).await,
+ Ok(match value {
+ InputFile::File(path) => self.add_file(name, path.clone()).await?,
InputFile::Memory { file_name, data } => self.add_file_from_memory(
name,
file_name.clone(),
@@ -45,20 +45,20 @@ impl FormBuilder {
),
InputFile::Url(url) => self.add_text(name, url),
InputFile::FileId(file_id) => self.add_text(name, file_id),
- }
+ })
}
// used in SendMediaGroup
- pub async fn add_file<'a, N>(self, name: N, path_to_file: PathBuf) -> Self
+ pub async fn add_file<'a, N>(self, name: N, path_to_file: PathBuf) -> tokio::io::Result
where
N: Into>,
{
- Self {
+ Ok(Self {
form: self.form.part(
name.into().into_owned(),
- file_to_part(path_to_file).await,
+ file_to_part(path_to_file).await?,
),
- }
+ })
}
fn add_file_from_memory<'a, N>(
diff --git a/src/requests/mod.rs b/src/requests/mod.rs
index 7f121d29..ee8549f9 100644
--- a/src/requests/mod.rs
+++ b/src/requests/mod.rs
@@ -18,3 +18,14 @@ pub trait Request {
/// Asynchronously sends this request to Telegram and returns the result.
async fn send(&self) -> ResponseResult;
}
+
+/// Designates an API request.
+#[async_trait::async_trait]
+pub trait RequestFile {
+ /// A data structure returned if success.
+ type Output;
+
+ /// Asynchronously sends this request to Telegram and returns the result.
+ async fn send(&self) -> tokio::io::Result>;
+}
+
diff --git a/src/requests/utils.rs b/src/requests/utils.rs
index 25cc5926..2023cf7e 100644
--- a/src/requests/utils.rs
+++ b/src/requests/utils.rs
@@ -21,18 +21,17 @@ impl Decoder for FileDecoder {
}
}
-pub async fn file_to_part(path_to_file: PathBuf) -> Part {
+pub async fn file_to_part(path_to_file: PathBuf) -> std::io::Result {
let file_name =
path_to_file.file_name().unwrap().to_string_lossy().into_owned();
-
+
let file = FramedRead::new(
- tokio::fs::File::open(path_to_file).await.unwrap(), /* TODO: this
- * can
- * cause panics */
+ tokio::fs::File::open(path_to_file)
+ .await?,
FileDecoder,
);
- Part::stream(Body::wrap_stream(file)).file_name(file_name)
+ Ok(Part::stream(Body::wrap_stream(file)).file_name(file_name))
}
pub fn file_from_memory_to_part(
From de6280150cfc6846fe77c83d58f8857f5537e0a1 Mon Sep 17 00:00:00 2001
From: p0lunin
Date: Sat, 4 Jul 2020 17:29:05 +0300
Subject: [PATCH 33/46] fmt
---
src/requests/all/add_sticker_to_set.rs | 2 +-
src/requests/all/create_new_sticker_set.rs | 3 +-
src/requests/all/send_animation.rs | 6 ++--
src/requests/all/send_audio.rs | 5 ++-
src/requests/all/send_document.rs | 8 ++---
src/requests/all/send_photo.rs | 3 +-
src/requests/all/send_sticker.rs | 3 +-
src/requests/all/send_video.rs | 3 +-
src/requests/all/send_video_note.rs | 3 +-
src/requests/all/send_voice.rs | 3 +-
src/requests/form_builder.rs | 38 +++++++++++++---------
src/requests/mod.rs | 1 -
src/requests/utils.rs | 5 ++-
13 files changed, 39 insertions(+), 44 deletions(-)
diff --git a/src/requests/all/add_sticker_to_set.rs b/src/requests/all/add_sticker_to_set.rs
index 7544992a..7f114e37 100644
--- a/src/requests/all/add_sticker_to_set.rs
+++ b/src/requests/all/add_sticker_to_set.rs
@@ -5,7 +5,7 @@ use crate::{
Bot,
};
-use crate::requests::{ResponseResult, RequestFile};
+use crate::requests::{RequestFile, ResponseResult};
use std::sync::Arc;
/// Use this method to add a new sticker to a set created by the bot.
diff --git a/src/requests/all/create_new_sticker_set.rs b/src/requests/all/create_new_sticker_set.rs
index a5b72bbb..7bbf9427 100644
--- a/src/requests/all/create_new_sticker_set.rs
+++ b/src/requests/all/create_new_sticker_set.rs
@@ -1,11 +1,10 @@
use crate::{
net,
- requests::{form_builder::FormBuilder, ResponseResult},
+ requests::{form_builder::FormBuilder, RequestFile, ResponseResult},
types::{InputFile, MaskPosition, True},
Bot,
};
use std::sync::Arc;
-use crate::requests::RequestFile;
/// Use this method to create new sticker set owned by a user. The bot will be
/// able to edit the created sticker set.
diff --git a/src/requests/all/send_animation.rs b/src/requests/all/send_animation.rs
index 942be539..b9b4073c 100644
--- a/src/requests/all/send_animation.rs
+++ b/src/requests/all/send_animation.rs
@@ -1,11 +1,10 @@
use crate::{
net,
- requests::{form_builder::FormBuilder, ResponseResult},
+ requests::{form_builder::FormBuilder, RequestFile, ResponseResult},
types::{ChatId, InputFile, Message, ParseMode, ReplyMarkup},
Bot,
};
use std::sync::Arc;
-use crate::requests::RequestFile;
/// Use this method to send animation files (GIF or H.264/MPEG-4 AVC video
/// without sound).
@@ -35,8 +34,7 @@ impl RequestFile for SendAnimation {
type Output = Message;
async fn send(&self) -> tokio::io::Result> {
- let mut builder =
- FormBuilder::new()
+ let mut builder = FormBuilder::new()
.add_text("chat_id", &self.chat_id)
.add_input_file("animation", &self.animation)
.await?
diff --git a/src/requests/all/send_audio.rs b/src/requests/all/send_audio.rs
index 00b9803c..b3137553 100644
--- a/src/requests/all/send_audio.rs
+++ b/src/requests/all/send_audio.rs
@@ -1,11 +1,10 @@
use crate::{
net,
- requests::{form_builder::FormBuilder, ResponseResult},
+ requests::{form_builder::FormBuilder, RequestFile, ResponseResult},
types::{ChatId, InputFile, Message, ParseMode, ReplyMarkup},
Bot,
};
use std::sync::Arc;
-use crate::requests::RequestFile;
/// Use this method to send audio files, if you want Telegram clients to display
/// them in the music player.
@@ -58,7 +57,7 @@ impl RequestFile for SendAudio {
self.bot.client(),
self.bot.token(),
"sendAudio",
- builder.build()
+ builder.build(),
)
.await)
}
diff --git a/src/requests/all/send_document.rs b/src/requests/all/send_document.rs
index 1b69df31..8c3ffb6b 100644
--- a/src/requests/all/send_document.rs
+++ b/src/requests/all/send_document.rs
@@ -1,11 +1,10 @@
use crate::{
net,
- requests::{form_builder::FormBuilder, ResponseResult},
+ requests::{form_builder::FormBuilder, RequestFile, ResponseResult},
types::{ChatId, InputFile, Message, ParseMode, ReplyMarkup},
Bot,
};
use std::sync::Arc;
-use crate::requests::RequestFile;
/// Use this method to send general files.
///
@@ -41,14 +40,13 @@ impl RequestFile for SendDocument {
.add_text("reply_to_message_id", &self.reply_to_message_id)
.add_text("reply_markup", &self.reply_markup);
if let Some(thumb) = self.thumb.as_ref() {
- builder = builder.add_input_file("thumb", thumb)
- .await?;
+ builder = builder.add_input_file("thumb", thumb).await?;
}
Ok(net::request_multipart(
self.bot.client(),
self.bot.token(),
"sendDocument",
- builder.build()
+ builder.build(),
)
.await)
}
diff --git a/src/requests/all/send_photo.rs b/src/requests/all/send_photo.rs
index 7c7c54aa..0cdea7cb 100644
--- a/src/requests/all/send_photo.rs
+++ b/src/requests/all/send_photo.rs
@@ -1,11 +1,10 @@
use crate::{
net,
- requests::{form_builder::FormBuilder, ResponseResult},
+ requests::{form_builder::FormBuilder, RequestFile, ResponseResult},
types::{ChatId, InputFile, Message, ParseMode, ReplyMarkup},
Bot,
};
use std::sync::Arc;
-use crate::requests::RequestFile;
/// Use this method to send photos.
///
diff --git a/src/requests/all/send_sticker.rs b/src/requests/all/send_sticker.rs
index a3caef2e..ed312b3d 100644
--- a/src/requests/all/send_sticker.rs
+++ b/src/requests/all/send_sticker.rs
@@ -1,11 +1,10 @@
use crate::{
net,
- requests::{form_builder::FormBuilder, ResponseResult},
+ requests::{form_builder::FormBuilder, RequestFile, ResponseResult},
types::{ChatId, InputFile, Message, ReplyMarkup},
Bot,
};
use std::sync::Arc;
-use crate::requests::RequestFile;
/// Use this method to send static .WEBP or [animated] .TGS stickers.
///
diff --git a/src/requests/all/send_video.rs b/src/requests/all/send_video.rs
index 3e78fd05..64edf2df 100644
--- a/src/requests/all/send_video.rs
+++ b/src/requests/all/send_video.rs
@@ -1,11 +1,10 @@
use crate::{
net,
- requests::{form_builder::FormBuilder, ResponseResult},
+ requests::{form_builder::FormBuilder, RequestFile, ResponseResult},
types::{ChatId, InputFile, Message, ParseMode, ReplyMarkup},
Bot,
};
use std::sync::Arc;
-use crate::requests::RequestFile;
/// Use this method to send video files, Telegram clients support mp4 videos
/// (other formats may be sent as Document).
diff --git a/src/requests/all/send_video_note.rs b/src/requests/all/send_video_note.rs
index cb063f53..8eb45c42 100644
--- a/src/requests/all/send_video_note.rs
+++ b/src/requests/all/send_video_note.rs
@@ -1,11 +1,10 @@
use crate::{
net,
- requests::{form_builder::FormBuilder, ResponseResult},
+ requests::{form_builder::FormBuilder, RequestFile, ResponseResult},
types::{ChatId, InputFile, Message, ReplyMarkup},
Bot,
};
use std::sync::Arc;
-use crate::requests::RequestFile;
/// As of [v.4.0], Telegram clients support rounded square mp4 videos of up to 1
/// minute long. Use this method to send video messages.
diff --git a/src/requests/all/send_voice.rs b/src/requests/all/send_voice.rs
index d230f40e..7cea5a36 100644
--- a/src/requests/all/send_voice.rs
+++ b/src/requests/all/send_voice.rs
@@ -1,11 +1,10 @@
use crate::{
net,
- requests::{form_builder::FormBuilder, ResponseResult},
+ requests::{form_builder::FormBuilder, RequestFile, ResponseResult},
types::{ChatId, InputFile, Message, ParseMode, ReplyMarkup},
Bot,
};
use std::sync::Arc;
-use crate::requests::RequestFile;
/// Use this method to send audio files, if you want Telegram clients to display
/// the file as a playable voice message.
diff --git a/src/requests/form_builder.rs b/src/requests/form_builder.rs
index f6ad156f..e94edaaa 100644
--- a/src/requests/form_builder.rs
+++ b/src/requests/form_builder.rs
@@ -22,34 +22,42 @@ impl FormBuilder {
}
pub fn add_text<'a, T, N>(self, name: N, value: &T) -> Self
- where
- N: Into>,
- T: IntoFormText,
+ where
+ N: Into>,
+ T: IntoFormText,
{
match value.into_form_text() {
- Some(val) => Self { form: self.form.text(name.into().into_owned(), val) },
- None => self
+ Some(val) => {
+ Self { form: self.form.text(name.into().into_owned(), val) }
+ }
+ None => self,
}
}
- pub async fn add_input_file<'a, N>(self, name: N, value: &InputFile) -> tokio::io::Result
- where
- N: Into>,
+ pub async fn add_input_file<'a, N>(
+ self,
+ name: N,
+ value: &InputFile,
+ ) -> tokio::io::Result
+ where
+ N: Into>,
{
Ok(match value {
InputFile::File(path) => self.add_file(name, path.clone()).await?,
- InputFile::Memory { file_name, data } => self.add_file_from_memory(
- name,
- file_name.clone(),
- data.clone(),
- ),
+ InputFile::Memory { file_name, data } => {
+ self.add_file_from_memory(name, file_name.clone(), data.clone())
+ }
InputFile::Url(url) => self.add_text(name, url),
InputFile::FileId(file_id) => self.add_text(name, file_id),
})
}
-
+
// used in SendMediaGroup
- pub async fn add_file<'a, N>(self, name: N, path_to_file: PathBuf) -> tokio::io::Result
+ pub async fn add_file<'a, N>(
+ self,
+ name: N,
+ path_to_file: PathBuf,
+ ) -> tokio::io::Result
where
N: Into>,
{
diff --git a/src/requests/mod.rs b/src/requests/mod.rs
index ee8549f9..3e70c321 100644
--- a/src/requests/mod.rs
+++ b/src/requests/mod.rs
@@ -28,4 +28,3 @@ pub trait RequestFile {
/// Asynchronously sends this request to Telegram and returns the result.
async fn send(&self) -> tokio::io::Result>;
}
-
diff --git a/src/requests/utils.rs b/src/requests/utils.rs
index 2023cf7e..399c8cd1 100644
--- a/src/requests/utils.rs
+++ b/src/requests/utils.rs
@@ -24,10 +24,9 @@ impl Decoder for FileDecoder {
pub async fn file_to_part(path_to_file: PathBuf) -> std::io::Result {
let file_name =
path_to_file.file_name().unwrap().to_string_lossy().into_owned();
-
+
let file = FramedRead::new(
- tokio::fs::File::open(path_to_file)
- .await?,
+ tokio::fs::File::open(path_to_file).await?,
FileDecoder,
);
From c7dbc869bad3cf7b2874a458d2f39b6803cf6684 Mon Sep 17 00:00:00 2001
From: p0lunin
Date: Sat, 4 Jul 2020 17:50:20 +0300
Subject: [PATCH 34/46] added changelog
---
CHANGELOG.md | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index bfb68427..60c78953 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+## [0.3.0] - ???
+### Changed
+ - Now methods which can send file to Telegram returns tokio::io::Result. Early its could panic. ([issue 216](https://github.com/teloxide/teloxide/issues/216))
+
## [0.2.0] - 2020-02-25
### Added
- The functionality to parse commands only with a correct bot's name (breaks backwards compatibility) ([Issue 168](https://github.com/teloxide/teloxide/issues/168)).
From 9eee923aad194a90790846f2c368209097493fc8 Mon Sep 17 00:00:00 2001
From: Temirkhan Myrzamadi
Date: Sat, 4 Jul 2020 20:54:47 +0600
Subject: [PATCH 35/46] 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 36/46] 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 37/46] 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 38/46] 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 39/46] 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:
From bcca608c9d180236f934d71a81fca47b38c89abc Mon Sep 17 00:00:00 2001
From: Temirkhan Myrzamadi
Date: Sat, 4 Jul 2020 21:24:50 +0600
Subject: [PATCH 40/46] Update README.md
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 0fcdab98..cafd011c 100644
--- a/README.md
+++ b/README.md
@@ -51,7 +51,7 @@ All the API
-Dialogues management is independent of how/where they are stored: just replace one line and make them persistent (for example, store on a disk, transmit through a network), without affecting the actual FSM algorithm. By default, teloxide stores all user dialogues in RAM. Default database implementations are coming!
+Dialogues management is independent of how/where they are stored: just replace one line and make them persistent (for example, store on a disk, transmit through a network), without affecting the actual FSM algorithm. By default, teloxide stores all user dialogues in RAM. Out-of-the-box storages include Redis.
From d7929d6fe0efa3c372f98222bc0847504b762902 Mon Sep 17 00:00:00 2001
From: Temirkhan Myrzamadi
Date: Sat, 4 Jul 2020 21:26:26 +0600
Subject: [PATCH 41/46] Shorten README.md a little bit
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index cafd011c..785bc166 100644
--- a/README.md
+++ b/README.md
@@ -51,7 +51,7 @@ All the API
-Dialogues management is independent of how/where they are stored: just replace one line and make them persistent (for example, store on a disk, transmit through a network), without affecting the actual FSM algorithm. By default, teloxide stores all user dialogues in RAM. Out-of-the-box storages include Redis.
+Dialogues management is independent of how/where they are stored: you can just replace one line and make them persistent (e.g. store on a disk, transmit through a network). By default, teloxide stores all user dialogues in RAM. Out-of-the-box storages include Redis.