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>> { + 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/46] 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/46] 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/46] 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/46] 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/46] 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/46] 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/46] 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/46] 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/46] 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/46] 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/46] --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/46] 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/46] 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/46] 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/46] 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/46] 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/46] 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/46] 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/46] 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/46] 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/46] 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/46] 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/46] 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 58cb5ad15211df472dd0b9cd672d016ab4efe574 Mon Sep 17 00:00:00 2001 From: p0lunin Date: Sat, 4 Jul 2020 10:44:42 +0300 Subject: [PATCH 25/46] added test for descriptions="off" attribute --- Cargo.toml | 2 +- tests/command.rs | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 0235fc94..4996921d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -45,7 +45,7 @@ futures = "0.3.1" pin-project = "0.4.6" serde_with_macros = "1.0.1" -teloxide-macros = "0.3.0" +teloxide-macros = { git = "http://github.com/teloxide/teloxide-macros", branch = "dev" } [dev-dependencies] smart-default = "0.6.0" diff --git a/tests/command.rs b/tests/command.rs index eadb214d..f3fc1912 100644 --- a/tests/command.rs +++ b/tests/command.rs @@ -171,3 +171,19 @@ fn parse_named_fields() { DefaultCommands::parse("/start 10 hello", "").unwrap() ); } + +#[test] +fn descriptions_off() { + #[command(rename = "lowercase")] + #[derive(BotCommand, Debug, PartialEq)] + enum DefaultCommands { + #[command(description = "off")] + Start, + Help, + } + + assert_eq!( + DefaultCommands::descriptions(), + "/help\n".to_owned() + ); +} From 030a349f605848ac8a6e142deeefaf91e889c833 Mon Sep 17 00:00:00 2001 From: p0lunin Date: Sat, 4 Jul 2020 10:47:46 +0300 Subject: [PATCH 26/46] fmt --- tests/command.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/tests/command.rs b/tests/command.rs index f3fc1912..47b3555f 100644 --- a/tests/command.rs +++ b/tests/command.rs @@ -182,8 +182,5 @@ fn descriptions_off() { Help, } - assert_eq!( - DefaultCommands::descriptions(), - "/help\n".to_owned() - ); + assert_eq!(DefaultCommands::descriptions(), "/help\n".to_owned()); } From 01439aae37ce46b85c4854bd9e8610bd5a976b95 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Sat, 4 Jul 2020 13:55:25 +0600 Subject: [PATCH 27/46] Update Cargo.toml --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 4996921d..e2fcd313 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -45,7 +45,7 @@ futures = "0.3.1" pin-project = "0.4.6" serde_with_macros = "1.0.1" -teloxide-macros = { git = "http://github.com/teloxide/teloxide-macros", branch = "dev" } +teloxide-macros = "0.3.1" [dev-dependencies] smart-default = "0.6.0" From 50dca962ab2e8d72c85c7c6c02888767e1cf420f Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Sat, 4 Jul 2020 19:45:19 +0600 Subject: [PATCH 28/46] Update the features (README.md) --- README.md | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 038b8329..2491e632 100644 --- a/README.md +++ b/README.md @@ -35,9 +35,16 @@ ## Features -

Type safety

+

Functional reactive design

-All the API types and methods are implemented with heavy use of ADTs to enforce type safety and tight integration with IDEs. Bot's commands have precise types too, thereby serving as a self-documenting code and respecting the parse, don't validate programming idiom. +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. +

+ +
+ +

API types as ADTs

+

+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.


@@ -47,6 +54,13 @@ 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. +

+ +
+ ## Setting up your environment 1. [Download Rust](http://rustup.rs/). 2. Create a new bot using [@Botfather](https://t.me/botfather) to get a token in the format `123456789:blablabla`. From c04032cdf0928aa48dfdf6e18348e42d7ae728db Mon Sep 17 00:00:00 2001 From: p0lunin Date: Sat, 4 Jul 2020 16:46:00 +0300 Subject: [PATCH 29/46] remove add method from FormBuilder --- src/requests/all/add_sticker_to_set.rs | 14 +-- src/requests/all/create_new_sticker_set.rs | 20 ++-- src/requests/all/edit_message_media.rs | 14 +-- src/requests/all/send_animation.rs | 41 ++++---- src/requests/all/send_audio.rs | 40 +++----- src/requests/all/send_document.rs | 32 +++--- src/requests/all/send_media_group.rs | 12 +-- src/requests/all/send_photo.rs | 20 ++-- src/requests/all/send_sticker.rs | 14 +-- src/requests/all/send_video.rs | 43 ++++---- src/requests/all/send_video_note.rs | 31 +++--- src/requests/all/send_voice.rs | 23 ++--- src/requests/form_builder.rs | 112 +++++++++------------ 13 files changed, 168 insertions(+), 248 deletions(-) diff --git a/src/requests/all/add_sticker_to_set.rs b/src/requests/all/add_sticker_to_set.rs index 119bd7e1..f76a6856 100644 --- a/src/requests/all/add_sticker_to_set.rs +++ b/src/requests/all/add_sticker_to_set.rs @@ -31,16 +31,12 @@ impl Request for AddStickerToSet { self.bot.token(), "addStickerToSet", FormBuilder::new() - .add("user_id", &self.user_id) - .await - .add("name", &self.name) - .await - .add("png_sticker", &self.png_sticker) - .await - .add("emojis", &self.emojis) - .await - .add("mask_position", &self.mask_position) + .add_text("user_id", &self.user_id) + .add_text("name", &self.name) + .add_input_file("png_sticker", &self.png_sticker) .await + .add_text("emojis", &self.emojis) + .add_text("mask_position", &self.mask_position) .build(), ) .await diff --git a/src/requests/all/create_new_sticker_set.rs b/src/requests/all/create_new_sticker_set.rs index cfb98634..8cffc8ae 100644 --- a/src/requests/all/create_new_sticker_set.rs +++ b/src/requests/all/create_new_sticker_set.rs @@ -32,20 +32,14 @@ impl Request for CreateNewStickerSet { self.bot.token(), "createNewStickerSet", FormBuilder::new() - .add("user_id", &self.user_id) - .await - .add("name", &self.name) - .await - .add("title", &self.title) - .await - .add("png_sticker", &self.png_sticker) - .await - .add("emojis", &self.emojis) - .await - .add("contains_masks", &self.contains_masks) - .await - .add("mask_position", &self.mask_position) + .add_text("user_id", &self.user_id) + .add_text("name", &self.name) + .add_text("title", &self.title) + .add_input_file("png_sticker", &self.png_sticker) .await + .add_text("emojis", &self.emojis) + .add_text("contains_masks", &self.contains_masks) + .add_text("mask_position", &self.mask_position) .build(), ) .await diff --git a/src/requests/all/edit_message_media.rs b/src/requests/all/edit_message_media.rs index e2d365b0..27cb1dd0 100644 --- a/src/requests/all/edit_message_media.rs +++ b/src/requests/all/edit_message_media.rs @@ -38,14 +38,12 @@ impl Request for EditMessageMedia { match &self.chat_or_inline_message { ChatOrInlineMessage::Chat { chat_id, message_id } => { params = params - .add("chat_id", chat_id) - .await - .add("message_id", message_id) - .await; + .add_text("chat_id", chat_id) + .add_text("message_id", message_id); } ChatOrInlineMessage::Inline { inline_message_id } => { params = - params.add("inline_message_id", inline_message_id).await; + params.add_text("inline_message_id", inline_message_id); } } @@ -54,10 +52,8 @@ impl Request for EditMessageMedia { self.bot.token(), "editMessageMedia", params - .add("media", &self.media) - .await - .add("reply_markup", &self.reply_markup) - .await + .add_text("media", &self.media) + .add_text("reply_markup", &self.reply_markup) .build(), ) .await diff --git a/src/requests/all/send_animation.rs b/src/requests/all/send_animation.rs index cbd40485..f79c2c0b 100644 --- a/src/requests/all/send_animation.rs +++ b/src/requests/all/send_animation.rs @@ -34,34 +34,27 @@ impl Request for SendAnimation { type Output = Message; async fn send(&self) -> ResponseResult { + let mut builder = + FormBuilder::new() + .add_text("chat_id", &self.chat_id) + .add_input_file("animation", &self.animation) + .await + .add_text("duration", &self.duration) + .add_text("width", &self.width) + .add_text("height", &self.height) + .add_text("caption", &self.caption) + .add_text("parse_mode", &self.parse_mode) + .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; + } net::request_multipart( self.bot.client(), self.bot.token(), "sendAnimation", - FormBuilder::new() - .add("chat_id", &self.chat_id) - .await - .add("animation", &self.animation) - .await - .add("duration", &self.duration) - .await - .add("width", &self.width) - .await - .add("height", &self.height) - .await - .add("thumb", &self.thumb) - .await - .add("caption", &self.caption) - .await - .add("parse_mode", &self.parse_mode) - .await - .add("disable_notification", &self.disable_notification) - .await - .add("reply_to_message_id", &self.reply_to_message_id) - .await - .add("reply_markup", &self.reply_markup) - .await - .build(), + builder.build(), ) .await } diff --git a/src/requests/all/send_audio.rs b/src/requests/all/send_audio.rs index 829e41f9..85add351 100644 --- a/src/requests/all/send_audio.rs +++ b/src/requests/all/send_audio.rs @@ -38,34 +38,26 @@ impl Request for SendAudio { type Output = Message; async fn send(&self) -> ResponseResult { + let mut builder = FormBuilder::new() + .add_text("chat_id", &self.chat_id) + .add_input_file("audio", &self.audio) + .await + .add_text("caption", &self.caption) + .add_text("parse_mode", &self.parse_mode) + .add_text("duration", &self.duration) + .add_text("performer", &self.performer) + .add_text("title", &self.title) + .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; + } net::request_multipart( self.bot.client(), self.bot.token(), "sendAudio", - FormBuilder::new() - .add("chat_id", &self.chat_id) - .await - .add("audio", &self.audio) - .await - .add("caption", &self.caption) - .await - .add("parse_mode", &self.parse_mode) - .await - .add("duration", &self.duration) - .await - .add("performer", &self.performer) - .await - .add("title", &self.title) - .await - .add("thumb", &self.thumb) - .await - .add("disable_notification", &self.disable_notification) - .await - .add("reply_to_message_id", &self.reply_to_message_id) - .await - .add("reply_markup", &self.reply_markup) - .await - .build(), + builder.build() ) .await } diff --git a/src/requests/all/send_document.rs b/src/requests/all/send_document.rs index 3e2cfd46..482f4a69 100644 --- a/src/requests/all/send_document.rs +++ b/src/requests/all/send_document.rs @@ -30,28 +30,24 @@ impl Request for SendDocument { type Output = Message; async fn send(&self) -> ResponseResult { + let mut builder = FormBuilder::new() + .add_text("chat_id", &self.chat_id) + .add_input_file("document", &self.document) + .await + .add_text("caption", &self.caption) + .add_text("parse_mode", &self.parse_mode) + .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; + } net::request_multipart( self.bot.client(), self.bot.token(), "sendDocument", - FormBuilder::new() - .add("chat_id", &self.chat_id) - .await - .add("document", &self.document) - .await - .add("thumb", &self.thumb) - .await - .add("caption", &self.caption) - .await - .add("parse_mode", &self.parse_mode) - .await - .add("disable_notification", &self.disable_notification) - .await - .add("reply_to_message_id", &self.reply_to_message_id) - .await - .add("reply_markup", &self.reply_markup) - .await - .build(), + builder.build() ) .await } diff --git a/src/requests/all/send_media_group.rs b/src/requests/all/send_media_group.rs index cae6604d..b6f2d9c3 100644 --- a/src/requests/all/send_media_group.rs +++ b/src/requests/all/send_media_group.rs @@ -28,14 +28,10 @@ impl Request for SendMediaGroup { self.bot.token(), "sendMediaGroup", FormBuilder::new() - .add("chat_id", &self.chat_id) - .await - .add("media", &self.media) - .await - .add("disable_notification", &self.disable_notification) - .await - .add("reply_to_message_id", &self.reply_to_message_id) - .await + .add_text("chat_id", &self.chat_id) + .add_text("media", &self.media) + .add_text("disable_notification", &self.disable_notification) + .add_text("reply_to_message_id", &self.reply_to_message_id) .build(), ) .await diff --git a/src/requests/all/send_photo.rs b/src/requests/all/send_photo.rs index ea603eca..3191373c 100644 --- a/src/requests/all/send_photo.rs +++ b/src/requests/all/send_photo.rs @@ -31,20 +31,14 @@ impl Request for SendPhoto { self.bot.token(), "sendPhoto", FormBuilder::new() - .add("chat_id", &self.chat_id) - .await - .add("photo", &self.photo) - .await - .add("caption", &self.caption) - .await - .add("parse_mode", &self.parse_mode) - .await - .add("disable_notification", &self.disable_notification) - .await - .add("reply_to_message_id", &self.reply_to_message_id) - .await - .add("reply_markup", &self.reply_markup) + .add_text("chat_id", &self.chat_id) + .add_input_file("photo", &self.photo) .await + .add_text("caption", &self.caption) + .add_text("parse_mode", &self.parse_mode) + .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 diff --git a/src/requests/all/send_sticker.rs b/src/requests/all/send_sticker.rs index e44dbb80..ae5c898f 100644 --- a/src/requests/all/send_sticker.rs +++ b/src/requests/all/send_sticker.rs @@ -31,16 +31,12 @@ impl Request for SendSticker { self.bot.token(), "sendSticker", FormBuilder::new() - .add("chat_id", &self.chat_id) - .await - .add("sticker", &self.sticker) - .await - .add("disable_notification", &self.disable_notification) - .await - .add("reply_to_message_id", &self.reply_to_message_id) - .await - .add("reply_markup", &self.reply_markup) + .add_text("chat_id", &self.chat_id) + .add_input_file("sticker", &self.sticker) .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 diff --git a/src/requests/all/send_video.rs b/src/requests/all/send_video.rs index 8e89ef2e..450fd224 100644 --- a/src/requests/all/send_video.rs +++ b/src/requests/all/send_video.rs @@ -35,36 +35,27 @@ impl Request for SendVideo { type Output = Message; async fn send(&self) -> ResponseResult { + let mut builder = FormBuilder::new() + .add_text("chat_id", &self.chat_id) + .add_input_file("video", &self.video) + .await + .add_text("duration", &self.duration) + .add_text("width", &self.width) + .add_text("height", &self.height) + .add_text("caption", &self.caption) + .add_text("parse_mode", &self.parse_mode) + .add_text("supports_streaming", &self.supports_streaming) + .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; + } net::request_multipart( self.bot.client(), self.bot.token(), "sendVideo", - FormBuilder::new() - .add("chat_id", &self.chat_id) - .await - .add("video", &self.video) - .await - .add("duration", &self.duration) - .await - .add("width", &self.width) - .await - .add("height", &self.height) - .await - .add("thumb", &self.thumb) - .await - .add("caption", &self.caption) - .await - .add("parse_mode", &self.parse_mode) - .await - .add("supports_streaming", &self.supports_streaming) - .await - .add("disable_notification", &self.disable_notification) - .await - .add("reply_to_message_id", &self.reply_to_message_id) - .await - .add("reply_markup", &self.reply_markup) - .await - .build(), + builder.build(), ) .await } diff --git a/src/requests/all/send_video_note.rs b/src/requests/all/send_video_note.rs index e3099c35..cf32b467 100644 --- a/src/requests/all/send_video_note.rs +++ b/src/requests/all/send_video_note.rs @@ -30,28 +30,23 @@ impl Request for SendVideoNote { type Output = Message; async fn send(&self) -> ResponseResult { + let mut builder = FormBuilder::new() + .add_text("chat_id", &self.chat_id) + .add_input_file("video_note", &self.video_note) + .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; + } net::request_multipart( self.bot.client(), self.bot.token(), "sendVideoNote", - FormBuilder::new() - .add("chat_id", &self.chat_id) - .await - .add("video_note", &self.video_note) - .await - .add("duration", &self.duration) - .await - .add("length", &self.length) - .await - .add("thumb", &self.thumb) - .await - .add("disable_notification", &self.disable_notification) - .await - .add("reply_to_message_id", &self.reply_to_message_id) - .await - .add("reply_markup", &self.reply_markup) - .await - .build(), + builder.build(), ) .await } diff --git a/src/requests/all/send_voice.rs b/src/requests/all/send_voice.rs index b5ef7b5d..71b042c3 100644 --- a/src/requests/all/send_voice.rs +++ b/src/requests/all/send_voice.rs @@ -41,22 +41,15 @@ impl Request for SendVoice { self.bot.token(), "sendVoice", FormBuilder::new() - .add("chat_id", &self.chat_id) - .await - .add("voice", &self.voice) - .await - .add("caption", &self.caption) - .await - .add("parse_mode", &self.parse_mode) - .await - .add("duration", &self.duration) - .await - .add("disable_notification", &self.disable_notification) - .await - .add("reply_to_message_id", &self.reply_to_message_id) - .await - .add("reply_markup", &self.reply_markup) + .add_text("chat_id", &self.chat_id) + .add_input_file("voice", &self.voice) .await + .add_text("caption", &self.caption) + .add_text("parse_mode", &self.parse_mode) + .add_text("duration", &self.duration) + .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 diff --git a/src/requests/form_builder.rs b/src/requests/form_builder.rs index 1f433035..3d0e48ad 100644 --- a/src/requests/form_builder.rs +++ b/src/requests/form_builder.rs @@ -21,25 +21,33 @@ impl FormBuilder { Self { form: Form::new() } } - /// Add the supplied key-value pair to this `FormBuilder`. - pub async fn add<'a, T, N>(self, name: N, value: &T) -> Self - where - N: Into>, - T: IntoFormValue, + pub fn add_text<'a, T, N>(self, name: N, value: &T) -> Self + where + N: Into>, + T: IntoFormText, { - let name = name.into().into_owned(); - match value.into_form_value() { - Some(FormValue::Str(string)) => { - Self { form: self.form.text(name, string) } - } - Some(FormValue::File(path)) => self.add_file(name, path).await, - Some(FormValue::Memory { file_name, data }) => { - self.add_file_from_memory(name, file_name, data) - } - None => self, + match value.into_form_text() { + 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) -> Self + where + N: Into>, + { + 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::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 where @@ -75,24 +83,18 @@ impl FormBuilder { } } -pub(crate) enum FormValue { - File(PathBuf), - Memory { file_name: String, data: Cow<'static, [u8]> }, - Str(String), -} - -pub(crate) trait IntoFormValue { - fn into_form_value(&self) -> Option; +pub(crate) trait IntoFormText { + fn into_form_text(&self) -> Option; } macro_rules! impl_for_struct { ($($name:ty),*) => { $( - impl IntoFormValue for $name { - fn into_form_value(&self) -> Option { + impl IntoFormText for $name { + fn into_form_text(&self) -> Option { let json = serde_json::to_string(self) .expect("serde_json::to_string failed"); - Some(FormValue::Str(json)) + Some(json) } } )* @@ -109,77 +111,63 @@ impl_for_struct!( MaskPosition ); -impl IntoFormValue for Option +impl IntoFormText for Option where - T: IntoFormValue, + T: IntoFormText, { - fn into_form_value(&self) -> Option { - self.as_ref().and_then(IntoFormValue::into_form_value) + fn into_form_text(&self) -> Option { + self.as_ref().and_then(IntoFormText::into_form_text) } } // TODO: fix InputMedia implementation of IntoFormValue (for now it doesn't // encode files :|) -impl IntoFormValue for Vec { - fn into_form_value(&self) -> Option { +impl IntoFormText for Vec { + fn into_form_text(&self) -> Option { let json = serde_json::to_string(self).expect("serde_json::to_string failed"); - Some(FormValue::Str(json)) + Some(json) } } -impl IntoFormValue for InputMedia { - fn into_form_value(&self) -> Option { +impl IntoFormText for InputMedia { + fn into_form_text(&self) -> Option { let json = serde_json::to_string(self).expect("serde_json::to_string failed"); - Some(FormValue::Str(json)) + Some(json) } } -impl IntoFormValue for str { - fn into_form_value(&self) -> Option { - Some(FormValue::Str(self.to_owned())) +impl IntoFormText for str { + fn into_form_text(&self) -> Option { + Some(self.to_owned()) } } -impl IntoFormValue for ParseMode { - fn into_form_value(&self) -> Option { +impl IntoFormText for ParseMode { + fn into_form_text(&self) -> Option { let string = match self { ParseMode::MarkdownV2 => String::from("MarkdownV2"), ParseMode::HTML => String::from("HTML"), #[allow(deprecated)] ParseMode::Markdown => String::from("Markdown"), }; - Some(FormValue::Str(string)) + Some(string) } } -impl IntoFormValue for ChatId { - fn into_form_value(&self) -> Option { +impl IntoFormText for ChatId { + fn into_form_text(&self) -> Option { let string = match self { ChatId::Id(id) => id.to_string(), ChatId::ChannelUsername(username) => username.clone(), }; - Some(FormValue::Str(string)) + Some(string) } } -impl IntoFormValue for String { - fn into_form_value(&self) -> Option { - Some(FormValue::Str(self.clone())) - } -} - -impl IntoFormValue for InputFile { - fn into_form_value(&self) -> Option { - match self { - InputFile::File(path) => Some(FormValue::File(path.clone())), - InputFile::Memory { file_name, data } => Some(FormValue::Memory { - file_name: file_name.clone(), - data: data.clone(), - }), - InputFile::Url(url) => Some(FormValue::Str(url.clone())), - InputFile::FileId(file_id) => Some(FormValue::Str(file_id.clone())), - } +impl IntoFormText for String { + fn into_form_text(&self) -> Option { + Some(self.clone()) } } From 26092cb4efc159c7056a491d2054c8bb2a8709e2 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Sat, 4 Jul 2020 19:51:45 +0600 Subject: [PATCH 30/46] Oops, correct README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2491e632..cc1a744a 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@

Functional reactive design

-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

Persistence

-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

Persistence

-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.


From a68b25a1e12e9cfe161165fade8ea320ffe0b724 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Sat, 4 Jul 2020 21:27:08 +0600 Subject: [PATCH 42/46] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 785bc166..78b3a949 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,7 @@ All the API

Persistence

-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. +Dialogues management is independent of how/where they are stored: you can just replace one line and make them persistent. By default, teloxide stores all user dialogues in RAM. Out-of-the-box storages include Redis.


From 26557eed8cbf7a5e5ac65f8c4d6fa7568ea97d15 Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Sat, 4 Jul 2020 21:27:55 +0600 Subject: [PATCH 43/46] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 78b3a949..30e7b73f 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,7 @@ All the API

Persistence

-Dialogues management is independent of how/where they are stored: you can just replace one line and make them persistent. By default, teloxide stores all user dialogues in RAM. Out-of-the-box storages include Redis. +Dialogues management is independent of how/where dialogues are stored: you can just replace one line and make them persistent. By default, teloxide stores all user dialogues in RAM. Out-of-the-box storages include Redis.


From a3fd06d96626a4257c18083e66598417371b9a3d Mon Sep 17 00:00:00 2001 From: Temirkhan Myrzamadi Date: Sat, 4 Jul 2020 21:28:35 +0600 Subject: [PATCH 44/46] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 30e7b73f..6ad32d8d 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,7 @@ All the API

Persistence

-Dialogues management is independent of how/where dialogues are stored: you can just replace one line and make them persistent. By default, teloxide stores all user dialogues in RAM. Out-of-the-box storages include Redis. +Dialogues management is independent of how/where dialogues are stored: you can just replace one line and make them persistent. Out-of-the-box storages include Redis.


From 44584938f5b6df083eda5e744ef14937288635af Mon Sep 17 00:00:00 2001 From: p0lunin Date: Sat, 4 Jul 2020 20:00:48 +0300 Subject: [PATCH 45/46] renamed RequestFile to RequestWithFile --- src/requests/all/add_sticker_to_set.rs | 4 ++-- src/requests/all/create_new_sticker_set.rs | 4 ++-- src/requests/all/send_animation.rs | 4 ++-- src/requests/all/send_audio.rs | 4 ++-- src/requests/all/send_document.rs | 4 ++-- src/requests/all/send_photo.rs | 4 ++-- src/requests/all/send_sticker.rs | 4 ++-- src/requests/all/send_video.rs | 4 ++-- src/requests/all/send_video_note.rs | 4 ++-- src/requests/all/send_voice.rs | 4 ++-- src/requests/mod.rs | 2 +- 11 files changed, 21 insertions(+), 21 deletions(-) diff --git a/src/requests/all/add_sticker_to_set.rs b/src/requests/all/add_sticker_to_set.rs index 7f114e37..23facb90 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::{RequestFile, ResponseResult}; +use crate::requests::{RequestWithFile, ResponseResult}; use std::sync::Arc; /// Use this method to add a new sticker to a set created by the bot. @@ -22,7 +22,7 @@ pub struct AddStickerToSet { } #[async_trait::async_trait] -impl RequestFile for AddStickerToSet { +impl RequestWithFile for AddStickerToSet { type Output = True; async fn send(&self) -> tokio::io::Result> { diff --git a/src/requests/all/create_new_sticker_set.rs b/src/requests/all/create_new_sticker_set.rs index 7bbf9427..7c80840b 100644 --- a/src/requests/all/create_new_sticker_set.rs +++ b/src/requests/all/create_new_sticker_set.rs @@ -1,6 +1,6 @@ use crate::{ net, - requests::{form_builder::FormBuilder, RequestFile, ResponseResult}, + requests::{form_builder::FormBuilder, RequestWithFile, ResponseResult}, types::{InputFile, MaskPosition, True}, Bot, }; @@ -23,7 +23,7 @@ pub struct CreateNewStickerSet { } #[async_trait::async_trait] -impl RequestFile for CreateNewStickerSet { +impl RequestWithFile for CreateNewStickerSet { type Output = True; async fn send(&self) -> tokio::io::Result> { diff --git a/src/requests/all/send_animation.rs b/src/requests/all/send_animation.rs index b9b4073c..ee5f2d39 100644 --- a/src/requests/all/send_animation.rs +++ b/src/requests/all/send_animation.rs @@ -1,6 +1,6 @@ use crate::{ net, - requests::{form_builder::FormBuilder, RequestFile, ResponseResult}, + requests::{form_builder::FormBuilder, RequestWithFile, ResponseResult}, types::{ChatId, InputFile, Message, ParseMode, ReplyMarkup}, Bot, }; @@ -30,7 +30,7 @@ pub struct SendAnimation { } #[async_trait::async_trait] -impl RequestFile for SendAnimation { +impl RequestWithFile for SendAnimation { type Output = Message; async fn send(&self) -> tokio::io::Result> { diff --git a/src/requests/all/send_audio.rs b/src/requests/all/send_audio.rs index b3137553..f7f45f27 100644 --- a/src/requests/all/send_audio.rs +++ b/src/requests/all/send_audio.rs @@ -1,6 +1,6 @@ use crate::{ net, - requests::{form_builder::FormBuilder, RequestFile, ResponseResult}, + requests::{form_builder::FormBuilder, RequestWithFile, ResponseResult}, types::{ChatId, InputFile, Message, ParseMode, ReplyMarkup}, Bot, }; @@ -34,7 +34,7 @@ pub struct SendAudio { } #[async_trait::async_trait] -impl RequestFile for SendAudio { +impl RequestWithFile for SendAudio { type Output = Message; async fn send(&self) -> tokio::io::Result> { diff --git a/src/requests/all/send_document.rs b/src/requests/all/send_document.rs index 8c3ffb6b..bd97fb91 100644 --- a/src/requests/all/send_document.rs +++ b/src/requests/all/send_document.rs @@ -1,6 +1,6 @@ use crate::{ net, - requests::{form_builder::FormBuilder, RequestFile, ResponseResult}, + requests::{form_builder::FormBuilder, RequestWithFile, ResponseResult}, types::{ChatId, InputFile, Message, ParseMode, ReplyMarkup}, Bot, }; @@ -26,7 +26,7 @@ pub struct SendDocument { } #[async_trait::async_trait] -impl RequestFile for SendDocument { +impl RequestWithFile for SendDocument { type Output = Message; async fn send(&self) -> tokio::io::Result> { diff --git a/src/requests/all/send_photo.rs b/src/requests/all/send_photo.rs index 0cdea7cb..247257b1 100644 --- a/src/requests/all/send_photo.rs +++ b/src/requests/all/send_photo.rs @@ -1,6 +1,6 @@ use crate::{ net, - requests::{form_builder::FormBuilder, RequestFile, ResponseResult}, + requests::{form_builder::FormBuilder, RequestWithFile, ResponseResult}, types::{ChatId, InputFile, Message, ParseMode, ReplyMarkup}, Bot, }; @@ -22,7 +22,7 @@ pub struct SendPhoto { } #[async_trait::async_trait] -impl RequestFile for SendPhoto { +impl RequestWithFile for SendPhoto { type Output = Message; async fn send(&self) -> tokio::io::Result> { diff --git a/src/requests/all/send_sticker.rs b/src/requests/all/send_sticker.rs index ed312b3d..37ca4769 100644 --- a/src/requests/all/send_sticker.rs +++ b/src/requests/all/send_sticker.rs @@ -1,6 +1,6 @@ use crate::{ net, - requests::{form_builder::FormBuilder, RequestFile, ResponseResult}, + requests::{form_builder::FormBuilder, RequestWithFile, ResponseResult}, types::{ChatId, InputFile, Message, ReplyMarkup}, Bot, }; @@ -22,7 +22,7 @@ pub struct SendSticker { } #[async_trait::async_trait] -impl RequestFile for SendSticker { +impl RequestWithFile for SendSticker { type Output = Message; async fn send(&self) -> tokio::io::Result> { diff --git a/src/requests/all/send_video.rs b/src/requests/all/send_video.rs index 64edf2df..b908fa5e 100644 --- a/src/requests/all/send_video.rs +++ b/src/requests/all/send_video.rs @@ -1,6 +1,6 @@ use crate::{ net, - requests::{form_builder::FormBuilder, RequestFile, ResponseResult}, + requests::{form_builder::FormBuilder, RequestWithFile, ResponseResult}, types::{ChatId, InputFile, Message, ParseMode, ReplyMarkup}, Bot, }; @@ -31,7 +31,7 @@ pub struct SendVideo { } #[async_trait::async_trait] -impl RequestFile for SendVideo { +impl RequestWithFile for SendVideo { type Output = Message; async fn send(&self) -> tokio::io::Result> { diff --git a/src/requests/all/send_video_note.rs b/src/requests/all/send_video_note.rs index 8eb45c42..e06f804b 100644 --- a/src/requests/all/send_video_note.rs +++ b/src/requests/all/send_video_note.rs @@ -1,6 +1,6 @@ use crate::{ net, - requests::{form_builder::FormBuilder, RequestFile, ResponseResult}, + requests::{form_builder::FormBuilder, RequestWithFile, ResponseResult}, types::{ChatId, InputFile, Message, ReplyMarkup}, Bot, }; @@ -26,7 +26,7 @@ pub struct SendVideoNote { } #[async_trait::async_trait] -impl RequestFile for SendVideoNote { +impl RequestWithFile for SendVideoNote { type Output = Message; async fn send(&self) -> tokio::io::Result> { diff --git a/src/requests/all/send_voice.rs b/src/requests/all/send_voice.rs index 7cea5a36..44f69fc7 100644 --- a/src/requests/all/send_voice.rs +++ b/src/requests/all/send_voice.rs @@ -1,6 +1,6 @@ use crate::{ net, - requests::{form_builder::FormBuilder, RequestFile, ResponseResult}, + requests::{form_builder::FormBuilder, RequestWithFile, ResponseResult}, types::{ChatId, InputFile, Message, ParseMode, ReplyMarkup}, Bot, }; @@ -32,7 +32,7 @@ pub struct SendVoice { } #[async_trait::async_trait] -impl RequestFile for SendVoice { +impl RequestWithFile for SendVoice { type Output = Message; async fn send(&self) -> tokio::io::Result> { diff --git a/src/requests/mod.rs b/src/requests/mod.rs index 3e70c321..d4aa6704 100644 --- a/src/requests/mod.rs +++ b/src/requests/mod.rs @@ -21,7 +21,7 @@ pub trait Request { /// Designates an API request. #[async_trait::async_trait] -pub trait RequestFile { +pub trait RequestWithFile { /// A data structure returned if success. type Output; From 5ceeae7f0787e0236c8a970cd5cb0a6221b26ca1 Mon Sep 17 00:00:00 2001 From: p0lunin Date: Sat, 4 Jul 2020 20:02:45 +0300 Subject: [PATCH 46/46] update documentation --- src/requests/mod.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/requests/mod.rs b/src/requests/mod.rs index d4aa6704..09f2af6c 100644 --- a/src/requests/mod.rs +++ b/src/requests/mod.rs @@ -19,12 +19,14 @@ pub trait Request { async fn send(&self) -> ResponseResult; } -/// Designates an API request. +/// Designates an API request with possibly sending file. #[async_trait::async_trait] pub trait RequestWithFile { /// A data structure returned if success. type Output; /// Asynchronously sends this request to Telegram and returns the result. + /// Returns `tokio::io::Result::Err` when trying to send file which does not + /// exists. async fn send(&self) -> tokio::io::Result>; }