From d3bf4499ea4b855e05b775ed61aa2ae06810b458 Mon Sep 17 00:00:00 2001 From: Waffle Date: Tue, 31 Aug 2021 17:45:20 +0300 Subject: [PATCH] Use `url::Url` for urls, use `chrono::DateTime` for dates in types --- CHANGELOG.md | 2 + src/serde_multipart/serializers.rs | 17 +++++-- src/serde_multipart/unserializers.rs | 2 +- .../unserializers/input_file.rs | 4 +- src/types.rs | 48 +++++++++++++++---- src/types/chat_invite_link.rs | 7 ++- src/types/chat_member_updated.rs | 6 ++- src/types/inline_keyboard_button.rs | 7 +-- src/types/inline_keyboard_markup.rs | 19 +++++--- src/types/inline_query_result_article.rs | 18 +++---- src/types/inline_query_result_audio.rs | 14 ++---- .../inline_query_result_cached_document.rs | 2 +- .../inline_query_result_cached_mpeg4_gif.rs | 2 +- src/types/inline_query_result_cached_photo.rs | 2 +- src/types/inline_query_result_cached_video.rs | 2 +- src/types/inline_query_result_cached_voice.rs | 2 +- src/types/inline_query_result_contact.rs | 9 ++-- src/types/inline_query_result_document.rs | 20 +++----- src/types/inline_query_result_gif.rs | 28 ++++------- src/types/inline_query_result_location.rs | 9 ++-- src/types/inline_query_result_mpeg4_gif.rs | 30 +++++------- src/types/inline_query_result_photo.rs | 34 +++++-------- src/types/inline_query_result_venue.rs | 9 ++-- src/types/inline_query_result_video.rs | 32 +++++-------- src/types/inline_query_result_voice.rs | 14 ++---- src/types/input_file.rs | 15 +++--- src/types/login_url.rs | 35 +++++++++++--- src/types/message.rs | 19 +++++--- src/types/message_entity.rs | 12 ++--- src/types/passport_file.rs | 6 ++- src/types/poll.rs | 9 ++-- src/types/update.rs | 7 ++- src/types/voice_chat_scheduled.rs | 8 ++-- src/types/webhook_info.rs | 11 +++-- 34 files changed, 246 insertions(+), 215 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b4c12199..637e5e48 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,8 +8,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [unreleased] - Add `EditedMessageIsTooLong` error [#109][pr109] +- Use `url::Url` for urls, use `chrono::DateTime` for dates in types ([#115][pr115]) [pr109]: https://github.com/teloxide/teloxide-core/pull/109 +[pr115]: https://github.com/teloxide/teloxide-core/pull/115 ## 0.3.3 diff --git a/src/serde_multipart/serializers.rs b/src/serde_multipart/serializers.rs index 5bdce22b..c3d3f139 100644 --- a/src/serde_multipart/serializers.rs +++ b/src/serde_multipart/serializers.rs @@ -485,7 +485,8 @@ impl Serializer for PartSerializer { let part = Part::text(format!("attach://{}", uuid)); Ok((part, vec![(uuid, f)])) } - InputFile::FileId(s) | InputFile::Url(s) => Ok((Part::text(s), Vec::new())), + InputFile::FileId(s) => Ok((Part::text(s), Vec::new())), + InputFile::Url(s) => Ok((Part::text(String::from(s)), Vec::new())), } } @@ -586,7 +587,8 @@ impl SerializeStructVariant for PartFromFile { Ok((part, vec![(uuid, f)])) } - InputFile::FileId(s) | InputFile::Url(s) => Ok((Part::text(s), vec![])), + InputFile::FileId(s) => Ok((Part::text(s), vec![])), + InputFile::Url(s) => Ok((Part::text(String::from(s)), vec![])), } } } @@ -615,9 +617,12 @@ impl SerializeSeq for InnerPartSerializer { value["media"] = serde_json::Value::String(format!("attach://{}", uuid)); self.files.push((uuid, f)); } - InputFile::FileId(s) | InputFile::Url(s) => { + InputFile::FileId(s) => { value["media"] = serde_json::Value::String(s); } + InputFile::Url(s) => { + value["media"] = serde_json::Value::String(String::from(s)); + } } self.array_json_parts.push(value); @@ -673,10 +678,14 @@ impl SerializeStruct for PartSerializerStruct { self.2.push((uuid, f)); } - InputFile::FileId(s) | InputFile::Url(s) => { + InputFile::FileId(s) => { SerializeStruct::serialize_field(&mut ser, key, &s)?; self.1 = get_state(ser) } + InputFile::Url(s) => { + SerializeStruct::serialize_field(&mut ser, key, s.as_str())?; + self.1 = get_state(ser) + } } } else { SerializeStruct::serialize_field(&mut ser, key, value)?; diff --git a/src/serde_multipart/unserializers.rs b/src/serde_multipart/unserializers.rs index 1068f211..07903d80 100644 --- a/src/serde_multipart/unserializers.rs +++ b/src/serde_multipart/unserializers.rs @@ -84,7 +84,7 @@ fn test() { let value = String::from("test"); assert_eq!(value.serialize(StringUnserializer), Ok(value)); - let value = InputFile::Url(String::from("url")); + let value = InputFile::Url(reqwest::Url::parse("http://example.com").unwrap()); assert_eq!(value.serialize(InputFileUnserializer::NotMem), Ok(value)); let value = InputFile::FileId(String::from("file_id")); diff --git a/src/serde_multipart/unserializers/input_file.rs b/src/serde_multipart/unserializers/input_file.rs index bcdd11ff..efc6c194 100644 --- a/src/serde_multipart/unserializers/input_file.rs +++ b/src/serde_multipart/unserializers/input_file.rs @@ -62,7 +62,9 @@ impl Serializer for InputFileUnserializer { // TODO match variant { "File" => Ok(InputFile::File(value.serialize(StringUnserializer)?.into())), - "Url" => Ok(InputFile::Url(value.serialize(StringUnserializer)?)), + "Url" => Ok(InputFile::Url( + reqwest::Url::parse(&value.serialize(StringUnserializer)?).unwrap(), + )), "FileId" => Ok(InputFile::FileId(value.serialize(StringUnserializer)?)), name => Err(UnserializerError::UnexpectedVariant { name, diff --git a/src/types.rs b/src/types.rs index ee88a831..1167cc22 100644 --- a/src/types.rs +++ b/src/types.rs @@ -221,8 +221,8 @@ mod non_telegram_types { } pub(crate) mod serde_opt_date_from_unix_timestamp { - use chrono::{DateTime, Utc}; - use serde::{Serialize, Serializer}; + use chrono::{DateTime, NaiveDateTime, Utc}; + use serde::{Deserialize, Deserializer, Serialize, Serializer}; pub(crate) fn serialize( this: &Option>, @@ -234,14 +234,42 @@ pub(crate) mod serde_opt_date_from_unix_timestamp { this.map(|dt| dt.timestamp()).serialize(serializer) } - // pub(crate) fn deserialize<'de, D>(deserializer: D) -> - // Result>, D::Error> where - // D: Deserializer<'de>, - // { - // Ok(Option::::deserialize(deserializer)? - // .map(|timestamp| - // DateTime::from_utc(NaiveDateTime::from_timestamp(timestamp, 0), Utc))) - // } + pub(crate) fn deserialize<'de, D>(deserializer: D) -> Result>, D::Error> + where + D: Deserializer<'de>, + { + Ok(Option::::deserialize(deserializer)? + .map(|timestamp| DateTime::from_utc(NaiveDateTime::from_timestamp(timestamp, 0), Utc))) + } + + pub(crate) fn none() -> Option { + None + } + + #[test] + fn test() { + #[derive(Serialize, Deserialize)] + struct Struct { + #[serde(with = "crate::types::serde_opt_date_from_unix_timestamp")] + #[serde(default = "crate::types::serde_opt_date_from_unix_timestamp::none")] + date: Option>, + } + + { + let json = r#"{"date":1}"#; + let expected = DateTime::from_utc(NaiveDateTime::from_timestamp(1, 0), Utc); + + let Struct { date } = serde_json::from_str(json).unwrap(); + assert_eq!(date, Some(expected)); + } + + { + let json = r#"{}"#; + + let Struct { date } = serde_json::from_str(json).unwrap(); + assert_eq!(date, None); + } + } } pub(crate) mod serde_date_from_unix_timestamp { diff --git a/src/types/chat_invite_link.rs b/src/types/chat_invite_link.rs index 4488dfbc..2638adad 100644 --- a/src/types/chat_invite_link.rs +++ b/src/types/chat_invite_link.rs @@ -1,3 +1,4 @@ +use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; use crate::types::User; @@ -13,9 +14,11 @@ pub struct ChatInviteLink { pub is_primary: bool, /// `true`, if the link is revoked pub is_revoked: bool, - /// Point in time (Unix timestamp) when the link will expire or has been + /// Point in time when the link will expire or has been /// expired - pub expire_date: Option, + #[serde(with = "crate::types::serde_opt_date_from_unix_timestamp")] + #[serde(default = "crate::types::serde_opt_date_from_unix_timestamp::none")] + pub expire_date: Option>, /// Maximum number of users that can be members of the chat simultaneously /// after joining the chat via this invite link; 1-99999 pub member_limit: Option, diff --git a/src/types/chat_member_updated.rs b/src/types/chat_member_updated.rs index 7f4b3a95..86094d2e 100644 --- a/src/types/chat_member_updated.rs +++ b/src/types/chat_member_updated.rs @@ -1,3 +1,4 @@ +use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; use crate::types::{Chat, ChatInviteLink, ChatMember, User}; @@ -8,8 +9,9 @@ pub struct ChatMemberUpdated { pub chat: Chat, /// Performer of the action, which resulted in the change pub from: User, - /// Date the change was done in Unix time - pub date: i64, + /// Date the change was done + #[serde(with = "crate::types::serde_date_from_unix_timestamp")] + pub date: DateTime, /// Previous information about the chat member pub old_chat_member: ChatMember, /// New information about the chat member diff --git a/src/types/inline_keyboard_button.rs b/src/types/inline_keyboard_button.rs index 86a5af04..9da2182c 100644 --- a/src/types/inline_keyboard_button.rs +++ b/src/types/inline_keyboard_button.rs @@ -42,7 +42,7 @@ impl InlineKeyboardButton { #[serde(rename_all = "snake_case")] pub enum InlineKeyboardButtonKind { /// HTTP or tg:// url to be opened when button is pressed. - Url(String), + Url(reqwest::Url), /// An HTTP URL used to automatically authorize the user. Can be used as a /// replacement for the [Telegram Login Widget](). @@ -104,10 +104,11 @@ pub enum InlineKeyboardButtonKind { /// ``` /// use teloxide_core::types::InlineKeyboardButton; /// -/// let url_button = InlineKeyboardButton::url("Text".to_string(), "http://url.com".to_string()); +/// let url = url::Url::parse("https://example.com").unwrap(); +/// let url_button = InlineKeyboardButton::url("Text".to_string(), url); /// ``` impl InlineKeyboardButton { - pub fn url(text: String, url: String) -> InlineKeyboardButton { + pub fn url(text: String, url: reqwest::Url) -> InlineKeyboardButton { InlineKeyboardButton { text, kind: InlineKeyboardButtonKind::Url(url), diff --git a/src/types/inline_keyboard_markup.rs b/src/types/inline_keyboard_markup.rs index 10828a80..21365f4f 100644 --- a/src/types/inline_keyboard_markup.rs +++ b/src/types/inline_keyboard_markup.rs @@ -26,7 +26,8 @@ pub struct InlineKeyboardMarkup { /// ``` /// use teloxide_core::types::{InlineKeyboardButton, InlineKeyboardMarkup}; /// -/// let url_button = InlineKeyboardButton::url("text".to_string(), "http://url.com".to_string()); +/// let url = url::Url::parse("https://example.com").unwrap(); +/// let url_button = InlineKeyboardButton::url("text".to_string(), url); /// let keyboard = InlineKeyboardMarkup::default().append_row(vec![url_button]); /// ``` impl InlineKeyboardMarkup { @@ -78,10 +79,14 @@ impl InlineKeyboardMarkup { mod tests { use super::*; + fn url(n: u32) -> reqwest::Url { + reqwest::Url::parse(&format!("https://example.com/{n}", n = n)).unwrap() + } + #[test] fn append_row() { - let button1 = InlineKeyboardButton::url("text 1".to_string(), "url 1".to_string()); - let button2 = InlineKeyboardButton::url("text 2".to_string(), "url 2".to_string()); + let button1 = InlineKeyboardButton::url("text 1".to_string(), url(1)); + let button2 = InlineKeyboardButton::url("text 2".to_string(), url(2)); let markup = InlineKeyboardMarkup::default().append_row(vec![button1.clone(), button2.clone()]); @@ -95,8 +100,8 @@ mod tests { #[test] fn append_to_row_existent_row() { - let button1 = InlineKeyboardButton::url("text 1".to_string(), "url 1".to_string()); - let button2 = InlineKeyboardButton::url("text 2".to_string(), "url 2".to_string()); + let button1 = InlineKeyboardButton::url("text 1".to_string(), url(1)); + let button2 = InlineKeyboardButton::url("text 2".to_string(), url(2)); let markup = InlineKeyboardMarkup::default() .append_row(vec![button1.clone()]) @@ -111,8 +116,8 @@ mod tests { #[test] fn append_to_row_nonexistent_row() { - let button1 = InlineKeyboardButton::url("text 1".to_string(), "url 1".to_string()); - let button2 = InlineKeyboardButton::url("text 2".to_string(), "url 2".to_string()); + let button1 = InlineKeyboardButton::url("text 1".to_string(), url(1)); + let button2 = InlineKeyboardButton::url("text 2".to_string(), url(2)); let markup = InlineKeyboardMarkup::default() .append_row(vec![button1.clone()]) diff --git a/src/types/inline_query_result_article.rs b/src/types/inline_query_result_article.rs index a4445d73..f25dd1d9 100644 --- a/src/types/inline_query_result_article.rs +++ b/src/types/inline_query_result_article.rs @@ -21,7 +21,7 @@ pub struct InlineQueryResultArticle { pub reply_markup: Option, /// URL of the result. - pub url: Option, + pub url: Option, /// Pass `true`, if you don't want the URL to be shown in the /// message. @@ -31,7 +31,7 @@ pub struct InlineQueryResultArticle { pub description: Option, /// Url of the thumbnail for the result. - pub thumb_url: Option, + pub thumb_url: Option, /// Thumbnail width. pub thumb_width: Option, @@ -86,11 +86,8 @@ impl InlineQueryResultArticle { self } - pub fn url(mut self, val: S) -> Self - where - S: Into, - { - self.url = Some(val.into()); + pub fn url(mut self, val: reqwest::Url) -> Self { + self.url = Some(val); self } @@ -107,11 +104,8 @@ impl InlineQueryResultArticle { self } - pub fn thumb_url(mut self, val: S) -> Self - where - S: Into, - { - self.thumb_url = Some(val.into()); + pub fn thumb_url(mut self, val: reqwest::Url) -> Self { + self.thumb_url = Some(val); self } diff --git a/src/types/inline_query_result_audio.rs b/src/types/inline_query_result_audio.rs index a1712079..9e2fbd99 100644 --- a/src/types/inline_query_result_audio.rs +++ b/src/types/inline_query_result_audio.rs @@ -16,7 +16,7 @@ pub struct InlineQueryResultAudio { pub id: String, /// A valid URL for the audio file. - pub audio_url: String, + pub audio_url: reqwest::Url, /// Title. pub title: String, @@ -52,15 +52,14 @@ pub struct InlineQueryResultAudio { } impl InlineQueryResultAudio { - pub fn new(id: S1, audio_url: S2, title: S3) -> Self + pub fn new(id: S1, audio_url: reqwest::Url, title: S2) -> Self where S1: Into, S2: Into, - S3: Into, { Self { id: id.into(), - audio_url: audio_url.into(), + audio_url, title: title.into(), caption: None, parse_mode: None, @@ -80,11 +79,8 @@ impl InlineQueryResultAudio { self } - pub fn audio_url(mut self, val: S) -> Self - where - S: Into, - { - self.audio_url = val.into(); + pub fn audio_url(mut self, val: reqwest::Url) -> Self { + self.audio_url = val; self } diff --git a/src/types/inline_query_result_cached_document.rs b/src/types/inline_query_result_cached_document.rs index 5b630f66..79de0b16 100644 --- a/src/types/inline_query_result_cached_document.rs +++ b/src/types/inline_query_result_cached_document.rs @@ -108,7 +108,7 @@ impl InlineQueryResultCachedDocument { self } - pub fn parse_mode(mut self, val: ParseMode) -> Self { + pub fn parse_mode(mut self, val: ParseMode) -> Self { self.parse_mode = Some(val); self } diff --git a/src/types/inline_query_result_cached_mpeg4_gif.rs b/src/types/inline_query_result_cached_mpeg4_gif.rs index 2714ac7e..05ccb521 100644 --- a/src/types/inline_query_result_cached_mpeg4_gif.rs +++ b/src/types/inline_query_result_cached_mpeg4_gif.rs @@ -88,7 +88,7 @@ impl InlineQueryResultCachedMpeg4Gif { self } - pub fn parse_mode(mut self, val: ParseMode) -> Self { + pub fn parse_mode(mut self, val: ParseMode) -> Self { self.parse_mode = Some(val); self } diff --git a/src/types/inline_query_result_cached_photo.rs b/src/types/inline_query_result_cached_photo.rs index 2be3d4cf..2a9c2bad 100644 --- a/src/types/inline_query_result_cached_photo.rs +++ b/src/types/inline_query_result_cached_photo.rs @@ -107,7 +107,7 @@ impl InlineQueryResultCachedPhoto { self } - pub fn parse_mode(mut self, val: ParseMode) -> Self { + pub fn parse_mode(mut self, val: ParseMode) -> Self { self.parse_mode = Some(val); self } diff --git a/src/types/inline_query_result_cached_video.rs b/src/types/inline_query_result_cached_video.rs index cb9ab93d..50bad2c0 100644 --- a/src/types/inline_query_result_cached_video.rs +++ b/src/types/inline_query_result_cached_video.rs @@ -108,7 +108,7 @@ impl InlineQueryResultCachedVideo { self } - pub fn parse_mode(mut self, val: ParseMode) -> Self { + pub fn parse_mode(mut self, val: ParseMode) -> Self { self.parse_mode = Some(val); self } diff --git a/src/types/inline_query_result_cached_voice.rs b/src/types/inline_query_result_cached_voice.rs index 0567b897..50733059 100644 --- a/src/types/inline_query_result_cached_voice.rs +++ b/src/types/inline_query_result_cached_voice.rs @@ -96,7 +96,7 @@ impl InlineQueryResultCachedVoice { self } - pub fn parse_mode(mut self, val: ParseMode) -> Self { + pub fn parse_mode(mut self, val: ParseMode) -> Self { self.parse_mode = Some(val); self } diff --git a/src/types/inline_query_result_contact.rs b/src/types/inline_query_result_contact.rs index cffb96f0..8b41f239 100644 --- a/src/types/inline_query_result_contact.rs +++ b/src/types/inline_query_result_contact.rs @@ -39,7 +39,7 @@ pub struct InlineQueryResultContact { pub input_message_content: Option, /// Url of the thumbnail for the result. - pub thumb_url: Option, + pub thumb_url: Option, /// Thumbnail width. pub thumb_width: Option, @@ -119,11 +119,8 @@ impl InlineQueryResultContact { self } - pub fn thumb_url(mut self, val: S) -> Self - where - S: Into, - { - self.thumb_url = Some(val.into()); + pub fn thumb_url(mut self, val: reqwest::Url) -> Self { + self.thumb_url = Some(val); self } diff --git a/src/types/inline_query_result_document.rs b/src/types/inline_query_result_document.rs index e44342ad..959469b1 100644 --- a/src/types/inline_query_result_document.rs +++ b/src/types/inline_query_result_document.rs @@ -36,7 +36,7 @@ pub struct InlineQueryResultDocument { pub caption_entities: Option>, /// A valid URL for the file. - pub document_url: String, + pub document_url: reqwest::Url, /// Mime type of the content of the file, either `application/pdf` or /// `application/zip`. @@ -53,7 +53,7 @@ pub struct InlineQueryResultDocument { pub input_message_content: Option, /// URL of the thumbnail (jpeg only) for the file. - pub thumb_url: Option, + pub thumb_url: Option, /// Thumbnail width. pub thumb_width: Option, @@ -87,7 +87,7 @@ impl InlineQueryResultDocument { self } - pub fn parse_mode(mut self, val: ParseMode) -> Self { + pub fn parse_mode(mut self, val: ParseMode) -> Self { self.parse_mode = Some(val); self } @@ -100,11 +100,8 @@ impl InlineQueryResultDocument { self } - pub fn document_url(mut self, val: S) -> Self - where - S: Into, - { - self.document_url = val.into(); + pub fn document_url(mut self, val: reqwest::Url) -> Self { + self.document_url = val; self } @@ -131,11 +128,8 @@ impl InlineQueryResultDocument { self } - pub fn thumb_url(mut self, val: S) -> Self - where - S: Into, - { - self.thumb_url = Some(val.into()); + pub fn thumb_url(mut self, val: reqwest::Url) -> Self { + self.thumb_url = Some(val); self } diff --git a/src/types/inline_query_result_gif.rs b/src/types/inline_query_result_gif.rs index fdfad686..0154bbf2 100644 --- a/src/types/inline_query_result_gif.rs +++ b/src/types/inline_query_result_gif.rs @@ -16,7 +16,7 @@ pub struct InlineQueryResultGif { pub id: String, /// A valid URL for the GIF file. File size must not exceed 1MB. - pub gif_url: String, + pub gif_url: reqwest::Url, /// Width of the GIF. pub gif_width: Option, @@ -28,7 +28,7 @@ pub struct InlineQueryResultGif { pub gif_duration: Option, /// URL of the static thumbnail for the result (jpeg or gif). - pub thumb_url: String, + pub thumb_url: reqwest::Url, /// Title for the result. pub title: Option, @@ -58,19 +58,17 @@ pub struct InlineQueryResultGif { } impl InlineQueryResultGif { - pub fn new(id: S1, gif_url: S2, thumb_url: S3) -> Self + pub fn new(id: S, gif_url: reqwest::Url, thumb_url: reqwest::Url) -> Self where - S1: Into, - S2: Into, - S3: Into, + S: Into, { Self { id: id.into(), - gif_url: gif_url.into(), + gif_url, gif_width: None, gif_height: None, gif_duration: None, - thumb_url: thumb_url.into(), + thumb_url, title: None, caption: None, parse_mode: None, @@ -88,11 +86,8 @@ impl InlineQueryResultGif { self } - pub fn gif_url(mut self, val: S) -> Self - where - S: Into, - { - self.gif_url = val.into(); + pub fn gif_url(mut self, val: reqwest::Url) -> Self { + self.gif_url = val; self } @@ -111,11 +106,8 @@ impl InlineQueryResultGif { self } - pub fn thumb_url(mut self, val: S) -> Self - where - S: Into, - { - self.thumb_url = val.into(); + pub fn thumb_url(mut self, val: reqwest::Url) -> Self { + self.thumb_url = val; self } diff --git a/src/types/inline_query_result_location.rs b/src/types/inline_query_result_location.rs index 733d84f4..25f64365 100644 --- a/src/types/inline_query_result_location.rs +++ b/src/types/inline_query_result_location.rs @@ -49,7 +49,7 @@ pub struct InlineQueryResultLocation { pub input_message_content: Option, /// Url of the thumbnail for the result. - pub thumb_url: Option, + pub thumb_url: Option, /// Thumbnail width. pub thumb_width: Option, @@ -137,11 +137,8 @@ impl InlineQueryResultLocation { self } - pub fn thumb_url(mut self, val: S) -> Self - where - S: Into, - { - self.thumb_url = Some(val.into()); + pub fn thumb_url(mut self, val: reqwest::Url) -> Self { + self.thumb_url = Some(val); self } diff --git a/src/types/inline_query_result_mpeg4_gif.rs b/src/types/inline_query_result_mpeg4_gif.rs index b8284243..2b700fac 100644 --- a/src/types/inline_query_result_mpeg4_gif.rs +++ b/src/types/inline_query_result_mpeg4_gif.rs @@ -17,7 +17,7 @@ pub struct InlineQueryResultMpeg4Gif { pub id: String, /// A valid URL for the MP4 file. File size must not exceed 1MB. - pub mpeg4_url: String, + pub mpeg4_url: reqwest::Url, /// Video width. pub mpeg4_width: Option, @@ -29,7 +29,7 @@ pub struct InlineQueryResultMpeg4Gif { pub mpeg4_duration: Option, /// URL of the static thumbnail (jpeg or gif) for the result. - pub thumb_url: String, + pub thumb_url: reqwest::Url, /// Title for the result. pub title: Option, @@ -59,16 +59,14 @@ pub struct InlineQueryResultMpeg4Gif { } impl InlineQueryResultMpeg4Gif { - pub fn new(id: S1, mpeg4_url: S2, thumb_url: S3) -> Self + pub fn new(id: S, mpeg4_url: reqwest::Url, thumb_url: reqwest::Url) -> Self where - S1: Into, - S2: Into, - S3: Into, + S: Into, { Self { id: id.into(), - mpeg4_url: mpeg4_url.into(), - thumb_url: thumb_url.into(), + mpeg4_url, + thumb_url, mpeg4_width: None, mpeg4_height: None, mpeg4_duration: None, @@ -89,11 +87,8 @@ impl InlineQueryResultMpeg4Gif { self } - pub fn mpeg4_url(mut self, val: S) -> Self - where - S: Into, - { - self.mpeg4_url = val.into(); + pub fn mpeg4_url(mut self, val: reqwest::Url) -> Self { + self.mpeg4_url = val; self } @@ -112,11 +107,8 @@ impl InlineQueryResultMpeg4Gif { self } - pub fn thumb_url(mut self, val: S) -> Self - where - S: Into, - { - self.thumb_url = val.into(); + pub fn thumb_url(mut self, val: reqwest::Url) -> Self { + self.thumb_url = val; self } @@ -136,7 +128,7 @@ impl InlineQueryResultMpeg4Gif { self } - pub fn parse_mode(mut self, val: ParseMode) -> Self { + pub fn parse_mode(mut self, val: ParseMode) -> Self { self.parse_mode = Some(val); self } diff --git a/src/types/inline_query_result_photo.rs b/src/types/inline_query_result_photo.rs index 51402e63..69cdf7ab 100644 --- a/src/types/inline_query_result_photo.rs +++ b/src/types/inline_query_result_photo.rs @@ -17,10 +17,10 @@ pub struct InlineQueryResultPhoto { /// A valid URL of the photo. Photo must be in **jpeg** format. Photo size /// must not exceed 5MB. - pub photo_url: String, + pub photo_url: reqwest::Url, /// URL of the thumbnail for the photo. - pub thumb_url: String, + pub thumb_url: reqwest::Url, /// Width of the photo. pub photo_width: Option, @@ -59,16 +59,14 @@ pub struct InlineQueryResultPhoto { } impl InlineQueryResultPhoto { - pub fn new(id: S1, photo_url: S2, thumb_url: S3) -> Self + pub fn new(id: S, photo_url: reqwest::Url, thumb_url: reqwest::Url) -> Self where - S1: Into, - S2: Into, - S3: Into, + S: Into, { Self { id: id.into(), - photo_url: photo_url.into(), - thumb_url: thumb_url.into(), + photo_url, + thumb_url, photo_width: None, photo_height: None, title: None, @@ -89,28 +87,22 @@ impl InlineQueryResultPhoto { self } - pub fn photo_url(mut self, val: S) -> Self - where - S: Into, - { - self.photo_url = val.into(); + pub fn photo_url(mut self, val: reqwest::Url) -> Self { + self.photo_url = val; self } - pub fn thumb_url(mut self, val: S) -> Self - where - S: Into, - { - self.thumb_url = val.into(); + pub fn thumb_url(mut self, val: reqwest::Url) -> Self { + self.thumb_url = val; self } - pub fn photo_width(mut self, val: i32) -> Self { + pub fn photo_width(mut self, val: i32) -> Self { self.photo_width = Some(val); self } - pub fn photo_height(mut self, val: i32) -> Self { + pub fn photo_height(mut self, val: i32) -> Self { self.photo_height = Some(val); self } @@ -139,7 +131,7 @@ impl InlineQueryResultPhoto { self } - pub fn parse_mode(mut self, val: ParseMode) -> Self { + pub fn parse_mode(mut self, val: ParseMode) -> Self { self.parse_mode = Some(val); self } diff --git a/src/types/inline_query_result_venue.rs b/src/types/inline_query_result_venue.rs index ac013ad0..6b6820bf 100644 --- a/src/types/inline_query_result_venue.rs +++ b/src/types/inline_query_result_venue.rs @@ -52,7 +52,7 @@ pub struct InlineQueryResultVenue { pub input_message_content: Option, /// Url of the thumbnail for the result. - pub thumb_url: Option, + pub thumb_url: Option, /// Thumbnail width. pub thumb_width: Option, @@ -162,11 +162,8 @@ impl InlineQueryResultVenue { self } - pub fn thumb_url(mut self, val: S) -> Self - where - S: Into, - { - self.thumb_url = Some(val.into()); + pub fn thumb_url(mut self, val: reqwest::Url) -> Self { + self.thumb_url = Some(val); self } diff --git a/src/types/inline_query_result_video.rs b/src/types/inline_query_result_video.rs index aa887cae..51e53a3b 100644 --- a/src/types/inline_query_result_video.rs +++ b/src/types/inline_query_result_video.rs @@ -18,14 +18,14 @@ pub struct InlineQueryResultVideo { pub id: String, /// A valid URL for the embedded video player or video file. - pub video_url: String, + pub video_url: reqwest::Url, /// Mime type of the content of video url, `text/html` or `video/mp4`. #[serde(with = "crate::types::non_telegram_types::mime::deser")] pub mime_type: Mime, /// URL of the thumbnail (jpeg only) for the video. - pub thumb_url: String, + pub thumb_url: reqwest::Url, /// Title for the result. pub title: String, @@ -72,24 +72,22 @@ pub struct InlineQueryResultVideo { } impl InlineQueryResultVideo { - pub fn new( + pub fn new( id: S1, - video_url: S2, + video_url: reqwest::Url, mime_type: Mime, - thumb_url: S3, - title: S4, + thumb_url: reqwest::Url, + title: S2, ) -> Self where S1: Into, S2: Into, - S3: Into, - S4: Into, { Self { id: id.into(), - video_url: video_url.into(), + video_url, mime_type, - thumb_url: thumb_url.into(), + thumb_url, title: title.into(), caption: None, parse_mode: None, @@ -111,11 +109,8 @@ impl InlineQueryResultVideo { self } - pub fn video_url(mut self, val: S) -> Self - where - S: Into, - { - self.video_url = val.into(); + pub fn video_url(mut self, val: reqwest::Url) -> Self { + self.video_url = val; self } @@ -124,11 +119,8 @@ impl InlineQueryResultVideo { self } - pub fn thumb_url(mut self, val: S) -> Self - where - S: Into, - { - self.thumb_url = val.into(); + pub fn thumb_url(mut self, val: reqwest::Url) -> Self { + self.thumb_url = val; self } diff --git a/src/types/inline_query_result_voice.rs b/src/types/inline_query_result_voice.rs index c2765a61..7b9cc1bc 100644 --- a/src/types/inline_query_result_voice.rs +++ b/src/types/inline_query_result_voice.rs @@ -17,7 +17,7 @@ pub struct InlineQueryResultVoice { pub id: String, /// A valid URL for the voice recording. - pub voice_url: String, + pub voice_url: reqwest::Url, /// Recording title. pub title: String, @@ -50,15 +50,14 @@ pub struct InlineQueryResultVoice { } impl InlineQueryResultVoice { - pub fn new(id: S1, voice_url: S2, title: S3) -> Self + pub fn new(id: S1, voice_url: reqwest::Url, title: S2) -> Self where S1: Into, S2: Into, - S3: Into, { Self { id: id.into(), - voice_url: voice_url.into(), + voice_url, title: title.into(), caption: None, parse_mode: None, @@ -77,11 +76,8 @@ impl InlineQueryResultVoice { self } - pub fn voice_url(mut self, val: S) -> Self - where - S: Into, - { - self.voice_url = val.into(); + pub fn voice_url(mut self, val: reqwest::Url) -> Self { + self.voice_url = val; self } diff --git a/src/types/input_file.rs b/src/types/input_file.rs index 4ace6e85..6565b254 100644 --- a/src/types/input_file.rs +++ b/src/types/input_file.rs @@ -1,3 +1,4 @@ +use reqwest::Url; use serde::{Deserialize, Serialize}; use std::{borrow::Cow, path::PathBuf}; @@ -13,7 +14,7 @@ pub enum InputFile { file_name: String, data: Cow<'static, [u8]>, }, - Url(String), + Url(Url), FileId(String), } @@ -36,11 +37,8 @@ impl InputFile { } } - pub fn url(url: T) -> Self - where - T: Into, - { - Self::Url(url.into()) + pub fn url(url: Url) -> Self { + Self::Url(url) } pub fn file_id(file_id: T) -> Self @@ -57,7 +55,7 @@ impl InputFile { } } - pub fn as_url(&self) -> Option<&String> { + pub fn as_url(&self) -> Option<&Url> { match self { Self::Url(url) => Some(url), _ => None, @@ -118,7 +116,8 @@ impl InputFile { Ok(Part::stream(Body::wrap_stream(file)).file_name(file_name)) } Self::Memory { file_name, data } => Ok(Part::bytes(data).file_name(file_name)), - Self::Url(s) | Self::FileId(s) => Ok(Part::text(s)), + Self::Url(s) => Ok(Part::text(String::from(s))), + Self::FileId(s) => Ok(Part::text(s)), } } } diff --git a/src/types/login_url.rs b/src/types/login_url.rs index e0e647eb..ba2c0d78 100644 --- a/src/types/login_url.rs +++ b/src/types/login_url.rs @@ -15,18 +15,39 @@ use serde::{Deserialize, Serialize}; #[serde_with_macros::skip_serializing_none] #[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] pub struct LoginUrl { - pub url: String, + /// An HTTP URL to be opened with user authorization data added to the query + /// string when the button is pressed. If the user refuses to provide + /// authorization data, the original URL without information about the user + /// will be opened. The data added is the same as described in [Receiving + /// authorization data]. + /// + /// [Receiving authorization data]: https://core.telegram.org/widgets/login#receiving-authorization-data + /// + /// NOTE: You must always check the hash of the received data to verify the + /// authentication and the integrity of the data as described in [Checking + /// authorization]. + /// + /// [Checking authorization]: https://core.telegram.org/widgets/login#checking-authorization + pub url: reqwest::Url, + /// New text of the button in forwarded messages. pub forward_text: Option, + /// Username of a bot, which will be used for user authorization. See + /// [Setting up a bot] for more details. If not specified, the current bot's + /// username will be assumed. The url's domain must be the same as the + /// domain linked with the bot. See [Linking your domain to the bot] for + /// more details. + /// + /// [Setting up a bot]: https://core.telegram.org/widgets/login#setting-up-a-bot + /// [Linking your domain to the bot]: https://core.telegram.org/widgets/login#linking-your-domain-to-the-bot pub bot_username: Option, + /// Pass `true` to request the permission for your bot to send messages to + /// the user. pub request_write_access: Option, } impl LoginUrl { - pub fn url(mut self, val: S) -> Self - where - S: Into, - { - self.url = val.into(); + pub fn url(mut self, val: reqwest::Url) -> Self { + self.url = val; self } @@ -46,7 +67,7 @@ impl LoginUrl { self } - pub fn request_write_access(mut self, val: bool) -> Self { + pub fn request_write_access(mut self, val: bool) -> Self { self.request_write_access = Some(val); self } diff --git a/src/types/message.rs b/src/types/message.rs index 0cb29a5f..fdb626af 100644 --- a/src/types/message.rs +++ b/src/types/message.rs @@ -1,5 +1,6 @@ #![allow(clippy::large_enum_variant)] +use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; use crate::types::{ @@ -19,7 +20,8 @@ pub struct Message { pub id: i32, /// Date the message was sent in Unix time. - pub date: i32, + #[serde(with = "crate::types::serde_date_from_unix_timestamp")] + pub date: DateTime, /// Conversation the message belongs to. pub chat: Chat, @@ -78,7 +80,9 @@ pub struct MessageCommon { pub forward_kind: ForwardKind, /// Date the message was last edited in Unix time. - pub edit_date: Option, + #[serde(with = "crate::types::serde_opt_date_from_unix_timestamp")] + #[serde(default = "crate::types::serde_opt_date_from_unix_timestamp::none")] + pub edit_date: Option>, #[serde(flatten)] pub media_kind: MediaKind, @@ -234,7 +238,8 @@ pub enum ForwardKind { #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct ForwardChannel { #[serde(rename = "forward_date")] - pub date: i32, + #[serde(with = "crate::types::serde_date_from_unix_timestamp")] + pub date: DateTime, #[serde(rename = "forward_from_chat")] pub chat: Chat, @@ -249,7 +254,8 @@ pub struct ForwardChannel { #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct ForwardNonChannel { #[serde(rename = "forward_date")] - pub date: i32, + #[serde(with = "crate::types::serde_date_from_unix_timestamp")] + pub date: DateTime, #[serde(flatten)] pub from: ForwardedFrom, @@ -480,6 +486,7 @@ pub struct MessageVoiceChatParticipantsInvited { } mod getters { + use chrono::{DateTime, Utc}; use std::ops::Deref; use crate::types::{ @@ -570,7 +577,7 @@ mod getters { } } - pub fn forward_date(&self) -> Option<&i32> { + pub fn forward_date(&self) -> Option<&DateTime> { match &self.kind { Common(MessageCommon { forward_kind: ForwardKind::Channel(ForwardChannel { date, .. }), @@ -597,7 +604,7 @@ mod getters { } } - pub fn edit_date(&self) -> Option<&i32> { + pub fn edit_date(&self) -> Option<&DateTime> { match &self.kind { Common(MessageCommon { edit_date, .. }) => edit_date.as_ref(), _ => None, diff --git a/src/types/message_entity.rs b/src/types/message_entity.rs index a74fe6b1..1f4873c8 100644 --- a/src/types/message_entity.rs +++ b/src/types/message_entity.rs @@ -59,7 +59,7 @@ pub enum MessageEntityKind { Italic, Code, Pre { language: Option }, - TextLink { url: String }, + TextLink { url: reqwest::Url }, TextMention { user: User }, Underline, Strikethrough, @@ -76,13 +76,13 @@ mod tests { assert_eq!( MessageEntity { kind: MessageEntityKind::TextLink { - url: "ya.ru".into() + url: reqwest::Url::parse("https://example.com").unwrap(), }, offset: 1, length: 2, }, from_str::( - r#"{"type":"text_link","url":"ya.ru","offset":1,"length":2}"# + r#"{"type":"text_link","url":"https://example.com","offset":1,"length":2}"# ) .unwrap() ); @@ -100,10 +100,8 @@ mod tests { offset: 1, length: 2, }, - from_str::( - r#"{"type":"pre","url":"ya.ru","offset":1,"length":2,"language":"rust"}"# - ) - .unwrap() + from_str::(r#"{"type":"pre","offset":1,"length":2,"language":"rust"}"#) + .unwrap() ); } } diff --git a/src/types/passport_file.rs b/src/types/passport_file.rs index 18f1a5d4..c3c057cd 100644 --- a/src/types/passport_file.rs +++ b/src/types/passport_file.rs @@ -1,3 +1,4 @@ +use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; /// This object represents a file uploaded to Telegram Passport. @@ -19,6 +20,7 @@ pub struct PassportFile { /// File size. pub file_size: u64, - /// Unix time when the file was uploaded. - pub file_date: u64, + /// Time when the file was uploaded. + #[serde(with = "crate::types::serde_date_from_unix_timestamp")] + pub file_date: DateTime, } diff --git a/src/types/poll.rs b/src/types/poll.rs index 23bb9409..caf6af5e 100644 --- a/src/types/poll.rs +++ b/src/types/poll.rs @@ -1,4 +1,6 @@ use crate::types::{MessageEntity, PollType}; + +use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; /// This object contains information about a poll. @@ -48,9 +50,10 @@ pub struct Poll { /// Amount of time in seconds the poll will be active after creation. pub open_period: Option, - /// Point in time (Unix timestamp) when the poll will be automatically - /// closed. - pub close_date: Option, + /// Point in time when the poll will be automatically closed. + #[serde(with = "crate::types::serde_opt_date_from_unix_timestamp")] + #[serde(default = "crate::types::serde_opt_date_from_unix_timestamp::none")] + pub close_date: Option>, } /// This object contains information about one answer option in a poll. diff --git a/src/types/update.rs b/src/types/update.rs index 5a1c1355..4767fcb0 100644 --- a/src/types/update.rs +++ b/src/types/update.rs @@ -144,9 +144,14 @@ mod test { MessageCommon, MessageKind, Update, UpdateKind, User, }; + use chrono::{DateTime, NaiveDateTime, Utc}; + // TODO: more tests for deserialization #[test] fn message() { + let timestamp = 1_569_518_342; + let date = DateTime::from_utc(NaiveDateTime::from_timestamp(timestamp, 0), Utc); + let json = r#"{ "update_id":892252934, "message":{ @@ -174,7 +179,7 @@ mod test { kind: UpdateKind::Message(Message { via_bot: None, id: 6557, - date: 1_569_518_342, + date, chat: Chat { id: 218_485_655, kind: ChatKind::Private(ChatPrivate { diff --git a/src/types/voice_chat_scheduled.rs b/src/types/voice_chat_scheduled.rs index c33a5ad8..06ca85cf 100644 --- a/src/types/voice_chat_scheduled.rs +++ b/src/types/voice_chat_scheduled.rs @@ -1,10 +1,12 @@ +use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; /// This object represents a service message about a voice chat scheduled in the /// chat. #[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] pub struct VoiceChatScheduled { - /// Point in time (Unix timestamp) when the voice chat is supposed to be - /// started by a chat administrator. - pub start_date: u64, + /// Point in time when the voice chat is supposed to be started by a chat + /// administrator. + #[serde(with = "crate::types::serde_date_from_unix_timestamp")] + pub start_date: DateTime, } diff --git a/src/types/webhook_info.rs b/src/types/webhook_info.rs index d725264a..6a96beb1 100644 --- a/src/types/webhook_info.rs +++ b/src/types/webhook_info.rs @@ -1,3 +1,4 @@ +use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; /// Contains information about the current status of a webhook. @@ -6,8 +7,8 @@ use serde::{Deserialize, Serialize}; #[serde_with_macros::skip_serializing_none] #[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] pub struct WebhookInfo { - /// Webhook URL, may be empty if webhook is not set up. - pub url: String, + /// Webhook URL, `None` if webhook is not set up. + pub url: Option, /// `true`, if a custom certificate was provided for webhook certificate /// checks. @@ -19,9 +20,11 @@ pub struct WebhookInfo { /// Currently used webhook IP address. pub ip_address: Option, - /// Unix time for the most recent error that happened when trying to + /// Time of the most recent error that happened when trying to /// deliver an update via webhook. - pub last_error_date: Option, + #[serde(with = "crate::types::serde_opt_date_from_unix_timestamp")] + #[serde(default = "crate::types::serde_opt_date_from_unix_timestamp::none")] + pub last_error_date: Option>, /// Error message in human-readable format for the most recent error that /// happened when trying to deliver an update via webhook.