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]
- 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
[pr115]: https://github.com/teloxide/teloxide-core/pull/115
## 0.3.3

View file

@ -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)?;

View file

@ -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"));

View file

@ -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,

View file

@ -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<S>(
this: &Option<DateTime<Utc>>,
@ -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<Option<DateTime<Utc>>, D::Error> where
// D: Deserializer<'de>,
// {
// Ok(Option::<i64>::deserialize(deserializer)?
// .map(|timestamp|
// DateTime::from_utc(NaiveDateTime::from_timestamp(timestamp, 0), Utc)))
// }
pub(crate) fn deserialize<'de, D>(deserializer: D) -> Result<Option<DateTime<Utc>>, D::Error>
where
D: Deserializer<'de>,
{
Ok(Option::<i64>::deserialize(deserializer)?
.map(|timestamp| 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 {

View file

@ -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<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
/// after joining the chat via this invite link; 1-99999
pub member_limit: Option<u32>,

View file

@ -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<Utc>,
/// Previous information about the chat member
pub old_chat_member: ChatMember,
/// New information about the chat member

View file

@ -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),

View file

@ -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()])

View file

@ -21,7 +21,7 @@ pub struct InlineQueryResultArticle {
pub reply_markup: Option<InlineKeyboardMarkup>,
/// 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
/// message.
@ -31,7 +31,7 @@ pub struct InlineQueryResultArticle {
pub description: Option<String>,
/// Url of the thumbnail for the result.
pub thumb_url: Option<String>,
pub thumb_url: Option<reqwest::Url>,
/// Thumbnail width.
pub thumb_width: Option<i32>,
@ -86,11 +86,8 @@ impl InlineQueryResultArticle {
self
}
pub fn url<S>(mut self, val: S) -> Self
where
S: Into<String>,
{
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<S>(mut self, val: S) -> Self
where
S: Into<String>,
{
self.thumb_url = Some(val.into());
pub fn thumb_url<S>(mut self, val: reqwest::Url) -> Self {
self.thumb_url = Some(val);
self
}

View file

@ -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<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
S1: Into<String>,
S2: Into<String>,
S3: Into<String>,
{
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<S>(mut self, val: S) -> Self
where
S: Into<String>,
{
self.audio_url = val.into();
pub fn audio_url(mut self, val: reqwest::Url) -> Self {
self.audio_url = val;
self
}

View file

@ -108,7 +108,7 @@ impl InlineQueryResultCachedDocument {
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
}

View file

@ -88,7 +88,7 @@ impl InlineQueryResultCachedMpeg4Gif {
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
}

View file

@ -107,7 +107,7 @@ impl InlineQueryResultCachedPhoto {
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
}

View file

@ -108,7 +108,7 @@ impl InlineQueryResultCachedVideo {
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
}

View file

@ -96,7 +96,7 @@ impl InlineQueryResultCachedVoice {
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
}

View file

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

View file

@ -36,7 +36,7 @@ pub struct InlineQueryResultDocument {
pub caption_entities: Option<Vec<MessageEntity>>,
/// 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<InputMessageContent>,
/// URL of the thumbnail (jpeg only) for the file.
pub thumb_url: Option<String>,
pub thumb_url: Option<reqwest::Url>,
/// Thumbnail width.
pub thumb_width: Option<i32>,
@ -87,7 +87,7 @@ impl InlineQueryResultDocument {
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
}
@ -100,11 +100,8 @@ impl InlineQueryResultDocument {
self
}
pub fn document_url<S>(mut self, val: S) -> Self
where
S: Into<String>,
{
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<S>(mut self, val: S) -> Self
where
S: Into<String>,
{
self.thumb_url = Some(val.into());
pub fn thumb_url(mut self, val: reqwest::Url) -> Self {
self.thumb_url = Some(val);
self
}

View file

@ -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<i32>,
@ -28,7 +28,7 @@ pub struct InlineQueryResultGif {
pub gif_duration: Option<i32>,
/// 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<String>,
@ -58,19 +58,17 @@ pub struct 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
S1: Into<String>,
S2: Into<String>,
S3: Into<String>,
S: Into<String>,
{
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<S>(mut self, val: S) -> Self
where
S: Into<String>,
{
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<S>(mut self, val: S) -> Self
where
S: Into<String>,
{
self.thumb_url = val.into();
pub fn thumb_url(mut self, val: reqwest::Url) -> Self {
self.thumb_url = val;
self
}

View file

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

View file

@ -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<i32>,
@ -29,7 +29,7 @@ pub struct InlineQueryResultMpeg4Gif {
pub mpeg4_duration: Option<i32>,
/// 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<String>,
@ -59,16 +59,14 @@ pub struct 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
S1: Into<String>,
S2: Into<String>,
S3: Into<String>,
S: Into<String>,
{
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<S>(mut self, val: S) -> Self
where
S: Into<String>,
{
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<S>(mut self, val: S) -> Self
where
S: Into<String>,
{
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<S>(mut self, val: ParseMode) -> Self {
pub fn parse_mode(mut self, val: ParseMode) -> Self {
self.parse_mode = Some(val);
self
}

View file

@ -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<i32>,
@ -59,16 +59,14 @@ pub struct 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
S1: Into<String>,
S2: Into<String>,
S3: Into<String>,
S: Into<String>,
{
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<S>(mut self, val: S) -> Self
where
S: Into<String>,
{
self.photo_url = val.into();
pub fn photo_url(mut self, val: reqwest::Url) -> Self {
self.photo_url = val;
self
}
pub fn thumb_url<S>(mut self, val: S) -> Self
where
S: Into<String>,
{
self.thumb_url = val.into();
pub fn thumb_url<S>(mut self, val: reqwest::Url) -> Self {
self.thumb_url = val;
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
}
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
}
@ -139,7 +131,7 @@ impl InlineQueryResultPhoto {
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
}

View file

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

View file

@ -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<S1, S2, S3, S4>(
pub fn new<S1, S2>(
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<String>,
S2: Into<String>,
S3: Into<String>,
S4: Into<String>,
{
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<S>(mut self, val: S) -> Self
where
S: Into<String>,
{
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<S>(mut self, val: S) -> Self
where
S: Into<String>,
{
self.thumb_url = val.into();
pub fn thumb_url(mut self, val: reqwest::Url) -> Self {
self.thumb_url = val;
self
}

View file

@ -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<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
S1: Into<String>,
S2: Into<String>,
S3: Into<String>,
{
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<S>(mut self, val: S) -> Self
where
S: Into<String>,
{
self.voice_url = val.into();
pub fn voice_url(mut self, val: reqwest::Url) -> Self {
self.voice_url = val;
self
}

View file

@ -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<T>(url: T) -> Self
where
T: Into<String>,
{
Self::Url(url.into())
pub fn url(url: Url) -> Self {
Self::Url(url)
}
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 {
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)),
}
}
}

View file

@ -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<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>,
/// Pass `true` to request the permission for your bot to send messages to
/// the user.
pub request_write_access: Option<bool>,
}
impl LoginUrl {
pub fn url<S>(mut self, val: S) -> Self
where
S: Into<String>,
{
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<S>(mut self, val: bool) -> Self {
pub fn request_write_access(mut self, val: bool) -> Self {
self.request_write_access = Some(val);
self
}

View file

@ -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<Utc>,
/// 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<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)]
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<Utc>,
#[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<Utc>,
#[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<Utc>> {
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<Utc>> {
match &self.kind {
Common(MessageCommon { edit_date, .. }) => edit_date.as_ref(),
_ => None,

View file

@ -59,7 +59,7 @@ pub enum MessageEntityKind {
Italic,
Code,
Pre { language: Option<String> },
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::<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()
);
@ -100,10 +100,8 @@ mod tests {
offset: 1,
length: 2,
},
from_str::<MessageEntity>(
r#"{"type":"pre","url":"ya.ru","offset":1,"length":2,"language":"rust"}"#
)
.unwrap()
from_str::<MessageEntity>(r#"{"type":"pre","offset":1,"length":2,"language":"rust"}"#)
.unwrap()
);
}
}

View file

@ -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<Utc>,
}

View file

@ -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<i32>,
/// Point in time (Unix timestamp) when the poll will be automatically
/// closed.
pub close_date: Option<i32>,
/// 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<DateTime<Utc>>,
}
/// This object contains information about one answer option in a poll.

View file

@ -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 {

View file

@ -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<Utc>,
}

View file

@ -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<reqwest::Url>,
/// `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<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.
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
/// happened when trying to deliver an update via webhook.