Merge pull request #115 from teloxide/make_urls_urlier

Use `url::Url` for urls, use `chrono::DateTime<Utc>` for dates in types
This commit is contained in:
Hirrolot 2021-08-31 07:50:38 -07:00 committed by GitHub
commit 3d7ca29681
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
34 changed files with 246 additions and 215 deletions

View file

@ -8,8 +8,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [unreleased] ## [unreleased]
- Add `EditedMessageIsTooLong` error [#109][pr109] - Add `EditedMessageIsTooLong` error [#109][pr109]
- Use `url::Url` for urls, use `chrono::DateTime<Utc>` for dates in types ([#115][pr115])
[pr109]: https://github.com/teloxide/teloxide-core/pull/109 [pr109]: https://github.com/teloxide/teloxide-core/pull/109
[pr115]: https://github.com/teloxide/teloxide-core/pull/115
## 0.3.3 ## 0.3.3

View file

@ -485,7 +485,8 @@ impl Serializer for PartSerializer {
let part = Part::text(format!("attach://{}", uuid)); let part = Part::text(format!("attach://{}", uuid));
Ok((part, vec![(uuid, f)])) 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)])) 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)); value["media"] = serde_json::Value::String(format!("attach://{}", uuid));
self.files.push((uuid, f)); self.files.push((uuid, f));
} }
InputFile::FileId(s) | InputFile::Url(s) => { InputFile::FileId(s) => {
value["media"] = serde_json::Value::String(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); self.array_json_parts.push(value);
@ -673,10 +678,14 @@ impl SerializeStruct for PartSerializerStruct {
self.2.push((uuid, f)); self.2.push((uuid, f));
} }
InputFile::FileId(s) | InputFile::Url(s) => { InputFile::FileId(s) => {
SerializeStruct::serialize_field(&mut ser, key, &s)?; SerializeStruct::serialize_field(&mut ser, key, &s)?;
self.1 = get_state(ser) self.1 = get_state(ser)
} }
InputFile::Url(s) => {
SerializeStruct::serialize_field(&mut ser, key, s.as_str())?;
self.1 = get_state(ser)
}
} }
} else { } else {
SerializeStruct::serialize_field(&mut ser, key, value)?; SerializeStruct::serialize_field(&mut ser, key, value)?;

View file

@ -84,7 +84,7 @@ fn test() {
let value = String::from("test"); let value = String::from("test");
assert_eq!(value.serialize(StringUnserializer), Ok(value)); 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)); assert_eq!(value.serialize(InputFileUnserializer::NotMem), Ok(value));
let value = InputFile::FileId(String::from("file_id")); let value = InputFile::FileId(String::from("file_id"));

View file

@ -62,7 +62,9 @@ impl Serializer for InputFileUnserializer {
// TODO // TODO
match variant { match variant {
"File" => Ok(InputFile::File(value.serialize(StringUnserializer)?.into())), "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)?)), "FileId" => Ok(InputFile::FileId(value.serialize(StringUnserializer)?)),
name => Err(UnserializerError::UnexpectedVariant { name => Err(UnserializerError::UnexpectedVariant {
name, name,

View file

@ -221,8 +221,8 @@ mod non_telegram_types {
} }
pub(crate) mod serde_opt_date_from_unix_timestamp { pub(crate) mod serde_opt_date_from_unix_timestamp {
use chrono::{DateTime, Utc}; use chrono::{DateTime, NaiveDateTime, Utc};
use serde::{Serialize, Serializer}; use serde::{Deserialize, Deserializer, Serialize, Serializer};
pub(crate) fn serialize<S>( pub(crate) fn serialize<S>(
this: &Option<DateTime<Utc>>, this: &Option<DateTime<Utc>>,
@ -234,14 +234,42 @@ pub(crate) mod serde_opt_date_from_unix_timestamp {
this.map(|dt| dt.timestamp()).serialize(serializer) this.map(|dt| dt.timestamp()).serialize(serializer)
} }
// pub(crate) fn deserialize<'de, D>(deserializer: D) -> pub(crate) fn deserialize<'de, D>(deserializer: D) -> Result<Option<DateTime<Utc>>, D::Error>
// Result<Option<DateTime<Utc>>, D::Error> where where
// D: Deserializer<'de>, D: Deserializer<'de>,
// { {
// Ok(Option::<i64>::deserialize(deserializer)? Ok(Option::<i64>::deserialize(deserializer)?
// .map(|timestamp| .map(|timestamp| DateTime::from_utc(NaiveDateTime::from_timestamp(timestamp, 0), Utc)))
// DateTime::from_utc(NaiveDateTime::from_timestamp(timestamp, 0), Utc))) }
// }
pub(crate) fn none<T>() -> Option<T> {
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<DateTime<Utc>>,
}
{
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 { pub(crate) mod serde_date_from_unix_timestamp {

View file

@ -1,3 +1,4 @@
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::types::User; use crate::types::User;
@ -13,9 +14,11 @@ pub struct ChatInviteLink {
pub is_primary: bool, pub is_primary: bool,
/// `true`, if the link is revoked /// `true`, if the link is revoked
pub is_revoked: bool, 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 /// expired
pub expire_date: Option<i64>, #[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<DateTime<Utc>>,
/// Maximum number of users that can be members of the chat simultaneously /// Maximum number of users that can be members of the chat simultaneously
/// after joining the chat via this invite link; 1-99999 /// after joining the chat via this invite link; 1-99999
pub member_limit: Option<u32>, pub member_limit: Option<u32>,

View file

@ -1,3 +1,4 @@
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::types::{Chat, ChatInviteLink, ChatMember, User}; use crate::types::{Chat, ChatInviteLink, ChatMember, User};
@ -8,8 +9,9 @@ pub struct ChatMemberUpdated {
pub chat: Chat, pub chat: Chat,
/// Performer of the action, which resulted in the change /// Performer of the action, which resulted in the change
pub from: User, pub from: User,
/// Date the change was done in Unix time /// Date the change was done
pub date: i64, #[serde(with = "crate::types::serde_date_from_unix_timestamp")]
pub date: DateTime<Utc>,
/// Previous information about the chat member /// Previous information about the chat member
pub old_chat_member: ChatMember, pub old_chat_member: ChatMember,
/// New information about the chat member /// New information about the chat member

View file

@ -42,7 +42,7 @@ impl InlineKeyboardButton {
#[serde(rename_all = "snake_case")] #[serde(rename_all = "snake_case")]
pub enum InlineKeyboardButtonKind { pub enum InlineKeyboardButtonKind {
/// HTTP or tg:// url to be opened when button is pressed. /// 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 /// An HTTP URL used to automatically authorize the user. Can be used as a
/// replacement for the [Telegram Login Widget](). /// replacement for the [Telegram Login Widget]().
@ -104,10 +104,11 @@ pub enum InlineKeyboardButtonKind {
/// ``` /// ```
/// use teloxide_core::types::InlineKeyboardButton; /// 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 { impl InlineKeyboardButton {
pub fn url(text: String, url: String) -> InlineKeyboardButton { pub fn url(text: String, url: reqwest::Url) -> InlineKeyboardButton {
InlineKeyboardButton { InlineKeyboardButton {
text, text,
kind: InlineKeyboardButtonKind::Url(url), kind: InlineKeyboardButtonKind::Url(url),

View file

@ -26,7 +26,8 @@ pub struct InlineKeyboardMarkup {
/// ``` /// ```
/// use teloxide_core::types::{InlineKeyboardButton, 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]); /// let keyboard = InlineKeyboardMarkup::default().append_row(vec![url_button]);
/// ``` /// ```
impl InlineKeyboardMarkup { impl InlineKeyboardMarkup {
@ -78,10 +79,14 @@ impl InlineKeyboardMarkup {
mod tests { mod tests {
use super::*; use super::*;
fn url(n: u32) -> reqwest::Url {
reqwest::Url::parse(&format!("https://example.com/{n}", n = n)).unwrap()
}
#[test] #[test]
fn append_row() { fn append_row() {
let button1 = InlineKeyboardButton::url("text 1".to_string(), "url 1".to_string()); let button1 = InlineKeyboardButton::url("text 1".to_string(), url(1));
let button2 = InlineKeyboardButton::url("text 2".to_string(), "url 2".to_string()); let button2 = InlineKeyboardButton::url("text 2".to_string(), url(2));
let markup = let markup =
InlineKeyboardMarkup::default().append_row(vec![button1.clone(), button2.clone()]); InlineKeyboardMarkup::default().append_row(vec![button1.clone(), button2.clone()]);
@ -95,8 +100,8 @@ mod tests {
#[test] #[test]
fn append_to_row_existent_row() { fn append_to_row_existent_row() {
let button1 = InlineKeyboardButton::url("text 1".to_string(), "url 1".to_string()); let button1 = InlineKeyboardButton::url("text 1".to_string(), url(1));
let button2 = InlineKeyboardButton::url("text 2".to_string(), "url 2".to_string()); let button2 = InlineKeyboardButton::url("text 2".to_string(), url(2));
let markup = InlineKeyboardMarkup::default() let markup = InlineKeyboardMarkup::default()
.append_row(vec![button1.clone()]) .append_row(vec![button1.clone()])
@ -111,8 +116,8 @@ mod tests {
#[test] #[test]
fn append_to_row_nonexistent_row() { fn append_to_row_nonexistent_row() {
let button1 = InlineKeyboardButton::url("text 1".to_string(), "url 1".to_string()); let button1 = InlineKeyboardButton::url("text 1".to_string(), url(1));
let button2 = InlineKeyboardButton::url("text 2".to_string(), "url 2".to_string()); let button2 = InlineKeyboardButton::url("text 2".to_string(), url(2));
let markup = InlineKeyboardMarkup::default() let markup = InlineKeyboardMarkup::default()
.append_row(vec![button1.clone()]) .append_row(vec![button1.clone()])

View file

@ -21,7 +21,7 @@ pub struct InlineQueryResultArticle {
pub reply_markup: Option<InlineKeyboardMarkup>, pub reply_markup: Option<InlineKeyboardMarkup>,
/// URL of the result. /// URL of the result.
pub url: Option<String>, pub url: Option<reqwest::Url>,
/// Pass `true`, if you don't want the URL to be shown in the /// Pass `true`, if you don't want the URL to be shown in the
/// message. /// message.
@ -31,7 +31,7 @@ pub struct InlineQueryResultArticle {
pub description: Option<String>, pub description: Option<String>,
/// Url of the thumbnail for the result. /// Url of the thumbnail for the result.
pub thumb_url: Option<String>, pub thumb_url: Option<reqwest::Url>,
/// Thumbnail width. /// Thumbnail width.
pub thumb_width: Option<i32>, pub thumb_width: Option<i32>,
@ -86,11 +86,8 @@ impl InlineQueryResultArticle {
self self
} }
pub fn url<S>(mut self, val: S) -> Self pub fn url(mut self, val: reqwest::Url) -> Self {
where self.url = Some(val);
S: Into<String>,
{
self.url = Some(val.into());
self self
} }
@ -107,11 +104,8 @@ impl InlineQueryResultArticle {
self self
} }
pub fn thumb_url<S>(mut self, val: S) -> Self pub fn thumb_url<S>(mut self, val: reqwest::Url) -> Self {
where self.thumb_url = Some(val);
S: Into<String>,
{
self.thumb_url = Some(val.into());
self self
} }

View file

@ -16,7 +16,7 @@ pub struct InlineQueryResultAudio {
pub id: String, pub id: String,
/// A valid URL for the audio file. /// A valid URL for the audio file.
pub audio_url: String, pub audio_url: reqwest::Url,
/// Title. /// Title.
pub title: String, pub title: String,
@ -52,15 +52,14 @@ pub struct InlineQueryResultAudio {
} }
impl InlineQueryResultAudio { impl InlineQueryResultAudio {
pub fn new<S1, S2, S3>(id: S1, audio_url: S2, title: S3) -> Self pub fn new<S1, S2>(id: S1, audio_url: reqwest::Url, title: S2) -> Self
where where
S1: Into<String>, S1: Into<String>,
S2: Into<String>, S2: Into<String>,
S3: Into<String>,
{ {
Self { Self {
id: id.into(), id: id.into(),
audio_url: audio_url.into(), audio_url,
title: title.into(), title: title.into(),
caption: None, caption: None,
parse_mode: None, parse_mode: None,
@ -80,11 +79,8 @@ impl InlineQueryResultAudio {
self self
} }
pub fn audio_url<S>(mut self, val: S) -> Self pub fn audio_url(mut self, val: reqwest::Url) -> Self {
where self.audio_url = val;
S: Into<String>,
{
self.audio_url = val.into();
self self
} }

View file

@ -108,7 +108,7 @@ impl InlineQueryResultCachedDocument {
self self
} }
pub fn parse_mode<S>(mut self, val: ParseMode) -> Self { pub fn parse_mode(mut self, val: ParseMode) -> Self {
self.parse_mode = Some(val); self.parse_mode = Some(val);
self self
} }

View file

@ -88,7 +88,7 @@ impl InlineQueryResultCachedMpeg4Gif {
self self
} }
pub fn parse_mode<S>(mut self, val: ParseMode) -> Self { pub fn parse_mode(mut self, val: ParseMode) -> Self {
self.parse_mode = Some(val); self.parse_mode = Some(val);
self self
} }

View file

@ -107,7 +107,7 @@ impl InlineQueryResultCachedPhoto {
self self
} }
pub fn parse_mode<S>(mut self, val: ParseMode) -> Self { pub fn parse_mode(mut self, val: ParseMode) -> Self {
self.parse_mode = Some(val); self.parse_mode = Some(val);
self self
} }

View file

@ -108,7 +108,7 @@ impl InlineQueryResultCachedVideo {
self self
} }
pub fn parse_mode<S>(mut self, val: ParseMode) -> Self { pub fn parse_mode(mut self, val: ParseMode) -> Self {
self.parse_mode = Some(val); self.parse_mode = Some(val);
self self
} }

View file

@ -96,7 +96,7 @@ impl InlineQueryResultCachedVoice {
self self
} }
pub fn parse_mode<S>(mut self, val: ParseMode) -> Self { pub fn parse_mode(mut self, val: ParseMode) -> Self {
self.parse_mode = Some(val); self.parse_mode = Some(val);
self self
} }

View file

@ -39,7 +39,7 @@ pub struct InlineQueryResultContact {
pub input_message_content: Option<InputMessageContent>, pub input_message_content: Option<InputMessageContent>,
/// Url of the thumbnail for the result. /// Url of the thumbnail for the result.
pub thumb_url: Option<String>, pub thumb_url: Option<reqwest::Url>,
/// Thumbnail width. /// Thumbnail width.
pub thumb_width: Option<i32>, pub thumb_width: Option<i32>,
@ -119,11 +119,8 @@ impl InlineQueryResultContact {
self self
} }
pub fn thumb_url<S>(mut self, val: S) -> Self pub fn thumb_url(mut self, val: reqwest::Url) -> Self {
where self.thumb_url = Some(val);
S: Into<String>,
{
self.thumb_url = Some(val.into());
self self
} }

View file

@ -36,7 +36,7 @@ pub struct InlineQueryResultDocument {
pub caption_entities: Option<Vec<MessageEntity>>, pub caption_entities: Option<Vec<MessageEntity>>,
/// A valid URL for the file. /// 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 /// Mime type of the content of the file, either `application/pdf` or
/// `application/zip`. /// `application/zip`.
@ -53,7 +53,7 @@ pub struct InlineQueryResultDocument {
pub input_message_content: Option<InputMessageContent>, pub input_message_content: Option<InputMessageContent>,
/// URL of the thumbnail (jpeg only) for the file. /// URL of the thumbnail (jpeg only) for the file.
pub thumb_url: Option<String>, pub thumb_url: Option<reqwest::Url>,
/// Thumbnail width. /// Thumbnail width.
pub thumb_width: Option<i32>, pub thumb_width: Option<i32>,
@ -87,7 +87,7 @@ impl InlineQueryResultDocument {
self self
} }
pub fn parse_mode<S>(mut self, val: ParseMode) -> Self { pub fn parse_mode(mut self, val: ParseMode) -> Self {
self.parse_mode = Some(val); self.parse_mode = Some(val);
self self
} }
@ -100,11 +100,8 @@ impl InlineQueryResultDocument {
self self
} }
pub fn document_url<S>(mut self, val: S) -> Self pub fn document_url(mut self, val: reqwest::Url) -> Self {
where self.document_url = val;
S: Into<String>,
{
self.document_url = val.into();
self self
} }
@ -131,11 +128,8 @@ impl InlineQueryResultDocument {
self self
} }
pub fn thumb_url<S>(mut self, val: S) -> Self pub fn thumb_url(mut self, val: reqwest::Url) -> Self {
where self.thumb_url = Some(val);
S: Into<String>,
{
self.thumb_url = Some(val.into());
self self
} }

View file

@ -16,7 +16,7 @@ pub struct InlineQueryResultGif {
pub id: String, pub id: String,
/// A valid URL for the GIF file. File size must not exceed 1MB. /// 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. /// Width of the GIF.
pub gif_width: Option<i32>, pub gif_width: Option<i32>,
@ -28,7 +28,7 @@ pub struct InlineQueryResultGif {
pub gif_duration: Option<i32>, pub gif_duration: Option<i32>,
/// URL of the static thumbnail for the result (jpeg or gif). /// URL of the static thumbnail for the result (jpeg or gif).
pub thumb_url: String, pub thumb_url: reqwest::Url,
/// Title for the result. /// Title for the result.
pub title: Option<String>, pub title: Option<String>,
@ -58,19 +58,17 @@ pub struct InlineQueryResultGif {
} }
impl InlineQueryResultGif { impl InlineQueryResultGif {
pub fn new<S1, S2, S3>(id: S1, gif_url: S2, thumb_url: S3) -> Self pub fn new<S>(id: S, gif_url: reqwest::Url, thumb_url: reqwest::Url) -> Self
where where
S1: Into<String>, S: Into<String>,
S2: Into<String>,
S3: Into<String>,
{ {
Self { Self {
id: id.into(), id: id.into(),
gif_url: gif_url.into(), gif_url,
gif_width: None, gif_width: None,
gif_height: None, gif_height: None,
gif_duration: None, gif_duration: None,
thumb_url: thumb_url.into(), thumb_url,
title: None, title: None,
caption: None, caption: None,
parse_mode: None, parse_mode: None,
@ -88,11 +86,8 @@ impl InlineQueryResultGif {
self self
} }
pub fn gif_url<S>(mut self, val: S) -> Self pub fn gif_url(mut self, val: reqwest::Url) -> Self {
where self.gif_url = val;
S: Into<String>,
{
self.gif_url = val.into();
self self
} }
@ -111,11 +106,8 @@ impl InlineQueryResultGif {
self self
} }
pub fn thumb_url<S>(mut self, val: S) -> Self pub fn thumb_url(mut self, val: reqwest::Url) -> Self {
where self.thumb_url = val;
S: Into<String>,
{
self.thumb_url = val.into();
self self
} }

View file

@ -49,7 +49,7 @@ pub struct InlineQueryResultLocation {
pub input_message_content: Option<InputMessageContent>, pub input_message_content: Option<InputMessageContent>,
/// Url of the thumbnail for the result. /// Url of the thumbnail for the result.
pub thumb_url: Option<String>, pub thumb_url: Option<reqwest::Url>,
/// Thumbnail width. /// Thumbnail width.
pub thumb_width: Option<u32>, pub thumb_width: Option<u32>,
@ -137,11 +137,8 @@ impl InlineQueryResultLocation {
self self
} }
pub fn thumb_url<S>(mut self, val: S) -> Self pub fn thumb_url<S>(mut self, val: reqwest::Url) -> Self {
where self.thumb_url = Some(val);
S: Into<String>,
{
self.thumb_url = Some(val.into());
self self
} }

View file

@ -17,7 +17,7 @@ pub struct InlineQueryResultMpeg4Gif {
pub id: String, pub id: String,
/// A valid URL for the MP4 file. File size must not exceed 1MB. /// A valid URL for the MP4 file. File size must not exceed 1MB.
pub mpeg4_url: String, pub mpeg4_url: reqwest::Url,
/// Video width. /// Video width.
pub mpeg4_width: Option<i32>, pub mpeg4_width: Option<i32>,
@ -29,7 +29,7 @@ pub struct InlineQueryResultMpeg4Gif {
pub mpeg4_duration: Option<i32>, pub mpeg4_duration: Option<i32>,
/// URL of the static thumbnail (jpeg or gif) for the result. /// URL of the static thumbnail (jpeg or gif) for the result.
pub thumb_url: String, pub thumb_url: reqwest::Url,
/// Title for the result. /// Title for the result.
pub title: Option<String>, pub title: Option<String>,
@ -59,16 +59,14 @@ pub struct InlineQueryResultMpeg4Gif {
} }
impl InlineQueryResultMpeg4Gif { impl InlineQueryResultMpeg4Gif {
pub fn new<S1, S2, S3>(id: S1, mpeg4_url: S2, thumb_url: S3) -> Self pub fn new<S>(id: S, mpeg4_url: reqwest::Url, thumb_url: reqwest::Url) -> Self
where where
S1: Into<String>, S: Into<String>,
S2: Into<String>,
S3: Into<String>,
{ {
Self { Self {
id: id.into(), id: id.into(),
mpeg4_url: mpeg4_url.into(), mpeg4_url,
thumb_url: thumb_url.into(), thumb_url,
mpeg4_width: None, mpeg4_width: None,
mpeg4_height: None, mpeg4_height: None,
mpeg4_duration: None, mpeg4_duration: None,
@ -89,11 +87,8 @@ impl InlineQueryResultMpeg4Gif {
self self
} }
pub fn mpeg4_url<S>(mut self, val: S) -> Self pub fn mpeg4_url(mut self, val: reqwest::Url) -> Self {
where self.mpeg4_url = val;
S: Into<String>,
{
self.mpeg4_url = val.into();
self self
} }
@ -112,11 +107,8 @@ impl InlineQueryResultMpeg4Gif {
self self
} }
pub fn thumb_url<S>(mut self, val: S) -> Self pub fn thumb_url(mut self, val: reqwest::Url) -> Self {
where self.thumb_url = val;
S: Into<String>,
{
self.thumb_url = val.into();
self self
} }
@ -136,7 +128,7 @@ impl InlineQueryResultMpeg4Gif {
self self
} }
pub fn parse_mode<S>(mut self, val: ParseMode) -> Self { pub fn parse_mode(mut self, val: ParseMode) -> Self {
self.parse_mode = Some(val); self.parse_mode = Some(val);
self self
} }

View file

@ -17,10 +17,10 @@ pub struct InlineQueryResultPhoto {
/// A valid URL of the photo. Photo must be in **jpeg** format. Photo size /// A valid URL of the photo. Photo must be in **jpeg** format. Photo size
/// must not exceed 5MB. /// must not exceed 5MB.
pub photo_url: String, pub photo_url: reqwest::Url,
/// URL of the thumbnail for the photo. /// URL of the thumbnail for the photo.
pub thumb_url: String, pub thumb_url: reqwest::Url,
/// Width of the photo. /// Width of the photo.
pub photo_width: Option<i32>, pub photo_width: Option<i32>,
@ -59,16 +59,14 @@ pub struct InlineQueryResultPhoto {
} }
impl InlineQueryResultPhoto { impl InlineQueryResultPhoto {
pub fn new<S1, S2, S3>(id: S1, photo_url: S2, thumb_url: S3) -> Self pub fn new<S>(id: S, photo_url: reqwest::Url, thumb_url: reqwest::Url) -> Self
where where
S1: Into<String>, S: Into<String>,
S2: Into<String>,
S3: Into<String>,
{ {
Self { Self {
id: id.into(), id: id.into(),
photo_url: photo_url.into(), photo_url,
thumb_url: thumb_url.into(), thumb_url,
photo_width: None, photo_width: None,
photo_height: None, photo_height: None,
title: None, title: None,
@ -89,28 +87,22 @@ impl InlineQueryResultPhoto {
self self
} }
pub fn photo_url<S>(mut self, val: S) -> Self pub fn photo_url(mut self, val: reqwest::Url) -> Self {
where self.photo_url = val;
S: Into<String>,
{
self.photo_url = val.into();
self self
} }
pub fn thumb_url<S>(mut self, val: S) -> Self pub fn thumb_url<S>(mut self, val: reqwest::Url) -> Self {
where self.thumb_url = val;
S: Into<String>,
{
self.thumb_url = val.into();
self self
} }
pub fn photo_width<S>(mut self, val: i32) -> Self { pub fn photo_width(mut self, val: i32) -> Self {
self.photo_width = Some(val); self.photo_width = Some(val);
self self
} }
pub fn photo_height<S>(mut self, val: i32) -> Self { pub fn photo_height(mut self, val: i32) -> Self {
self.photo_height = Some(val); self.photo_height = Some(val);
self self
} }
@ -139,7 +131,7 @@ impl InlineQueryResultPhoto {
self self
} }
pub fn parse_mode<S>(mut self, val: ParseMode) -> Self { pub fn parse_mode(mut self, val: ParseMode) -> Self {
self.parse_mode = Some(val); self.parse_mode = Some(val);
self self
} }

View file

@ -52,7 +52,7 @@ pub struct InlineQueryResultVenue {
pub input_message_content: Option<InputMessageContent>, pub input_message_content: Option<InputMessageContent>,
/// Url of the thumbnail for the result. /// Url of the thumbnail for the result.
pub thumb_url: Option<String>, pub thumb_url: Option<reqwest::Url>,
/// Thumbnail width. /// Thumbnail width.
pub thumb_width: Option<i32>, pub thumb_width: Option<i32>,
@ -162,11 +162,8 @@ impl InlineQueryResultVenue {
self self
} }
pub fn thumb_url<S>(mut self, val: S) -> Self pub fn thumb_url(mut self, val: reqwest::Url) -> Self {
where self.thumb_url = Some(val);
S: Into<String>,
{
self.thumb_url = Some(val.into());
self self
} }

View file

@ -18,14 +18,14 @@ pub struct InlineQueryResultVideo {
pub id: String, pub id: String,
/// A valid URL for the embedded video player or video file. /// 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`. /// Mime type of the content of video url, `text/html` or `video/mp4`.
#[serde(with = "crate::types::non_telegram_types::mime::deser")] #[serde(with = "crate::types::non_telegram_types::mime::deser")]
pub mime_type: Mime, pub mime_type: Mime,
/// URL of the thumbnail (jpeg only) for the video. /// URL of the thumbnail (jpeg only) for the video.
pub thumb_url: String, pub thumb_url: reqwest::Url,
/// Title for the result. /// Title for the result.
pub title: String, pub title: String,
@ -72,24 +72,22 @@ pub struct InlineQueryResultVideo {
} }
impl InlineQueryResultVideo { impl InlineQueryResultVideo {
pub fn new<S1, S2, S3, S4>( pub fn new<S1, S2>(
id: S1, id: S1,
video_url: S2, video_url: reqwest::Url,
mime_type: Mime, mime_type: Mime,
thumb_url: S3, thumb_url: reqwest::Url,
title: S4, title: S2,
) -> Self ) -> Self
where where
S1: Into<String>, S1: Into<String>,
S2: Into<String>, S2: Into<String>,
S3: Into<String>,
S4: Into<String>,
{ {
Self { Self {
id: id.into(), id: id.into(),
video_url: video_url.into(), video_url,
mime_type, mime_type,
thumb_url: thumb_url.into(), thumb_url,
title: title.into(), title: title.into(),
caption: None, caption: None,
parse_mode: None, parse_mode: None,
@ -111,11 +109,8 @@ impl InlineQueryResultVideo {
self self
} }
pub fn video_url<S>(mut self, val: S) -> Self pub fn video_url(mut self, val: reqwest::Url) -> Self {
where self.video_url = val;
S: Into<String>,
{
self.video_url = val.into();
self self
} }
@ -124,11 +119,8 @@ impl InlineQueryResultVideo {
self self
} }
pub fn thumb_url<S>(mut self, val: S) -> Self pub fn thumb_url(mut self, val: reqwest::Url) -> Self {
where self.thumb_url = val;
S: Into<String>,
{
self.thumb_url = val.into();
self self
} }

View file

@ -17,7 +17,7 @@ pub struct InlineQueryResultVoice {
pub id: String, pub id: String,
/// A valid URL for the voice recording. /// A valid URL for the voice recording.
pub voice_url: String, pub voice_url: reqwest::Url,
/// Recording title. /// Recording title.
pub title: String, pub title: String,
@ -50,15 +50,14 @@ pub struct InlineQueryResultVoice {
} }
impl InlineQueryResultVoice { impl InlineQueryResultVoice {
pub fn new<S1, S2, S3>(id: S1, voice_url: S2, title: S3) -> Self pub fn new<S1, S2>(id: S1, voice_url: reqwest::Url, title: S2) -> Self
where where
S1: Into<String>, S1: Into<String>,
S2: Into<String>, S2: Into<String>,
S3: Into<String>,
{ {
Self { Self {
id: id.into(), id: id.into(),
voice_url: voice_url.into(), voice_url,
title: title.into(), title: title.into(),
caption: None, caption: None,
parse_mode: None, parse_mode: None,
@ -77,11 +76,8 @@ impl InlineQueryResultVoice {
self self
} }
pub fn voice_url<S>(mut self, val: S) -> Self pub fn voice_url(mut self, val: reqwest::Url) -> Self {
where self.voice_url = val;
S: Into<String>,
{
self.voice_url = val.into();
self self
} }

View file

@ -1,3 +1,4 @@
use reqwest::Url;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::{borrow::Cow, path::PathBuf}; use std::{borrow::Cow, path::PathBuf};
@ -13,7 +14,7 @@ pub enum InputFile {
file_name: String, file_name: String,
data: Cow<'static, [u8]>, data: Cow<'static, [u8]>,
}, },
Url(String), Url(Url),
FileId(String), FileId(String),
} }
@ -36,11 +37,8 @@ impl InputFile {
} }
} }
pub fn url<T>(url: T) -> Self pub fn url(url: Url) -> Self {
where Self::Url(url)
T: Into<String>,
{
Self::Url(url.into())
} }
pub fn file_id<T>(file_id: T) -> Self pub fn file_id<T>(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 { match self {
Self::Url(url) => Some(url), Self::Url(url) => Some(url),
_ => None, _ => None,
@ -118,7 +116,8 @@ impl InputFile {
Ok(Part::stream(Body::wrap_stream(file)).file_name(file_name)) 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::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)),
} }
} }
} }

View file

@ -15,18 +15,39 @@ use serde::{Deserialize, Serialize};
#[serde_with_macros::skip_serializing_none] #[serde_with_macros::skip_serializing_none]
#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] #[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
pub struct LoginUrl { 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<String>, pub forward_text: Option<String>,
/// 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<String>, pub bot_username: Option<String>,
/// Pass `true` to request the permission for your bot to send messages to
/// the user.
pub request_write_access: Option<bool>, pub request_write_access: Option<bool>,
} }
impl LoginUrl { impl LoginUrl {
pub fn url<S>(mut self, val: S) -> Self pub fn url(mut self, val: reqwest::Url) -> Self {
where self.url = val;
S: Into<String>,
{
self.url = val.into();
self self
} }
@ -46,7 +67,7 @@ impl LoginUrl {
self self
} }
pub fn request_write_access<S>(mut self, val: bool) -> Self { pub fn request_write_access(mut self, val: bool) -> Self {
self.request_write_access = Some(val); self.request_write_access = Some(val);
self self
} }

View file

@ -1,5 +1,6 @@
#![allow(clippy::large_enum_variant)] #![allow(clippy::large_enum_variant)]
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::types::{ use crate::types::{
@ -19,7 +20,8 @@ pub struct Message {
pub id: i32, pub id: i32,
/// Date the message was sent in Unix time. /// Date the message was sent in Unix time.
pub date: i32, #[serde(with = "crate::types::serde_date_from_unix_timestamp")]
pub date: DateTime<Utc>,
/// Conversation the message belongs to. /// Conversation the message belongs to.
pub chat: Chat, pub chat: Chat,
@ -78,7 +80,9 @@ pub struct MessageCommon {
pub forward_kind: ForwardKind, pub forward_kind: ForwardKind,
/// Date the message was last edited in Unix time. /// Date the message was last edited in Unix time.
pub edit_date: Option<i32>, #[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<DateTime<Utc>>,
#[serde(flatten)] #[serde(flatten)]
pub media_kind: MediaKind, pub media_kind: MediaKind,
@ -234,7 +238,8 @@ pub enum ForwardKind {
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct ForwardChannel { pub struct ForwardChannel {
#[serde(rename = "forward_date")] #[serde(rename = "forward_date")]
pub date: i32, #[serde(with = "crate::types::serde_date_from_unix_timestamp")]
pub date: DateTime<Utc>,
#[serde(rename = "forward_from_chat")] #[serde(rename = "forward_from_chat")]
pub chat: Chat, pub chat: Chat,
@ -249,7 +254,8 @@ pub struct ForwardChannel {
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct ForwardNonChannel { pub struct ForwardNonChannel {
#[serde(rename = "forward_date")] #[serde(rename = "forward_date")]
pub date: i32, #[serde(with = "crate::types::serde_date_from_unix_timestamp")]
pub date: DateTime<Utc>,
#[serde(flatten)] #[serde(flatten)]
pub from: ForwardedFrom, pub from: ForwardedFrom,
@ -480,6 +486,7 @@ pub struct MessageVoiceChatParticipantsInvited {
} }
mod getters { mod getters {
use chrono::{DateTime, Utc};
use std::ops::Deref; use std::ops::Deref;
use crate::types::{ use crate::types::{
@ -570,7 +577,7 @@ mod getters {
} }
} }
pub fn forward_date(&self) -> Option<&i32> { pub fn forward_date(&self) -> Option<&DateTime<Utc>> {
match &self.kind { match &self.kind {
Common(MessageCommon { Common(MessageCommon {
forward_kind: ForwardKind::Channel(ForwardChannel { date, .. }), 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<Utc>> {
match &self.kind { match &self.kind {
Common(MessageCommon { edit_date, .. }) => edit_date.as_ref(), Common(MessageCommon { edit_date, .. }) => edit_date.as_ref(),
_ => None, _ => None,

View file

@ -59,7 +59,7 @@ pub enum MessageEntityKind {
Italic, Italic,
Code, Code,
Pre { language: Option<String> }, Pre { language: Option<String> },
TextLink { url: String }, TextLink { url: reqwest::Url },
TextMention { user: User }, TextMention { user: User },
Underline, Underline,
Strikethrough, Strikethrough,
@ -76,13 +76,13 @@ mod tests {
assert_eq!( assert_eq!(
MessageEntity { MessageEntity {
kind: MessageEntityKind::TextLink { kind: MessageEntityKind::TextLink {
url: "ya.ru".into() url: reqwest::Url::parse("https://example.com").unwrap(),
}, },
offset: 1, offset: 1,
length: 2, length: 2,
}, },
from_str::<MessageEntity>( from_str::<MessageEntity>(
r#"{"type":"text_link","url":"ya.ru","offset":1,"length":2}"# r#"{"type":"text_link","url":"https://example.com","offset":1,"length":2}"#
) )
.unwrap() .unwrap()
); );
@ -100,10 +100,8 @@ mod tests {
offset: 1, offset: 1,
length: 2, length: 2,
}, },
from_str::<MessageEntity>( from_str::<MessageEntity>(r#"{"type":"pre","offset":1,"length":2,"language":"rust"}"#)
r#"{"type":"pre","url":"ya.ru","offset":1,"length":2,"language":"rust"}"# .unwrap()
)
.unwrap()
); );
} }
} }

View file

@ -1,3 +1,4 @@
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
/// This object represents a file uploaded to Telegram Passport. /// This object represents a file uploaded to Telegram Passport.
@ -19,6 +20,7 @@ pub struct PassportFile {
/// File size. /// File size.
pub file_size: u64, pub file_size: u64,
/// Unix time when the file was uploaded. /// Time when the file was uploaded.
pub file_date: u64, #[serde(with = "crate::types::serde_date_from_unix_timestamp")]
pub file_date: DateTime<Utc>,
} }

View file

@ -1,4 +1,6 @@
use crate::types::{MessageEntity, PollType}; use crate::types::{MessageEntity, PollType};
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
/// This object contains information about a poll. /// 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. /// Amount of time in seconds the poll will be active after creation.
pub open_period: Option<i32>, pub open_period: Option<i32>,
/// Point in time (Unix timestamp) when the poll will be automatically /// Point in time when the poll will be automatically closed.
/// closed. #[serde(with = "crate::types::serde_opt_date_from_unix_timestamp")]
pub close_date: Option<i32>, #[serde(default = "crate::types::serde_opt_date_from_unix_timestamp::none")]
pub close_date: Option<DateTime<Utc>>,
} }
/// This object contains information about one answer option in a poll. /// This object contains information about one answer option in a poll.

View file

@ -144,9 +144,14 @@ mod test {
MessageCommon, MessageKind, Update, UpdateKind, User, MessageCommon, MessageKind, Update, UpdateKind, User,
}; };
use chrono::{DateTime, NaiveDateTime, Utc};
// TODO: more tests for deserialization // TODO: more tests for deserialization
#[test] #[test]
fn message() { fn message() {
let timestamp = 1_569_518_342;
let date = DateTime::from_utc(NaiveDateTime::from_timestamp(timestamp, 0), Utc);
let json = r#"{ let json = r#"{
"update_id":892252934, "update_id":892252934,
"message":{ "message":{
@ -174,7 +179,7 @@ mod test {
kind: UpdateKind::Message(Message { kind: UpdateKind::Message(Message {
via_bot: None, via_bot: None,
id: 6557, id: 6557,
date: 1_569_518_342, date,
chat: Chat { chat: Chat {
id: 218_485_655, id: 218_485_655,
kind: ChatKind::Private(ChatPrivate { kind: ChatKind::Private(ChatPrivate {

View file

@ -1,10 +1,12 @@
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
/// This object represents a service message about a voice chat scheduled in the /// This object represents a service message about a voice chat scheduled in the
/// chat. /// chat.
#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] #[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
pub struct VoiceChatScheduled { pub struct VoiceChatScheduled {
/// Point in time (Unix timestamp) when the voice chat is supposed to be /// Point in time when the voice chat is supposed to be started by a chat
/// started by a chat administrator. /// administrator.
pub start_date: u64, #[serde(with = "crate::types::serde_date_from_unix_timestamp")]
pub start_date: DateTime<Utc>,
} }

View file

@ -1,3 +1,4 @@
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
/// Contains information about the current status of a webhook. /// Contains information about the current status of a webhook.
@ -6,8 +7,8 @@ use serde::{Deserialize, Serialize};
#[serde_with_macros::skip_serializing_none] #[serde_with_macros::skip_serializing_none]
#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] #[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
pub struct WebhookInfo { pub struct WebhookInfo {
/// Webhook URL, may be empty if webhook is not set up. /// Webhook URL, `None` if webhook is not set up.
pub url: String, pub url: Option<reqwest::Url>,
/// `true`, if a custom certificate was provided for webhook certificate /// `true`, if a custom certificate was provided for webhook certificate
/// checks. /// checks.
@ -19,9 +20,11 @@ pub struct WebhookInfo {
/// Currently used webhook IP address. /// Currently used webhook IP address.
pub ip_address: Option<String>, pub ip_address: Option<String>,
/// 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. /// deliver an update via webhook.
pub last_error_date: Option<u64>, #[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<DateTime<Utc>>,
/// Error message in human-readable format for the most recent error that /// Error message in human-readable format for the most recent error that
/// happened when trying to deliver an update via webhook. /// happened when trying to deliver an update via webhook.