From 553ffd928be6983768e1a9ae4893fe6df60e6787 Mon Sep 17 00:00:00 2001 From: Waffle Date: Thu, 19 Sep 2019 01:37:45 +0300 Subject: [PATCH 01/26] Fix some "unused imports" warnings --- src/core/types/inline_query_result_contact.rs | 4 +--- src/core/types/inline_query_result_game.rs | 4 +--- src/core/types/inline_query_result_location.rs | 4 +--- src/core/types/inline_query_result_photo.rs | 3 +++ src/core/types/inline_query_result_venue.rs | 4 +--- 5 files changed, 7 insertions(+), 12 deletions(-) diff --git a/src/core/types/inline_query_result_contact.rs b/src/core/types/inline_query_result_contact.rs index fb4b2d28..4f9c704d 100644 --- a/src/core/types/inline_query_result_contact.rs +++ b/src/core/types/inline_query_result_contact.rs @@ -1,6 +1,4 @@ -use crate::core::types::{ - InlineKeyboardMarkup, InputMessageContent, ParseMode, -}; +use crate::core::types::{InlineKeyboardMarkup, InputMessageContent}; #[derive(Debug, Serialize, PartialEq, Clone)] pub struct InlineQueryResultContact { diff --git a/src/core/types/inline_query_result_game.rs b/src/core/types/inline_query_result_game.rs index f19aa16f..9a01d158 100644 --- a/src/core/types/inline_query_result_game.rs +++ b/src/core/types/inline_query_result_game.rs @@ -1,6 +1,4 @@ -use crate::core::types::{ - InlineKeyboardMarkup, InputMessageContent, ParseMode, -}; +use crate::core::types::InlineKeyboardMarkup; #[derive(Debug, Serialize, Hash, PartialEq, Eq, Clone)] pub struct InlineQueryResultGame { diff --git a/src/core/types/inline_query_result_location.rs b/src/core/types/inline_query_result_location.rs index a89ca988..846a3f04 100644 --- a/src/core/types/inline_query_result_location.rs +++ b/src/core/types/inline_query_result_location.rs @@ -1,6 +1,4 @@ -use crate::core::types::{ - InlineKeyboardMarkup, InputMessageContent, ParseMode, -}; +use crate::core::types::{InlineKeyboardMarkup, InputMessageContent}; #[derive(Debug, Serialize, PartialEq, Clone)] pub struct InlineQueryResultLocation { diff --git a/src/core/types/inline_query_result_photo.rs b/src/core/types/inline_query_result_photo.rs index d0626100..c752ae8a 100644 --- a/src/core/types/inline_query_result_photo.rs +++ b/src/core/types/inline_query_result_photo.rs @@ -2,6 +2,9 @@ use crate::core::types::{ InlineKeyboardMarkup, InputMessageContent, ParseMode, }; +/// Represents a link to a photo. By default, this photo will be sent by the +/// user with optional caption. Alternatively, you can use input_message_content +/// to send a message with the specified content instead of the photo. #[derive(Debug, Serialize, PartialEq, Clone)] pub struct InlineQueryResultPhoto { pub id: String, diff --git a/src/core/types/inline_query_result_venue.rs b/src/core/types/inline_query_result_venue.rs index 9237e001..e018a55c 100644 --- a/src/core/types/inline_query_result_venue.rs +++ b/src/core/types/inline_query_result_venue.rs @@ -1,6 +1,4 @@ -use crate::core::types::{ - InlineKeyboardMarkup, InputMessageContent, ParseMode, -}; +use crate::core::types::{InlineKeyboardMarkup, InputMessageContent}; #[derive(Debug, Serialize, PartialEq, Clone)] pub struct InlineQueryResultVenue { From 9cc5806f0779585ad6702d8f227aeae04a1ed1f5 Mon Sep 17 00:00:00 2001 From: Waffle Date: Thu, 19 Sep 2019 01:45:40 +0300 Subject: [PATCH 02/26] Fix some visibility issues that were causing warnings --- src/core/requests/get_file.rs | 8 ++++++-- src/core/requests/send_chat_action.rs | 4 ++-- src/core/requests/send_contact.rs | 2 +- src/core/requests/send_poll.rs | 2 +- src/core/requests/send_venue.rs | 4 ++-- 5 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/core/requests/get_file.rs b/src/core/requests/get_file.rs index 089ff743..9ab0788d 100644 --- a/src/core/requests/get_file.rs +++ b/src/core/requests/get_file.rs @@ -12,11 +12,11 @@ use crate::core::types::File; /// It is guaranteed that the link will be valid for at least 1 hour. /// When the link expires, a new one can be requested by calling getFile again. #[derive(Debug, Clone, Serialize)] -struct GetFile<'a> { +pub struct GetFile<'a> { #[serde(skip_serializing)] ctx: RequestContext<'a>, /// File identifier to get info about - file_id: String, + pub file_id: String, } impl<'a> Request<'a> for GetFile<'a> { @@ -36,6 +36,10 @@ impl<'a> Request<'a> for GetFile<'a> { } impl<'a> GetFile<'a> { + pub(crate) fn new(ctx: RequestContext<'a>, file_id: String) -> Self { + Self { ctx, file_id } + } + pub fn file_id(mut self, file_id: T) -> Self where T: Into, diff --git a/src/core/requests/send_chat_action.rs b/src/core/requests/send_chat_action.rs index ae07ec05..9b9bf940 100644 --- a/src/core/requests/send_chat_action.rs +++ b/src/core/requests/send_chat_action.rs @@ -8,7 +8,7 @@ use crate::core::requests::{ /// arrives from your bot, Telegram clients clear its typing status). /// Returns True on success. #[derive(Debug, Clone, Serialize)] -struct SendChatAction<'a> { +pub struct SendChatAction<'a> { #[serde(skip_serializing)] ctx: RequestContext<'a>, /// Unique identifier for the target chat or @@ -24,7 +24,7 @@ struct SendChatAction<'a> { #[derive(Debug, Serialize, From, Clone)] #[serde(rename_all = "snake_case")] -enum ChatAction { +pub enum ChatAction { Typing, UploadPhoto, RecordVideo, diff --git a/src/core/requests/send_contact.rs b/src/core/requests/send_contact.rs index 16cbec0e..a064dd21 100644 --- a/src/core/requests/send_contact.rs +++ b/src/core/requests/send_contact.rs @@ -7,7 +7,7 @@ use crate::core::types::{Message, ReplyMarkup}; /// Use this method to send phone contacts. /// returned. #[derive(Debug, Clone, Serialize)] -struct SendContact<'a> { +pub struct SendContact<'a> { #[serde(skip_serializing)] ctx: RequestContext<'a>, /// Unique identifier for the target chat or diff --git a/src/core/requests/send_poll.rs b/src/core/requests/send_poll.rs index 861c98e7..92a11901 100644 --- a/src/core/requests/send_poll.rs +++ b/src/core/requests/send_poll.rs @@ -7,7 +7,7 @@ use crate::core::types::{Message, ReplyMarkup}; /// Use this method to send a native poll. A native poll can't be sent to a /// private chat. On success, the sent Message is returned. #[derive(Debug, Clone, Serialize)] -struct SendPoll<'a> { +pub struct SendPoll<'a> { #[serde(skip_serializing)] ctx: RequestContext<'a>, /// identifier for the target chat or username of the target channel (in diff --git a/src/core/requests/send_venue.rs b/src/core/requests/send_venue.rs index 85fee6d6..cdb5fb90 100644 --- a/src/core/requests/send_venue.rs +++ b/src/core/requests/send_venue.rs @@ -7,7 +7,7 @@ use crate::core::types::{Message, ReplyMarkup}; /// Use this method to send information about a venue. /// Message is returned. #[derive(Debug, Clone, Serialize)] -struct SendVenue<'a> { +pub struct SendVenue<'a> { #[serde(skip_serializing)] ctx: RequestContext<'a>, /// Unique identifier for the target chat or @@ -62,7 +62,7 @@ impl<'a> Request<'a> for SendVenue<'a> { } impl<'a> SendVenue<'a> { - pub fn new( + pub(crate) fn new( ctx: RequestContext<'a>, chat_id: ChatId, latitude: f64, From fe75e0ff07ee669b5b96f0b33b18d038ea61b6b1 Mon Sep 17 00:00:00 2001 From: Waffle Date: Thu, 19 Sep 2019 01:59:16 +0300 Subject: [PATCH 03/26] Add `MigrateToChatId` and `RetryAfter` to `RequestError` --- src/core/network/mod.rs | 56 +++++++++++++++++++++++----------------- src/core/requests/mod.rs | 12 +++++++++ 2 files changed, 44 insertions(+), 24 deletions(-) diff --git a/src/core/network/mod.rs b/src/core/network/mod.rs index 244c6d63..b890a173 100644 --- a/src/core/network/mod.rs +++ b/src/core/network/mod.rs @@ -57,18 +57,7 @@ pub async fn request_multipart( ) .map_err(RequestError::InvalidJson)?; - match response { - TelegramResponse::Ok { result, .. } => Ok(result), - TelegramResponse::Err { - description, - error_code, - response_parameters: _, - .. - } => Err(RequestError::ApiError { - description, - status_code: StatusCode::from_u16(error_code).unwrap(), - }), - } + response.into() } pub async fn request_json( @@ -89,18 +78,7 @@ pub async fn request_json( ) .map_err(RequestError::InvalidJson)?; - match response { - TelegramResponse::Ok { result, .. } => Ok(result), - TelegramResponse::Err { - description, - error_code, - response_parameters: _, - .. - } => Err(RequestError::ApiError { - description, - status_code: StatusCode::from_u16(error_code).unwrap(), - }), - } + response.into() } #[derive(Deserialize)] @@ -124,6 +102,36 @@ enum TelegramResponse { }, } +impl Into> for TelegramResponse { + fn into(self) -> Result { + match self { + TelegramResponse::Ok { result, .. } => Ok(result), + TelegramResponse::Err { + description, + error_code, + response_parameters, + .. + } => { + if let Some(params) = response_parameters { + match params { + ResponseParameters::RetryAfter(i) => { + Err(RequestError::RetryAfter(i)) + } + ResponseParameters::MigrateToChatId(to) => { + Err(RequestError::MigrateToChatId(to)) + } + } + } else { + Err(RequestError::ApiError { + description, + status_code: StatusCode::from_u16(error_code).unwrap(), + }) + } + } + } + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/core/requests/mod.rs b/src/core/requests/mod.rs index a1dfa82f..aa6bb37f 100644 --- a/src/core/requests/mod.rs +++ b/src/core/requests/mod.rs @@ -16,6 +16,16 @@ pub enum RequestError { description: String, }, + /// The group has been migrated to a supergroup with the specified + /// identifier. + #[display(fmt = "The group has been migrated to a supergroup with id {id}", id = _0)] + MigrateToChatId(i64), + + /// In case of exceeding flood control, the number of seconds left to wait + /// before the request can be repeated + #[display(fmt = "Retry after {secs} seconds", secs = _0)] + RetryAfter(i32), + #[display(fmt = "Network error: {err}", err = _0)] NetworkError(reqwest::Error), @@ -27,6 +37,8 @@ impl std::error::Error for RequestError { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match self { RequestError::ApiError { .. } => None, + RequestError::MigrateToChatId(_) => None, + RequestError::RetryAfter(_) => None, RequestError::NetworkError(err) => Some(err), RequestError::InvalidJson(err) => Some(err), } From 9f157057ecc6524e7f3a3b57dc983b3f28049449 Mon Sep 17 00:00:00 2001 From: Waffle Date: Thu, 19 Sep 2019 16:31:11 +0300 Subject: [PATCH 04/26] Remove done TODO --- src/core/requests/mod.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/core/requests/mod.rs b/src/core/requests/mod.rs index aa6bb37f..7deba0fd 100644 --- a/src/core/requests/mod.rs +++ b/src/core/requests/mod.rs @@ -11,7 +11,6 @@ mod utils; pub enum RequestError { #[display(fmt = "Telegram error #{}: {}", status_code, description)] ApiError { - // TODO: add response parameters status_code: StatusCode, description: String, }, From c395e24792d113dd8f67323cb08295e203a7bc4c Mon Sep 17 00:00:00 2001 From: P0lunin Date: Thu, 19 Sep 2019 19:37:48 +0300 Subject: [PATCH 05/26] add enum ChatMemberStatus --- src/core/types/chat_member.rs | 73 ++++++++++++++++++++++++- src/core/types/mod.rs | 2 +- src/core/types/not_implemented_types.rs | 3 - 3 files changed, 73 insertions(+), 5 deletions(-) diff --git a/src/core/types/chat_member.rs b/src/core/types/chat_member.rs index 600b7e2e..cee60bc6 100644 --- a/src/core/types/chat_member.rs +++ b/src/core/types/chat_member.rs @@ -1,4 +1,4 @@ -use crate::core::types::{ChatMemberStatus, User}; +use crate::core::types::User; /// This object contains information about one member of the chat. #[derive(Debug, Deserialize, Hash, PartialEq, Eq, Clone)] @@ -54,3 +54,74 @@ pub struct ChatMember { /// his messages, implies can_send_media_messages pub can_add_web_page_previews: Option, } + +#[derive(Deserialize, Debug, Hash, PartialEq, Eq, Clone)] +#[serde(rename_all = "snake_case")] +pub enum ChatMemberStatus { + Creator, + Administrator, + Member, + Restricted, + Left, + Kicked +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn deserialize() { + let json = r#"{ + "user":{ + "id":12345, + "is_bot":false, + "first_name":"firstName" + }, + "status":"creator", + "until_date":123456, + "can_be_edited":true, + "can_post_messages":true, + "can_edit_messages":true, + "can_delete_messages":true, + "can_restrict_members":true, + "can_promote_members":true, + "can_change_info":true, + "can_invite_users":true, + "can_pin_messages":true, + "is_member":true, + "can_send_messages":true, + "can_send_media_messages":true, + "can_send_polls":true, + "can_send_other_messages":true, + "can_add_web_page_previews":true + }"#; + let expected = ChatMember { + user: User { + id: 12345, + is_bot: false, + first_name: "firstName".to_string(), + last_name: None, + username: None, + language_code: None + }, + status: ChatMemberStatus::Creator, + until_date: Some(123456), + can_be_edited: Some(true), + can_change_info: Some(true), + can_post_messages: Some(true), + can_edit_messages: Some(true), + can_delete_messages: Some(true), + can_invite_users: Some(true), + can_restrict_members: Some(true), + can_pin_messages: Some(true), + can_promote_members: Some(true), + can_send_messages: Some(true), + can_send_media_messages: Some(true), + can_send_other_messages: Some(true), + can_add_web_page_previews: Some(true) + }; + let actual = serde_json::from_str::(&json).unwrap(); + assert_eq!(actual, expected) + } +} \ No newline at end of file diff --git a/src/core/types/mod.rs b/src/core/types/mod.rs index 42e9869a..27ba457b 100644 --- a/src/core/types/mod.rs +++ b/src/core/types/mod.rs @@ -4,7 +4,7 @@ pub use self::{ audio::Audio, callback_query::CallbackQuery, chat::{Chat, ChatKind, NonPrivateChatKind}, - chat_member::ChatMember, + chat_member::{ChatMember, ChatMemberStatus}, chat_permissions::ChatPermissions, chat_photo::ChatPhoto, chosen_inline_result::ChosenInlineResult, diff --git a/src/core/types/not_implemented_types.rs b/src/core/types/not_implemented_types.rs index c2700b2d..c1e9d2ba 100644 --- a/src/core/types/not_implemented_types.rs +++ b/src/core/types/not_implemented_types.rs @@ -1,5 +1,2 @@ #[derive(Debug, Deserialize, Eq, Hash, PartialEq, Serialize, Clone)] pub struct PassportData; - -#[derive(Debug, Deserialize, Eq, Hash, PartialEq, Serialize, Clone)] -pub struct ChatMemberStatus; From 7b638eb2c0a73b02b99d4ed04cb05a0e2cfe90a9 Mon Sep 17 00:00:00 2001 From: P0lunin Date: Thu, 19 Sep 2019 19:38:07 +0300 Subject: [PATCH 06/26] add tests --- src/core/types/audio.rs | 46 ++++++++++++++++++++++---- src/core/types/callback_query.rs | 57 ++++++++++++++++++++++++-------- src/core/types/user.rs | 27 +++++++++++++++ 3 files changed, 110 insertions(+), 20 deletions(-) diff --git a/src/core/types/audio.rs b/src/core/types/audio.rs index 304d4946..66cc0878 100644 --- a/src/core/types/audio.rs +++ b/src/core/types/audio.rs @@ -1,17 +1,51 @@ use crate::core::types::PhotoSize; -#[derive(Debug, Deserialize, Eq, Hash, PartialEq, Serialize, Clone)] +#[derive(Debug, Deserialize, Eq, Hash, PartialEq, Clone)] pub struct Audio { pub file_id: String, pub duration: u32, - #[serde(skip_serializing_if = "Option::is_none")] pub performer: Option, - #[serde(skip_serializing_if = "Option::is_none")] pub title: Option, - #[serde(skip_serializing_if = "Option::is_none")] pub mime_type: Option, - #[serde(skip_serializing_if = "Option::is_none")] pub file_size: Option, - #[serde(skip_serializing_if = "Option::is_none")] pub thumb: Option, } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn deserialize() { + let json = r#"{ + "file_id":"id", + "duration":60, + "performer":"Performer", + "title":"Title", + "mime_type":"MimeType", + "file_size":123456, + "thumb":{ + "file_id":"id", + "width":320, + "height":320, + "file_size":3452 + } + }"#; + let expected = Audio { + file_id: "id".to_string(), + duration: 60, + performer: Some("Performer".to_string()), + title: Some("Title".to_string()), + mime_type: Some("MimeType".to_string()), + file_size: Some(123456), + thumb: Some(PhotoSize { + file_id: "id".to_string(), + width: 320, + height: 320, + file_size: Some(3452) + }) + }; + let actual = serde_json::from_str::