Merge pull request #17 from async-telegram-bot/bug/fmt

cargo fmt for all project files
This commit is contained in:
Waffle Lapkin 2019-09-12 19:47:41 +03:00 committed by GitHub
commit a8f04a8d4c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
28 changed files with 266 additions and 245 deletions

View file

@ -4,16 +4,12 @@ use crate::core::{
}; };
use apply::Apply; use apply::Apply;
use serde_json::Value;
use serde::{
Serialize,
de::DeserializeOwned
};
use reqwest::{ use reqwest::{
r#async::{multipart::Form, Client},
StatusCode, StatusCode,
r#async::{Client, multipart::Form},
}; };
use serde::{de::DeserializeOwned, Serialize};
use serde_json::Value;
const TELEGRAM_API_URL: &str = "https://api.telegram.org"; const TELEGRAM_API_URL: &str = "https://api.telegram.org";
@ -58,12 +54,9 @@ pub async fn request_multipart<T: DeserializeOwned>(
.map_err(RequestError::NetworkError)?; .map_err(RequestError::NetworkError)?;
let response = serde_json::from_str::<TelegramResponse<T>>( let response = serde_json::from_str::<TelegramResponse<T>>(
&response &response.text().await.map_err(RequestError::NetworkError)?,
.text()
.await
.map_err(RequestError::NetworkError)?,
) )
.map_err(RequestError::InvalidJson)?; .map_err(RequestError::InvalidJson)?;
match response { match response {
TelegramResponse::Ok { result, .. } => Ok(result), TelegramResponse::Ok { result, .. } => Ok(result),
@ -72,7 +65,10 @@ pub async fn request_multipart<T: DeserializeOwned>(
error_code, error_code,
response_parameters, response_parameters,
.. ..
} => Err(RequestError::ApiError { description, status_code: StatusCode::from_u16(error_code).unwrap() }) } => Err(RequestError::ApiError {
description,
status_code: StatusCode::from_u16(error_code).unwrap(),
}),
} }
} }
@ -90,10 +86,7 @@ pub async fn request_json<T: DeserializeOwned, P: Serialize>(
.map_err(RequestError::NetworkError)?; .map_err(RequestError::NetworkError)?;
let response = serde_json::from_str::<TelegramResponse<T>>( let response = serde_json::from_str::<TelegramResponse<T>>(
&response &response.text().await.map_err(RequestError::NetworkError)?,
.text()
.await
.map_err(RequestError::NetworkError)?,
) )
.map_err(RequestError::InvalidJson)?; .map_err(RequestError::InvalidJson)?;
@ -104,7 +97,10 @@ pub async fn request_json<T: DeserializeOwned, P: Serialize>(
error_code, error_code,
response_parameters, response_parameters,
.. ..
} => Err(RequestError::ApiError { description, status_code: StatusCode::from_u16(error_code).unwrap() }) } => Err(RequestError::ApiError {
description,
status_code: StatusCode::from_u16(error_code).unwrap(),
}),
} }
} }

View file

@ -1,7 +1,9 @@
use serde::Serialize;
use crate::core::requests::{RequestContext, ChatId, Request, RequestFuture, ResponseResult};
use crate::core::types::{Message, ReplyMarkup};
use crate::core::network; use crate::core::network;
use crate::core::requests::{
ChatId, Request, RequestContext, RequestFuture, ResponseResult,
};
use crate::core::types::{Message, ReplyMarkup};
use serde::Serialize;
#[derive(Debug, Clone, Serialize)] #[derive(Debug, Clone, Serialize)]
/// Use this method to edit live location messages. A location can be edited /// Use this method to edit live location messages. A location can be edited
@ -13,16 +15,16 @@ pub struct EditMessageLiveLocation<'a> {
#[serde(skip_serializing)] #[serde(skip_serializing)]
ctx: RequestContext<'a>, ctx: RequestContext<'a>,
#[serde(skip_serializing_if="Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
/// Required if inline_message_id is not specified. Unique identifier for /// Required if inline_message_id is not specified. Unique identifier for
/// the target chat or username of the target channel (in the format /// the target chat or username of the target channel (in the format
/// @channelusername) /// @channelusername)
chat_id: Option<ChatId>, chat_id: Option<ChatId>,
#[serde(skip_serializing_if="Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
/// Required if inline_message_id is not specified. Identifier of the /// Required if inline_message_id is not specified. Identifier of the
/// message to edit /// message to edit
message_id: Option<i64>, message_id: Option<i64>,
#[serde(skip_serializing_if="Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
/// Required if chat_id and message_id are not specified. Identifier of /// Required if chat_id and message_id are not specified. Identifier of
/// the inline message /// the inline message
inline_message_id: Option<String>, inline_message_id: Option<String>,
@ -30,9 +32,9 @@ pub struct EditMessageLiveLocation<'a> {
latitude: f64, latitude: f64,
/// Longitude of new location /// Longitude of new location
longitude: f64, longitude: f64,
#[serde(skip_serializing_if="Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
/// A JSON-serialized object for a new inline keyboard. /// A JSON-serialized object for a new inline keyboard.
reply_markup: Option<ReplyMarkup> reply_markup: Option<ReplyMarkup>,
} }
impl<'a> Request<'a> for EditMessageLiveLocation<'a> { impl<'a> Request<'a> for EditMessageLiveLocation<'a> {
@ -44,14 +46,15 @@ impl<'a> Request<'a> for EditMessageLiveLocation<'a> {
&self.ctx.client, &self.ctx.client,
&self.ctx.token, &self.ctx.token,
"editMessageLiveLocation", "editMessageLiveLocation",
&self &self,
).await )
.await
}) })
} }
} }
impl<'a> EditMessageLiveLocation<'a> { impl<'a> EditMessageLiveLocation<'a> {
pub(crate) fn new ( pub(crate) fn new(
ctx: RequestContext<'a>, ctx: RequestContext<'a>,
latitude: f64, latitude: f64,
longitude: f64, longitude: f64,
@ -63,7 +66,7 @@ impl<'a> EditMessageLiveLocation<'a> {
inline_message_id: None, inline_message_id: None,
latitude, latitude,
longitude, longitude,
reply_markup: None reply_markup: None,
} }
} }
@ -78,7 +81,8 @@ impl<'a> EditMessageLiveLocation<'a> {
} }
pub fn inline_message_id<T>(mut self, inline_message_id: T) -> Self pub fn inline_message_id<T>(mut self, inline_message_id: T) -> Self
where T: Into<String> where
T: Into<String>,
{ {
self.inline_message_id = Some(inline_message_id.into()); self.inline_message_id = Some(inline_message_id.into());
self self

View file

@ -1,14 +1,13 @@
use std::path::PathBuf; use std::path::PathBuf;
use crate::core::{ use crate::core::{
types::{ParseMode, InputMedia}, requests::{utils, ChatId},
requests::{ChatId, utils}, types::{InputMedia, ParseMode},
}; };
use reqwest::r#async::multipart::Form; use reqwest::r#async::multipart::Form;
use serde::Serialize; use serde::Serialize;
/// This is a convenient struct that builds `reqwest::r#async::multipart::Form` /// This is a convenient struct that builds `reqwest::r#async::multipart::Form`
/// from scratch. /// from scratch.
pub struct FormBuilder { pub struct FormBuilder {
@ -22,20 +21,19 @@ impl FormBuilder {
/// Add the supplied key-value pair to this `FormBuilder`. /// Add the supplied key-value pair to this `FormBuilder`.
pub fn add<T>(self, name: &str, value: &T) -> Self pub fn add<T>(self, name: &str, value: &T) -> Self
where T: ToFormValue + ?Sized where
T: ToFormValue + ?Sized,
{ {
Self { Self {
form: self.form.text( form: self.form.text(name.to_owned(), value.to_form_value()),
name.to_owned(),
value.to_form_value()
)
} }
} }
/// Adds a key-value pair to the supplied `FormBuilder` if `value` is some. /// Adds a key-value pair to the supplied `FormBuilder` if `value` is some.
/// Don't forget to implement `serde::Serialize` for `T`! /// Don't forget to implement `serde::Serialize` for `T`!
pub fn add_if_some<T>(self, name: &str, value: Option<&T>) -> Self pub fn add_if_some<T>(self, name: &str, value: Option<&T>) -> Self
where T: ToFormValue + ?Sized where
T: ToFormValue + ?Sized,
{ {
match value { match value {
None => Self { form: self.form }, None => Self { form: self.form },
@ -45,7 +43,9 @@ impl FormBuilder {
pub fn add_file(self, name: &str, path_to_file: &PathBuf) -> Self { pub fn add_file(self, name: &str, path_to_file: &PathBuf) -> Self {
Self { Self {
form: self.form.part(name.to_owned(), utils::file_to_part(path_to_file)) form: self
.form
.part(name.to_owned(), utils::file_to_part(path_to_file)),
} }
} }
@ -70,9 +70,7 @@ macro_rules! impl_for_struct {
}; };
} }
impl_for_struct!( impl_for_struct!(bool, i32, i64, Vec<InputMedia>);
bool, i32, i64, Vec<InputMedia>
);
impl ToFormValue for str { impl ToFormValue for str {
fn to_form_value(&self) -> String { fn to_form_value(&self) -> String {

View file

@ -1,17 +1,12 @@
use crate::core::{ use crate::core::{
network, network,
types::Message,
requests::{ requests::{
ChatId, form_builder::FormBuilder, ChatId, Request, RequestContext,
Request, RequestFuture, ResponseResult,
RequestFuture,
RequestContext,
ResponseResult,
form_builder::FormBuilder,
}, },
types::Message,
}; };
#[derive(Debug, Clone, Serialize)] #[derive(Debug, Clone, Serialize)]
/// Use this method to forward messages of any kind. On success, the sent /// Use this method to forward messages of any kind. On success, the sent
/// [`Message`] is returned. /// [`Message`] is returned.
@ -28,8 +23,9 @@ pub struct ForwardMessage<'a> {
/// Message identifier in the chat specified in from_chat_id /// Message identifier in the chat specified in from_chat_id
pub message_id: i64, pub message_id: i64,
/// Sends the message silently. Users will receive a notification with no sound. /// Sends the message silently. Users will receive a notification with no
#[serde(skip_serializing_if="Option::is_none")] /// sound.
#[serde(skip_serializing_if = "Option::is_none")]
pub disable_notification: Option<bool>, pub disable_notification: Option<bool>,
} }
@ -43,22 +39,25 @@ impl<'a> Request<'a> for ForwardMessage<'a> {
self.ctx.token, self.ctx.token,
"forwardMessage", "forwardMessage",
&self, &self,
).await )
.await
}) })
} }
} }
impl<'a> ForwardMessage<'a> { impl<'a> ForwardMessage<'a> {
pub(crate) fn new(ctx: RequestContext<'a>, pub(crate) fn new(
chat_id: ChatId, ctx: RequestContext<'a>,
from_chat_id: ChatId, chat_id: ChatId,
message_id: i64) -> Self { from_chat_id: ChatId,
message_id: i64,
) -> Self {
Self { Self {
ctx, ctx,
chat_id, chat_id,
from_chat_id, from_chat_id,
message_id, message_id,
disable_notification: None disable_notification: None,
} }
} }

View file

@ -1,15 +1,9 @@
use crate::core::{ use crate::core::{
network, network,
requests::{Request, RequestContext, RequestFuture, ResponseResult},
types::User, types::User,
requests::{
Request,
RequestFuture,
RequestContext,
ResponseResult
},
}; };
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
/// A simple method for testing your bot's auth token. Requires no parameters. /// A simple method for testing your bot's auth token. Requires no parameters.
/// Returns basic information about the bot in form of a [`User`] object. /// Returns basic information about the bot in form of a [`User`] object.
@ -21,14 +15,12 @@ impl<'a> Request<'a> for GetMe<'a> {
type ReturnValue = User; type ReturnValue = User;
fn send(self) -> RequestFuture<'a, ResponseResult<Self::ReturnValue>> { fn send(self) -> RequestFuture<'a, ResponseResult<Self::ReturnValue>> {
Box::pin( Box::pin(network::request_multipart(
network::request_multipart( self.ctx.client,
self.ctx.client, self.ctx.token,
self.ctx.token, "getMe",
"getMe", None,
None ))
)
)
} }
} }

View file

@ -1,19 +1,17 @@
use std::pin::Pin;
use std::future::Future; use std::future::Future;
use std::pin::Pin;
use reqwest::{ use reqwest::{r#async::Client, StatusCode};
r#async::Client, StatusCode
};
use serde::de::DeserializeOwned; use serde::de::DeserializeOwned;
mod form_builder; mod form_builder;
mod utils; mod utils;
#[derive(Debug, Display)] #[derive(Debug, Display)]
pub enum RequestError { pub enum RequestError {
#[display(fmt = "Telegram error #{}: {}", status_code, description)] #[display(fmt = "Telegram error #{}: {}", status_code, description)]
ApiError { // TODO: add response parameters ApiError {
// TODO: add response parameters
status_code: StatusCode, status_code: StatusCode,
description: String, description: String,
}, },
@ -82,20 +80,23 @@ mod tests {
#[test] #[test]
fn chat_id_channel_username_serialization() { fn chat_id_channel_username_serialization() {
let expected_json = String::from(r#""@username""#); let expected_json = String::from(r#""@username""#);
let actual_json = serde_json::to_string(&ChatId::ChannelUsername(String::from("@username"))).unwrap(); let actual_json = serde_json::to_string(&ChatId::ChannelUsername(
String::from("@username"),
))
.unwrap();
assert_eq!(expected_json, actual_json) assert_eq!(expected_json, actual_json)
} }
} }
pub mod get_me; pub mod edit_message_live_location;
pub mod send_message;
pub mod forward_message; pub mod forward_message;
pub mod send_photo; pub mod get_me;
pub mod send_media_group;
pub mod send_audio; pub mod send_audio;
pub mod send_location; pub mod send_location;
pub mod edit_message_live_location; pub mod send_media_group;
pub mod send_message;
pub mod send_photo;
pub mod stop_message_live_location; pub mod stop_message_live_location;
pub mod send_venue; pub mod send_venue;
pub mod send_contact; pub mod send_contact;

View file

@ -1,8 +1,10 @@
use crate::core::{ use crate::core::{
network, network,
requests::{ChatId, Request, RequestFuture, ResponseResult, RequestContext},
requests::form_builder::FormBuilder, requests::form_builder::FormBuilder,
types::{InputFile, ParseMode, Message, ReplyMarkup}, requests::{
ChatId, Request, RequestContext, RequestFuture, ResponseResult,
},
types::{InputFile, Message, ParseMode, ReplyMarkup},
}; };
/// Use this method to send audio files, if you want Telegram clients to display /// Use this method to send audio files, if you want Telegram clients to display
@ -71,11 +73,11 @@ impl<'a> Request<'a> for SendAudio<'a> {
.add_if_some("title", self.title.as_ref()) .add_if_some("title", self.title.as_ref())
.add_if_some( .add_if_some(
"disable_notification", "disable_notification",
self.disable_notification.as_ref() self.disable_notification.as_ref(),
) )
.add_if_some( .add_if_some(
"reply_to_message_id", "reply_to_message_id",
self.reply_to_message_id.as_ref() self.reply_to_message_id.as_ref(),
); );
params = match self.audio { params = match self.audio {
InputFile::File(file) => params.add_file("audio", &file), InputFile::File(file) => params.add_file("audio", &file),
@ -95,8 +97,9 @@ impl<'a> Request<'a> for SendAudio<'a> {
&self.ctx.client, &self.ctx.client,
&self.ctx.token, &self.ctx.token,
"sendAudio", "sendAudio",
Some(params) Some(params),
).await )
.await
}) })
} }
} }
@ -119,7 +122,7 @@ impl<'a> SendAudio<'a> {
thumb: None, thumb: None,
disable_notification: None, disable_notification: None,
reply_to_message_id: None, reply_to_message_id: None,
reply_markup: None reply_markup: None,
} }
} }
@ -163,12 +166,18 @@ impl<'a> SendAudio<'a> {
self self
} }
pub fn disable_notification<T: Into<bool>>(mut self, disable_notification: T) -> Self { pub fn disable_notification<T: Into<bool>>(
mut self,
disable_notification: T,
) -> Self {
self.disable_notification = Some(disable_notification.into()); self.disable_notification = Some(disable_notification.into());
self self
} }
pub fn reply_to_message_id<T: Into<i64>>(mut self, reply_to_message_id: T) -> Self { pub fn reply_to_message_id<T: Into<i64>>(
mut self,
reply_to_message_id: T,
) -> Self {
self.reply_to_message_id = Some(reply_to_message_id.into()); self.reply_to_message_id = Some(reply_to_message_id.into());
self self
} }

View file

@ -1,7 +1,9 @@
use crate::core::{ use crate::core::{
requests::{RequestContext, ChatId, Request, RequestFuture, ResponseResult},
types::{Message, ReplyMarkup},
network, network,
requests::{
ChatId, Request, RequestContext, RequestFuture, ResponseResult,
},
types::{Message, ReplyMarkup},
}; };
use serde::Serialize; use serde::Serialize;
@ -20,19 +22,19 @@ pub struct SendLocation<'a> {
latitude: f64, latitude: f64,
/// Longitude of the location /// Longitude of the location
longitude: f64, longitude: f64,
#[serde(skip_serializing_if="Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
/// Period in seconds for which the location will be updated /// Period in seconds for which the location will be updated
/// (see [Live Locations](https://telegram.org/blog/live-locations)), /// (see [Live Locations](https://telegram.org/blog/live-locations)),
/// should be between 60 and 86400. /// should be between 60 and 86400.
live_period: Option<i32>, live_period: Option<i32>,
#[serde(skip_serializing_if="Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
/// Sends the message silently. Users will receive a notification with /// Sends the message silently. Users will receive a notification with
/// no sound. /// no sound.
disable_notification: Option<bool>, disable_notification: Option<bool>,
#[serde(skip_serializing_if="Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
/// If the message is a reply, ID of the original message /// If the message is a reply, ID of the original message
reply_to_message_id: Option<i64>, reply_to_message_id: Option<i64>,
#[serde(skip_serializing_if="Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
reply_markup: Option<ReplyMarkup>, reply_markup: Option<ReplyMarkup>,
} }
@ -45,8 +47,9 @@ impl<'a> Request<'a> for SendLocation<'a> {
&self.ctx.client, &self.ctx.client,
&self.ctx.token, &self.ctx.token,
"sendLocation", "sendLocation",
&self &self,
).await )
.await
}) })
} }
} }
@ -66,7 +69,7 @@ impl<'a> SendLocation<'a> {
live_period: None, live_period: None,
disable_notification: None, disable_notification: None,
reply_to_message_id: None, reply_to_message_id: None,
reply_markup: None reply_markup: None,
} }
} }

View file

@ -1,14 +1,10 @@
use crate::core::{ use crate::core::{
network::request_multipart, network::request_multipart,
types::{Message, InputMedia, InputFile},
requests::{ requests::{
ChatId, form_builder::FormBuilder, ChatId, Request, RequestContext,
Request, RequestFuture, ResponseResult,
RequestFuture, },
RequestContext, types::{InputFile, InputMedia, Message},
ResponseResult,
form_builder::FormBuilder,
}
}; };
use apply::Apply; use apply::Apply;
@ -32,28 +28,40 @@ impl<'a> Request<'a> for SendMediaGroup<'a> {
let params = FormBuilder::new() let params = FormBuilder::new()
.add("chat_id", &self.chat_id) .add("chat_id", &self.chat_id)
.apply(|form| { .apply(|form| {
self.media self.media.iter().map(|e| e.media()).fold(
.iter() form,
.map(|e| e.media()) |acc, file| {
.fold(form, |acc, file| {
if let InputFile::File(path) = file { if let InputFile::File(path) = file {
acc.add_file( acc.add_file(
&path &path
.file_name() .file_name()
.unwrap() .unwrap()
.to_string_lossy(), .to_string_lossy(),
path path,
) )
} else { } else {
acc acc
} }
}) },
)
}) })
.add("media", &self.media) .add("media", &self.media)
.add_if_some("disable_notification", self.disable_notification.as_ref()) .add_if_some(
.add_if_some("reply_to_message_id", self.reply_to_message_id.as_ref()) "disable_notification",
self.disable_notification.as_ref(),
)
.add_if_some(
"reply_to_message_id",
self.reply_to_message_id.as_ref(),
)
.build(); .build();
request_multipart(&self.ctx.client, &self.ctx.token, "sendMediaGroup", Some(params)).await request_multipart(
&self.ctx.client,
&self.ctx.token,
"sendMediaGroup",
Some(params),
)
.await
}) })
} }
} }

View file

@ -1,19 +1,15 @@
use crate::core::{ use crate::core::{
network, network,
types::{Message, ParseMode, ReplyMarkup},
requests::{ requests::{
form_builder::FormBuilder, form_builder::FormBuilder, ChatId, Request, RequestContext,
ChatId, RequestFuture, ResponseResult,
Request,
RequestFuture,
RequestContext,
ResponseResult,
}, },
types::{Message, ParseMode, ReplyMarkup},
}; };
#[derive(Debug, Clone, Serialize)] #[derive(Debug, Clone, Serialize)]
/// Use this method to send text messages. On success, the sent [`Message`] is returned. /// Use this method to send text messages. On success, the sent [`Message`] is
/// returned.
pub struct SendMessage<'a> { pub struct SendMessage<'a> {
#[serde(skip_serializing)] #[serde(skip_serializing)]
ctx: RequestContext<'a>, ctx: RequestContext<'a>,
@ -32,18 +28,19 @@ pub struct SendMessage<'a> {
/// [Html]: crate::core::types::ParseMode::Html /// [Html]: crate::core::types::ParseMode::Html
/// [bold, italic, fixed-width text or inline URLs]: /// [bold, italic, fixed-width text or inline URLs]:
/// crate::core::types::ParseMode /// crate::core::types::ParseMode
#[serde(skip_serializing_if="Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub parse_mode: Option<ParseMode>, pub parse_mode: Option<ParseMode>,
/// Disables link previews for links in this message /// Disables link previews for links in this message
#[serde(skip_serializing_if="Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub disable_web_page_preview: Option<bool>, pub disable_web_page_preview: Option<bool>,
/// Sends the message silently. Users will receive a notification with no sound. /// Sends the message silently. Users will receive a notification with no
#[serde(skip_serializing_if="Option::is_none")] /// sound.
#[serde(skip_serializing_if = "Option::is_none")]
pub disable_notification: Option<bool>, pub disable_notification: Option<bool>,
/// If the message is a reply, ID of the original message /// If the message is a reply, ID of the original message
#[serde(skip_serializing_if="Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub reply_to_message_id: Option<i64>, pub reply_to_message_id: Option<i64>,
#[serde(skip_serializing_if="Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub reply_markup: Option<ReplyMarkup>, pub reply_markup: Option<ReplyMarkup>,
} }
@ -57,7 +54,8 @@ impl<'a> Request<'a> for SendMessage<'a> {
self.ctx.token, self.ctx.token,
"sendMessage", "sendMessage",
&self, &self,
).await )
.await
}) })
} }
} }

View file

@ -2,20 +2,16 @@ use std::path::Path;
use crate::core::{ use crate::core::{
network, network,
types::{ParseMode, Message, InputFile, ReplyMarkup},
requests::{ requests::{
ChatId, form_builder::FormBuilder, ChatId, Request, RequestContext,
Request, RequestFuture, ResponseResult,
RequestFuture,
RequestContext,
ResponseResult,
form_builder::FormBuilder,
}, },
types::{InputFile, Message, ParseMode, ReplyMarkup},
}; };
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
/// Use this method to send photos. On success, the sent [`Message`] is returned. /// Use this method to send photos. On success, the sent [`Message`] is
/// returned.
pub struct SendPhoto<'a> { pub struct SendPhoto<'a> {
ctx: RequestContext<'a>, ctx: RequestContext<'a>,
@ -23,13 +19,14 @@ pub struct SendPhoto<'a> {
/// (in the format @channelusername) /// (in the format @channelusername)
pub chat_id: ChatId, pub chat_id: ChatId,
/// Photo to send. /// Photo to send.
/// [`InputFile::FileId`] - Pass a file_id as String to send a photo that exists on the /// [`InputFile::FileId`] - Pass a file_id as String to send a photo that
/// Telegram servers (recommended) /// exists on the Telegram servers (recommended)
/// [`InputFile::Url`] - Pass an HTTP URL as a String for Telegram /// [`InputFile::Url`] - Pass an HTTP URL as a String for Telegram
/// to get a photo from the Internet /// to get a photo from the Internet
/// [`InputFile::File`] - Upload a new photo. /// [`InputFile::File`] - Upload a new photo.
pub photo: InputFile, pub photo: InputFile,
/// Photo caption (may also be used when resending photos by file_id), 0-1024 characters /// Photo caption (may also be used when resending photos by file_id),
/// 0-1024 characters
pub caption: Option<String>, pub caption: Option<String>,
/// Send [Markdown] or [HTML], /// Send [Markdown] or [HTML],
/// if you want Telegram apps to show [bold, italic, fixed-width text /// if you want Telegram apps to show [bold, italic, fixed-width text
@ -40,7 +37,8 @@ pub struct SendPhoto<'a> {
/// [bold, italic, fixed-width text or inline URLs]: /// [bold, italic, fixed-width text or inline URLs]:
/// crate::core::types::ParseMode /// crate::core::types::ParseMode
pub parse_mode: Option<ParseMode>, pub parse_mode: Option<ParseMode>,
/// Sends the message silently. Users will receive a notification with no sound. /// Sends the message silently. Users will receive a notification with no
/// sound.
pub disable_notification: Option<bool>, pub disable_notification: Option<bool>,
/// If the message is a reply, ID of the original message /// If the message is a reply, ID of the original message
pub reply_to_message_id: Option<i64>, pub reply_to_message_id: Option<i64>,
@ -58,13 +56,13 @@ impl<'a> Request<'a> for SendPhoto<'a> {
.add_if_some("parse_mode", self.parse_mode.as_ref()) .add_if_some("parse_mode", self.parse_mode.as_ref())
.add_if_some( .add_if_some(
"disable_notification", "disable_notification",
self.disable_notification.as_ref() self.disable_notification.as_ref(),
) )
.add_if_some( .add_if_some(
"reply_to_message_id", "reply_to_message_id",
self.reply_to_message_id.as_ref() self.reply_to_message_id.as_ref(),
); );
params = match self.photo { params = match self.photo {
InputFile::File(path) => params.add_file("photo", &path), InputFile::File(path) => params.add_file("photo", &path),
InputFile::Url(url) => params.add("photo", &url), InputFile::Url(url) => params.add("photo", &url),
@ -76,8 +74,9 @@ impl<'a> Request<'a> for SendPhoto<'a> {
&self.ctx.client, &self.ctx.client,
&self.ctx.token, &self.ctx.token,
"sendPhoto", "sendPhoto",
Some(params) Some(params),
).await )
.await
}) })
} }
} }
@ -86,7 +85,7 @@ impl<'a> SendPhoto<'a> {
pub(crate) fn new( pub(crate) fn new(
ctx: RequestContext<'a>, ctx: RequestContext<'a>,
chat_id: ChatId, chat_id: ChatId,
photo: InputFile photo: InputFile,
) -> Self { ) -> Self {
Self { Self {
ctx, ctx,
@ -96,7 +95,7 @@ impl<'a> SendPhoto<'a> {
parse_mode: None, parse_mode: None,
disable_notification: None, disable_notification: None,
reply_to_message_id: None, reply_to_message_id: None,
reply_markup: None reply_markup: None,
} }
} }
@ -120,12 +119,18 @@ impl<'a> SendPhoto<'a> {
self self
} }
pub fn disable_notification<T: Into<bool>>(mut self, disable_notification: T) -> Self { pub fn disable_notification<T: Into<bool>>(
mut self,
disable_notification: T,
) -> Self {
self.disable_notification = Some(disable_notification.into()); self.disable_notification = Some(disable_notification.into());
self self
} }
pub fn reply_to_message_id<T: Into<i64>>(mut self, reply_to_message_id: T) -> Self { pub fn reply_to_message_id<T: Into<i64>>(
mut self,
reply_to_message_id: T,
) -> Self {
self.reply_to_message_id = Some(reply_to_message_id.into()); self.reply_to_message_id = Some(reply_to_message_id.into());
self self
} }

View file

@ -3,7 +3,7 @@ use std::path::Path;
use crate::core::{ use crate::core::{
network, network,
requests::{ requests::{
ChatId, form_builder::FormBuilder, Request, RequestContext, form_builder::FormBuilder, ChatId, Request, RequestContext,
RequestFuture, ResponseResult, RequestFuture, ResponseResult,
}, },
types::{InlineKeyboardMarkup, Message, ParseMode}, types::{InlineKeyboardMarkup, Message, ParseMode},
@ -18,19 +18,19 @@ struct StopMessageLiveLocation<'a> {
/// Required if inline_message_id is not specified. Unique identifier for /// Required if inline_message_id is not specified. Unique identifier for
/// the target chat or username of the target channel (in the format /// the target chat or username of the target channel (in the format
/// @channelusername) /// @channelusername)
#[serde(skip_serializing_if="Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub chat_id: Option<ChatId>, pub chat_id: Option<ChatId>,
/// Required if inline_message_id is not specified. Identifier of the /// Required if inline_message_id is not specified. Identifier of the
/// message with live location to stop /// message with live location to stop
#[serde(skip_serializing_if="Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub message_id: Option<i32>, pub message_id: Option<i32>,
/// Required if chat_id and message_id are not specified. Identifier of the /// Required if chat_id and message_id are not specified. Identifier of the
/// inline message /// inline message
#[serde(skip_serializing_if="Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub inline_message_id: Option<String>, pub inline_message_id: Option<String>,
/// A JSON-serialized object InlineKeyboardMarkup for a new inline /// A JSON-serialized object InlineKeyboardMarkup for a new inline
/// keyboard. /// keyboard.
#[serde(skip_serializing_if="Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub reply_markup: Option<InlineKeyboardMarkup>, pub reply_markup: Option<InlineKeyboardMarkup>,
} }
@ -51,9 +51,7 @@ impl<'a> Request<'a> for StopMessageLiveLocation<'a> {
} }
impl<'a> StopMessageLiveLocation<'a> { impl<'a> StopMessageLiveLocation<'a> {
fn new( fn new(ctx: RequestContext<'a>) -> Self {
ctx: RequestContext<'a>,
) -> Self {
Self { Self {
ctx, ctx,
chat_id: None, chat_id: None,
@ -64,28 +62,32 @@ impl<'a> StopMessageLiveLocation<'a> {
} }
pub fn chat_id<T>(mut self, chat_id: T) -> Self pub fn chat_id<T>(mut self, chat_id: T) -> Self
where T: Into<ChatId> where
T: Into<ChatId>,
{ {
self.chat_id = chat_id.into(); self.chat_id = chat_id.into();
self self
} }
pub fn message_id<T>(mut self, message_id: T) -> Self pub fn message_id<T>(mut self, message_id: T) -> Self
where T: Into<i32> where
T: Into<i32>,
{ {
self.message_id = Some(message_id.into()); self.message_id = Some(message_id.into());
self self
} }
pub fn inline_message_id<T>(mut self, inline_message_id: T) -> Self pub fn inline_message_id<T>(mut self, inline_message_id: T) -> Self
where T: Into<String> where
T: Into<String>,
{ {
self.inline_message_id = Some(inline_message_id.into()); self.inline_message_id = Some(inline_message_id.into());
self self
} }
pub fn reply_markup<T>(mut self, reply_markup: T) -> Self pub fn reply_markup<T>(mut self, reply_markup: T) -> Self
where T: Into<InlineKeyboardMarkup> where
T: Into<InlineKeyboardMarkup>,
{ {
self.inline_message_id = Some(reply_markup.into()); self.inline_message_id = Some(reply_markup.into());
self self

View file

@ -1,9 +1,9 @@
use tokio::codec::FramedRead;
use std::fs::File;
use bytes::{Bytes, BytesMut}; use bytes::{Bytes, BytesMut};
use tokio::prelude::*;
use reqwest::r#async::multipart::Part; use reqwest::r#async::multipart::Part;
use std::fs::File;
use std::path::PathBuf; use std::path::PathBuf;
use tokio::codec::FramedRead;
use tokio::prelude::*;
struct FileDecoder; struct FileDecoder;
@ -11,9 +11,12 @@ impl tokio::codec::Decoder for FileDecoder {
type Item = Bytes; type Item = Bytes;
type Error = std::io::Error; type Error = std::io::Error;
fn decode(&mut self, src: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error> { fn decode(
&mut self,
src: &mut BytesMut,
) -> Result<Option<Self::Item>, Self::Error> {
if src.is_empty() { if src.is_empty() {
return Ok(None) return Ok(None);
} }
Ok(Some(src.take().freeze())) Ok(Some(src.take().freeze()))
} }
@ -21,9 +24,19 @@ impl tokio::codec::Decoder for FileDecoder {
pub fn file_to_part(path_to_file: &PathBuf) -> Part { pub fn file_to_part(path_to_file: &PathBuf) -> Part {
let file = tokio::fs::File::open(path_to_file.clone()) let file = tokio::fs::File::open(path_to_file.clone())
.map(|file| FramedRead::new(file.unwrap() /* TODO: this can cause panics */, FileDecoder)) .map(|file| {
FramedRead::new(
file.unwrap(), /* TODO: this can cause panics */
FileDecoder,
)
})
.flatten_stream(); .flatten_stream();
let part = Part::stream(file) let part = Part::stream(file).file_name(
.file_name(path_to_file.file_name().unwrap().to_string_lossy().into_owned()); path_to_file
.file_name()
.unwrap()
.to_string_lossy()
.into_owned(),
);
part part
} }

View file

@ -1,4 +1,4 @@
#[derive(Debug, Deserialize, Hash, PartialEq, Eq, Clone )] #[derive(Debug, Deserialize, Hash, PartialEq, Eq, Clone)]
pub struct AnswerPreCheckoutQuery { pub struct AnswerPreCheckoutQuery {
pub pre_checkout_query_id: String, pub pre_checkout_query_id: String,
pub ok: bool, pub ok: bool,

View file

@ -1,6 +1,5 @@
use crate::core::types::{ChatPermissions, ChatPhoto, Message}; use crate::core::types::{ChatPermissions, ChatPhoto, Message};
#[derive(Debug, Deserialize, Eq, Hash, PartialEq, Clone, Serialize)] #[derive(Debug, Deserialize, Eq, Hash, PartialEq, Clone, Serialize)]
pub struct Chat { pub struct Chat {
pub id: i64, pub id: i64,
@ -9,7 +8,6 @@ pub struct Chat {
pub photo: Option<ChatPhoto>, pub photo: Option<ChatPhoto>,
} }
#[derive(Debug, Deserialize, Eq, Hash, PartialEq, Clone, Serialize)] #[derive(Debug, Deserialize, Eq, Hash, PartialEq, Clone, Serialize)]
#[serde(untagged)] #[serde(untagged)]
pub enum ChatKind { pub enum ChatKind {
@ -32,7 +30,6 @@ pub enum ChatKind {
}, },
} }
#[derive(Debug, Deserialize, Eq, Hash, PartialEq, Clone, Serialize)] #[derive(Debug, Deserialize, Eq, Hash, PartialEq, Clone, Serialize)]
#[serde(rename_all = "snake_case")] #[serde(rename_all = "snake_case")]
#[serde(tag = "type")] #[serde(tag = "type")]
@ -101,10 +98,9 @@ mod tests {
}, },
photo: None, photo: None,
}; };
let actual = from_str( let actual =
r#"{"id":-1,"type":"channel","username":"channelname"}"#, from_str(r#"{"id":-1,"type":"channel","username":"channelname"}"#)
) .unwrap();
.unwrap();
assert_eq!(expected, actual); assert_eq!(expected, actual);
} }

View file

@ -15,4 +15,4 @@ pub struct ForceReply {
/// [`Message`] object; 2) if the bot's message is a reply /// [`Message`] object; 2) if the bot's message is a reply
/// (has reply_to_message_id), sender of the original message. /// (has reply_to_message_id), sender of the original message.
pub selective: Option<bool>, pub selective: Option<bool>,
} }

View file

@ -8,7 +8,9 @@ pub struct InlineKeyboardButton {
pub kind: InlineKeyboardButtonKind, pub kind: InlineKeyboardButtonKind,
} }
#[derive(Debug, Clone, PartialEq, PartialOrd, Serialize, Eq, Hash, Deserialize)] #[derive(
Debug, Clone, PartialEq, PartialOrd, Serialize, Eq, Hash, Deserialize,
)]
#[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
@ -27,13 +29,14 @@ pub enum InlineKeyboardButtonKind {
/// the user will be automatically returned to the chat they switched from, /// the user will be automatically returned to the chat they switched from,
/// skipping the chat selection screen. /// skipping the chat selection screen.
SwitchInlineQuery(String), SwitchInlineQuery(String),
/// Optional. If set, pressing the button will insert the bots username and /// Optional. If set, pressing the button will insert the bots username
/// the specified inline query in the current chat's input field. Can be /// and the specified inline query in the current chat's input field.
/// empty, in which case only the bots username will be inserted. /// Can be empty, in which case only the bots username will be
/// inserted.
/// ///
///This offers a quick way for the user to open your bot in inline mode in ///This offers a quick way for the user to open your bot in inline mode in
/// the same chat good for selecting something from multiple options. /// the same chat good for selecting something from multiple options.
SwitchInlineQueryCurrentChat(String), SwitchInlineQueryCurrentChat(String),
// CallbackGame(CallbackGame), TODO: разобраться, что с этим делать /* CallbackGame(CallbackGame), TODO: разобраться, что с этим делать
// TODO: add LoginUrl, pay * TODO: add LoginUrl, pay */
} }

View file

@ -168,11 +168,11 @@ pub enum InputMedia {
impl InputMedia { impl InputMedia {
pub fn media(&self) -> &InputFile { pub fn media(&self) -> &InputFile {
match self { match self {
InputMedia::Photo { media, .. } | InputMedia::Photo { media, .. }
InputMedia::Document { media, .. } | | InputMedia::Document { media, .. }
InputMedia::Audio { media, .. } | | InputMedia::Audio { media, .. }
InputMedia::Animation { media, .. } | | InputMedia::Animation { media, .. }
InputMedia::Video { media, .. } => media, | InputMedia::Video { media, .. } => media,
} }
} }
} }

View file

@ -16,4 +16,4 @@ pub struct KeyboardButton {
/// Optional. If True, the user's current location will be sent when the /// Optional. If True, the user's current location will be sent when the
/// button is pressed. Available in private chats only /// button is pressed. Available in private chats only
pub request_location: Option<bool>, pub request_location: Option<bool>,
} }

View file

@ -125,13 +125,13 @@ pub enum MediaKind {
document: (), document: (),
caption: Option<String>, caption: Option<String>,
#[serde(default = "Vec::new")] #[serde(default = "Vec::new")]
caption_entities: Vec<MessageEntity> caption_entities: Vec<MessageEntity>,
}, },
Audio { Audio {
audio: Audio, audio: Audio,
caption: Option<String>, caption: Option<String>,
#[serde(default = "Vec::new")] #[serde(default = "Vec::new")]
caption_entities: Vec<MessageEntity> caption_entities: Vec<MessageEntity>,
}, },
Contact { Contact {
contact: Contact, contact: Contact,
@ -140,7 +140,7 @@ pub enum MediaKind {
document: Document, document: Document,
caption: Option<String>, caption: Option<String>,
#[serde(default = "Vec::new")] #[serde(default = "Vec::new")]
caption_entities: Vec<MessageEntity> caption_entities: Vec<MessageEntity>,
}, },
Game { Game {
game: Game, game: Game,

View file

@ -8,9 +8,13 @@ pub use self::{
chat_permissions::ChatPermissions, chat_permissions::ChatPermissions,
chat_photo::ChatPhoto, chat_photo::ChatPhoto,
document::Document, document::Document,
force_reply::ForceReply,
inline_keyboard_button::InlineKeyboardButton,
inline_keyboard_markup::InlineKeyboardMarkup,
input_file::InputFile, input_file::InputFile,
input_media::InputMedia, input_media::InputMedia,
invoice::Invoice, invoice::Invoice,
keyboard_button::KeyboardButton,
label_price::LabeledPrice, label_price::LabeledPrice,
message::{ message::{
ForwardKind, ForwardedFrom, MediaKind, Message, MessageKind, Sender, ForwardKind, ForwardedFrom, MediaKind, Message, MessageKind, Sender,
@ -21,6 +25,9 @@ pub use self::{
photo_size::PhotoSize, photo_size::PhotoSize,
poll::{Poll, PollOption}, poll::{Poll, PollOption},
pre_checkout_query::PreCheckoutQuery, pre_checkout_query::PreCheckoutQuery,
reply_keyboard_markup::ReplyKeyboardMarkup,
reply_keyboard_remove::ReplyKeyboardRemove,
reply_markup::ReplyMarkup,
response_parameters::ResponseParameters, response_parameters::ResponseParameters,
send_invoice::SendInvoice, send_invoice::SendInvoice,
shipping_address::ShippingAddress, shipping_address::ShippingAddress,
@ -30,13 +37,6 @@ pub use self::{
successful_payment::SuccessfulPayment, successful_payment::SuccessfulPayment,
user::User, user::User,
video::Video, video::Video,
reply_markup::ReplyMarkup,
force_reply::ForceReply,
inline_keyboard_button::InlineKeyboardButton,
inline_keyboard_markup::InlineKeyboardMarkup,
reply_keyboard_remove::ReplyKeyboardRemove,
reply_keyboard_markup::ReplyKeyboardMarkup,
keyboard_button::KeyboardButton,
}; };
mod answer_pre_checkout_query; mod answer_pre_checkout_query;
@ -48,8 +48,8 @@ mod chat_permissions;
mod chat_photo; mod chat_photo;
mod document; mod document;
mod force_reply; mod force_reply;
mod inline_keyboard_markup;
mod inline_keyboard_button; mod inline_keyboard_button;
mod inline_keyboard_markup;
mod input_file; mod input_file;
mod input_media; mod input_media;
mod invoice; mod invoice;
@ -63,10 +63,10 @@ mod parse_mode;
mod photo_size; mod photo_size;
mod poll; mod poll;
mod pre_checkout_query; mod pre_checkout_query;
mod response_parameters;
mod reply_markup;
mod reply_keyboard_markup; mod reply_keyboard_markup;
mod reply_keyboard_remove; mod reply_keyboard_remove;
mod reply_markup;
mod response_parameters;
mod send_invoice; mod send_invoice;
mod shipping_address; mod shipping_address;
mod shipping_option; mod shipping_option;

View file

@ -69,7 +69,6 @@ pub enum ParseMode {
Markdown, Markdown,
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;

View file

@ -3,11 +3,11 @@ pub struct Poll {
pub id: String, pub id: String,
pub question: String, pub question: String,
pub options: Vec<PollOption>, pub options: Vec<PollOption>,
pub is_closed: bool pub is_closed: bool,
} }
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)] #[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
pub struct PollOption { pub struct PollOption {
pub text: String, pub text: String,
pub voter_count: i32 pub voter_count: i32,
} }

View file

@ -3,23 +3,24 @@ use crate::core::types::KeyboardButton;
/// This object represents a custom keyboard with reply options. /// This object represents a custom keyboard with reply options.
#[derive(Debug, Serialize, Deserialize, Hash, PartialEq, Eq, Clone)] #[derive(Debug, Serialize, Deserialize, Hash, PartialEq, Eq, Clone)]
pub struct ReplyKeyboardMarkup { pub struct ReplyKeyboardMarkup {
/// Array of button rows, each represented by an Array of [`KeyboardButton`] /// Array of button rows, each represented by an Array of
/// objects /// [`KeyboardButton`] objects
pub keyboard: Vec<Vec<KeyboardButton>>, pub keyboard: Vec<Vec<KeyboardButton>>,
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
/// Optional. Requests clients to resize the keyboard vertically for optimal /// Optional. Requests clients to resize the keyboard vertically for
/// fit (e.g., make the keyboard smaller if there are just two rows of /// optimal fit (e.g., make the keyboard smaller if there are just two
/// buttons). Defaults to false, in which case the custom keyboard is always /// rows of buttons). Defaults to false, in which case the custom
/// of the same height as the app's standard keyboard. /// keyboard is always of the same height as the app's standard
/// keyboard.
pub resize_keyboard: Option<bool>, pub resize_keyboard: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
/// Optional. Requests clients to hide the keyboard as soon as it's been /// Optional. Requests clients to hide the keyboard as soon as it's been
/// used. The keyboard will still be available, but clients will /// used. The keyboard will still be available, but clients will
/// automatically display the usual letter-keyboard in the chat the user /// automatically display the usual letter-keyboard in the chat the user
/// can press a special button in the input field to see the custom keyboard /// can press a special button in the input field to see the custom
/// again. Defaults to false. /// keyboard again. Defaults to false.
pub one_time_keyboard: Option<bool>, pub one_time_keyboard: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
@ -32,4 +33,4 @@ pub struct ReplyKeyboardMarkup {
/// the request with a keyboard to select the new language. Other users in /// the request with a keyboard to select the new language. Other users in
/// the group dont see the keyboard. /// the group dont see the keyboard.
pub selective: Option<bool>, pub selective: Option<bool>,
} }

View file

@ -20,4 +20,4 @@ pub struct ReplyKeyboardRemove {
/// the request with a keyboard to select the new language. Other users in /// the request with a keyboard to select the new language. Other users in
/// the group dont see the keyboard. /// the group dont see the keyboard.
pub selective: Option<bool>, pub selective: Option<bool>,
} }

View file

@ -1,8 +1,5 @@
use crate::core::types::{ use crate::core::types::{
InlineKeyboardMarkup, ForceReply, InlineKeyboardMarkup, ReplyKeyboardMarkup, ReplyKeyboardRemove,
ReplyKeyboardMarkup,
ReplyKeyboardRemove,
ForceReply,
}; };
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]

View file

@ -12,9 +12,8 @@ mod tests {
#[test] #[test]
fn migrate_to_chat_id_deserialization() { fn migrate_to_chat_id_deserialization() {
let expected = ResponseParameters::MigrateToChatId(123456); let expected = ResponseParameters::MigrateToChatId(123456);
let actual: ResponseParameters = serde_json::from_str( let actual: ResponseParameters =
r#"{"migrate_to_chat_id":123456}"# serde_json::from_str(r#"{"migrate_to_chat_id":123456}"#).unwrap();
).unwrap();
assert_eq!(expected, actual); assert_eq!(expected, actual);
} }
@ -22,10 +21,9 @@ mod tests {
#[test] #[test]
fn retry_after_deserialization() { fn retry_after_deserialization() {
let expected = ResponseParameters::RetryAfter(123456); let expected = ResponseParameters::RetryAfter(123456);
let actual: ResponseParameters = serde_json::from_str( let actual: ResponseParameters =
r#"{"retry_after":123456}"# serde_json::from_str(r#"{"retry_after":123456}"#).unwrap();
).unwrap();
assert_eq!(expected, actual); assert_eq!(expected, actual);
} }
} }

View file

@ -1,6 +1,5 @@
use crate::core::types::PhotoSize; use crate::core::types::PhotoSize;
/// This object represents a video file. /// This object represents a video file.
#[derive(Debug, Deserialize, Eq, Hash, PartialEq, Serialize, Clone)] #[derive(Debug, Deserialize, Eq, Hash, PartialEq, Serialize, Clone)]
pub struct Video { pub struct Video {