diff --git a/Cargo.toml b/Cargo.toml index 09993956..1cae30f1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -60,3 +60,7 @@ full = ["throttle", "cache_me", "auto_send"] [package.metadata."docs.rs"] all-features = true rustdoc-args = ["--cfg", "docsrs", "-Znormalize-docs"] + +[[example]] +name = "self_info" +required-features = ["tokio/macros", "tokio/rt-multi-thread", "auto_send"] diff --git a/README.md b/README.md index e6ad7315..0e01f9b1 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,30 @@ +
+ +
+ # teloxide-core [![CI status](https://github.com/teloxide/teloxide-core/workflows/Continuous%20integration/badge.svg)](https://github.com/teloxide/teloxide-core/actions) +[![documentation](https://docs.rs/teloxide_core/badge.svg)](https://docs.rs/teloxide_core/) [![documentation (master)](https://img.shields.io/badge/docs-master-blue)](https://teloxide-core.netlify.com) [![LICENSE](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) +[![Api Cov](https://img.shields.io/badge/API%20coverage-Up%20to%200.4.9%20(inclusively)-green.svg)](https://core.telegram.org/bots/api) +[![crates.io](https://img.shields.io/crates/v/teloxide_core.svg)](https://crates.io/crates/teloxide_core) +[![Official Chat](https://img.shields.io/badge/official%20chat-t.me%2Fteloxide-blueviolet)](https://t.me/teloxide) -Core part of `teloxide` library. + + +Core part of the [`teloxide`] library. + +This library provides tools for making requests to the [Telegram Bot API] +(Currently, version `4.9` is supported) with ease. The library is fully +asynchronouns and built using [`tokio`]. + +```toml +teloxide_core = "0.1" +``` +_Compiler support: requires rustc 1.49+_ + +[`teloxide`]: https://docs.rs/teloxide +[Telegram Bot API]: https://core.telegram.org/bots/api +[`tokio`]: https://tokio.rs \ No newline at end of file diff --git a/examples/self_info.rs b/examples/self_info.rs new file mode 100644 index 00000000..78678e9f --- /dev/null +++ b/examples/self_info.rs @@ -0,0 +1,23 @@ +use teloxide_core::{ + prelude::*, + types::{DiceEmoji, ParseMode}, +}; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let chat_id = std::env::var("CHAT_ID") + .expect("Expected CHAT_ID env var") + .parse::()?; + + let bot = Bot::from_env() + .parse_mode(ParseMode::MarkdownV2) + .auto_send(); + + let me = bot.get_me().await?; + + bot.send_dice(chat_id, DiceEmoji::Dice).await?; + bot.send_message(chat_id, format!("Hi, my name is **{}** 👋", me.first_name)) + .await?; + + Ok(()) +} diff --git a/media/example.gif b/media/example.gif new file mode 100644 index 00000000..a0605aa6 Binary files /dev/null and b/media/example.gif differ diff --git a/media/logo.png b/media/logo.png new file mode 100644 index 00000000..2155dd30 Binary files /dev/null and b/media/logo.png differ diff --git a/media/logo.svg b/media/logo.svg new file mode 100644 index 00000000..08a5a668 --- /dev/null +++ b/media/logo.svg @@ -0,0 +1,1907 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/adaptors.rs b/src/adaptors.rs index 1a308c57..8692d43c 100644 --- a/src/adaptors.rs +++ b/src/adaptors.rs @@ -8,12 +8,27 @@ //! //! [`Requester`]: crate::requests::Requester +/// [`AutoSend`] bot adaptor which allows sending a request without calling +/// [`send`]. +/// +/// [`AutoSend`]: auto_send::AutoSend +/// [`send`]: crate::requests::Request::send #[cfg(feature = "auto_send")] #[cfg_attr(all(docsrs, feature = "nightly"), doc(cfg(feature = "auto_send")))] pub mod auto_send; + +/// [`CacheMe`] bot adaptor which caches [`GetMe`] requests. +/// +/// [`CacheMe`]: cache_me::CacheMe +/// [`GetMe`]: crate::payloads::GetMe #[cfg(feature = "cache_me")] #[cfg_attr(all(docsrs, feature = "nightly"), doc(cfg(feature = "cache_me")))] pub mod cache_me; + +/// [`Throttle`] bot adaptor which allows automatically throttle when hitting +/// API limits. +/// +/// [`Throttle`]: throttle::Throttle #[cfg(feature = "throttle")] #[cfg_attr(all(docsrs, feature = "nightly"), doc(cfg(feature = "throttle")))] pub mod throttle; diff --git a/src/adaptors/auto_send.rs b/src/adaptors/auto_send.rs index 890979ac..77ce8e25 100644 --- a/src/adaptors/auto_send.rs +++ b/src/adaptors/auto_send.rs @@ -20,8 +20,8 @@ use crate::{ /// Notes: /// 1. This wrapper should be the most outer i.e.: `AutoSend>` /// will automatically send requests, while `CacheMe>` - won't. -/// 2. After first call to `poll` on a request you will unable to access payload -/// nor could you use [`send_ref`](Request::send_ref). +/// 2. After first call to `poll` on a request you will be unable to access +/// payload nor could you use [`send_ref`](Request::send_ref). /// /// ## Examples /// @@ -74,7 +74,10 @@ macro_rules! fty { }; } -impl Requester for AutoSend { +impl Requester for AutoSend +where + B: Requester, +{ type Err = B::Err; requester_forward! { @@ -112,7 +115,10 @@ download_forward! { #[pin_project::pin_project] pub struct AutoRequest(#[pin] Inner); -impl AutoRequest { +impl AutoRequest +where + R: Request, +{ pub fn new(inner: R) -> Self { Self(Inner::Request(inner)) } @@ -133,7 +139,10 @@ enum Inner { Done, } -impl Request for AutoRequest { +impl Request for AutoRequest +where + R: Request, +{ type Err = R::Err; type Send = R::Send; type SendRef = R::SendRef; diff --git a/src/bot/mod.rs b/src/bot/mod.rs index 07c78829..06f6cea6 100644 --- a/src/bot/mod.rs +++ b/src/bot/mod.rs @@ -22,9 +22,41 @@ pub(crate) const TELOXIDE_PROXY: &str = "TELOXIDE_PROXY"; /// A requests sender. /// -/// No need to put it into [`Arc`], because it's already in. +/// This is the main type of the library, it allows to send requests to the +/// [Telegram Bot API] and download files. +/// +/// ## TBA methods +/// +/// All TBA methods are located in the [`Requester`] [`impl for Bot`]. This +/// allows for opt-in behaviours using requester [adaptors]. +/// +/// ``` +/// # async { +/// use teloxide_core::prelude::*; +/// +/// let bot = Bot::new("TOKEN"); +/// dbg!(bot.get_me().send().await?); +/// # Ok::<_, teloxide_core::RequestError>(()) }; +/// ``` +/// +/// [`Requester`]: crate::requests::Requester +/// [`impl for Bot`]: Bot#impl-Requester +/// [adaptors]: crate::adaptors +/// +/// ## File download +/// +/// In the similar way as with TBA methods, file downloading methods are located +/// in a trait — [`Download<'_>`]. See its documentation for more. +/// +/// [`Download<'_>`]: crate::net::Download +/// +/// ## Clone cost +/// +/// `Bot::clone` is relatively cheap, so if you need to share `Bot`, it's +/// recommended to clone it, instead of wrapping it in [`Arc<_>`]. /// /// [`Arc`]: std::sync::Arc +/// [Telegram Bot API]: https://core.telegram.org/bots/api #[derive(Debug, Clone)] pub struct Bot { token: Arc, @@ -32,6 +64,7 @@ pub struct Bot { client: Client, } +/// Constructors impl Bot { /// Creates a new `Bot` with the specified token and the default /// [http-client](reqwest::Client). @@ -81,7 +114,7 @@ impl Bot { /// [`reqwest::Client`]: https://docs.rs/reqwest/0.10.1/reqwest/struct.Client.html /// [`reqwest::Proxy::all`]: https://docs.rs/reqwest/latest/reqwest/struct.Proxy.html#method.all pub fn from_env() -> Self { - Self::from_env_with_client(crate::client_from_env()) + Self::from_env_with_client(crate::net::client_from_env()) } /// Creates a new `Bot` with the `TELOXIDE_TOKEN` environmental variable (a @@ -122,11 +155,31 @@ impl Bot { /// bot.get_me().send().await /// # }; /// ``` + /// + /// ## Multi-instance behaviour + /// + /// This method only sets the url for one bot instace, older clones are + /// unaffected. + /// + /// ``` + /// use teloxide_core::Bot; + /// + /// let bot = Bot::new("TOKEN"); + /// let bot2 = bot.clone(); + /// let bot = bot.set_api_url(reqwest::Url::parse("https://example.com/").unwrap()); + /// + /// assert_eq!(bot.api_url().as_str(), "https://example.com/"); + /// assert_eq!(bot.clone().api_url().as_str(), "https://example.com/"); + /// assert_ne!(bot2.api_url().as_str(), "https://example.com/"); + /// ``` pub fn set_api_url(mut self, url: reqwest::Url) -> Self { self.api_url = ApiUrl::Custom(Arc::new(url)); self } +} +/// Getters +impl Bot { /// Returns currently used token. pub fn token(&self) -> &str { &self.token diff --git a/src/errors.rs b/src/errors.rs index 3476ceed..4efcae04 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -53,7 +53,7 @@ pub enum RequestError { #[error("An error while parsing JSON: {0}")] InvalidJson(#[source] serde_json::Error), - // Occurs when trying to send a file to Telegram. + /// Occurs when trying to send a file to Telegram. #[error("An I/O error: {0}")] Io(#[source] io::Error), } diff --git a/src/lib.rs b/src/lib.rs index d1105eaf..cca00141 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,13 +1,69 @@ -//! Core part of `teloxide` library. -// TODO: expand docs +//! Core part of the [`teloxide`] library. +//! +//! This library provides tools for making requests to the [Telegram Bot API] +//! (Currently, version `4.9` is supported) with ease. The library is fully +//! asynchronouns and built using [`tokio`]. +//! +//!```toml +//! teloxide_core = "0.1" +//! ``` +//! _Compiler support: requires rustc 1.49+_ +//! +//! ``` +//! # #[cfg(feature = "auto_send")] +//! # async { +//! # let chat_id = 0; +//! use teloxide_core::{ +//! prelude::*, +//! types::{DiceEmoji, ParseMode}, +//! }; +//! +//! let bot = Bot::from_env() +//! .parse_mode(ParseMode::MarkdownV2) +//! .auto_send(); +//! +//! let me = bot.get_me().await?; +//! +//! bot.send_dice(chat_id).emoji(DiceEmoji::Dice).await?; +//! bot.send_message(chat_id, format!("Hi, my name is **{}** 👋", me.first_name)) +//! .await?; +//! # Ok::<_, Box>(()) }; +//! ``` +//! +//!
+//! +//!
+//! +//! [`teloxide`]: https://docs.rs/teloxide +//! [Telegram Bot API]: https://core.telegram.org/bots/api +//! [`tokio`]: https://tokio.rs +//! +//! ## Cargo features +//! +//! - `auto_send` — enables [`AutoSend`] bot adaptor +//! - `throttle` — enables [`Throttle`] bot adaptor +//! - `cache_me` — enables [`CacheMe`] bot adaptor +//! - `full` — enables all features except `nigthly` +//! - `nightly` — enables nigthly-only features, currently: +//! - Removes some future boxing using `#![feature(type_alias_impl_trait)]` +//! - Used to built docs (`#![feature(doc_cfg, doc_spotlight)]`) +//! +//! [`AutoSend`]: adaptors::AutoSend +//! [`Throttle`]: adaptors::Throttle +//! [`CacheMe`]: adaptors::CacheMe +#![doc( + // FIXME(waffle): use github + html_logo_url = "https://cdn.discordapp.com/attachments/224881373326999553/798598120760934410/logo.png", + html_favicon_url = "https://cdn.discordapp.com/attachments/224881373326999553/798598120760934410/logo.png" +)] +#![forbid(unsafe_code)] // we pass "--cfg docsrs" when building docs to add `This is supported on feature="..." only.` // // To properly build docs of this crate run // ```console // $ RUSTDOCFLAGS="--cfg docsrs -Znormalize-docs" cargo doc --open --all-features // ``` -#![forbid(unsafe_code)] #![cfg_attr(all(docsrs, feature = "nightly"), feature(doc_cfg, doc_spotlight))] #![cfg_attr(feature = "nightly", feature(type_alias_impl_trait))] #![cfg_attr(feature = "full", deny(broken_intra_doc_links))] @@ -35,29 +91,3 @@ mod errors; // implementation details mod serde_multipart; - -/// Constructs a client from the `TELOXIDE_PROXY` environmental variable. -/// -/// This function passes the value of `TELOXIDE_PROXY` into -/// [`reqwest::Proxy::all`], if it exists, otherwise returns the default -/// client. -/// -/// # Note -/// The created client will have safe settings, meaning that it will be able to -/// work in long time durations, see the [issue 223]. -/// -/// [`reqwest::Proxy::all`]: https://docs.rs/reqwest/latest/reqwest/struct.Proxy.html#method.all -/// [issue 223]: https://github.com/teloxide/teloxide/issues/223 -pub fn client_from_env() -> reqwest::Client { - use crate::bot::{sound_bot, TELOXIDE_PROXY}; - use reqwest::Proxy; - - let builder = sound_bot(); - - match std::env::var(TELOXIDE_PROXY).ok() { - Some(proxy) => builder.proxy(Proxy::all(&proxy).expect("creating reqwest::Proxy")), - None => builder, - } - .build() - .expect("creating reqwest::Client") -} diff --git a/src/local_macros.rs b/src/local_macros.rs index 271d7cad..fb528f5d 100644 --- a/src/local_macros.rs +++ b/src/local_macros.rs @@ -47,7 +47,15 @@ macro_rules! req_future { $(where $($wh:tt)*)? ) => { #[pin_project::pin_project] - $v struct $i<$T> + pub + // FIXME(waffle): + // The `pub` above should ideally be `$v`, but we currently can't do + // this due to compiler bug, see: + // - pin_project bug report + // - related rustc issue + // - original fix (closed) + // - second iteration of the fix + struct $i<$T> $(where $($wh)*)? { #[pin] @@ -188,6 +196,8 @@ macro_rules! impl_payload { #[allow(clippy::too_many_arguments)] // It's just easier for macros to generate such code. #[allow(clippy::redundant_field_names)] + // It's obvious what this method does. (If you think it's not, feel free to open a PR) + #[allow(missing_docs)] $vi fn new($($($fields : impl_payload!(@convert? $FTy $([$conv])?)),*)?) -> Self { Self { $( diff --git a/src/net/mod.rs b/src/net/mod.rs index 944e1538..6c9665dc 100644 --- a/src/net/mod.rs +++ b/src/net/mod.rs @@ -14,6 +14,38 @@ mod telegram_response; /// The default Telegram API URL. pub const TELEGRAM_API_URL: &str = "https://api.telegram.org"; +/// Constructs a network client from the `TELOXIDE_PROXY` environmental +/// variable. +/// +/// This function passes the value of `TELOXIDE_PROXY` into +/// [`reqwest::Proxy::all`], if it exists, otherwise returns the default +/// client. +/// +/// ## Note +/// +/// The created client will have safe settings, meaning that it will be able to +/// work in long time durations, see the [issue 223]. +/// +/// [`reqwest::Proxy::all`]: https://docs.rs/reqwest/latest/reqwest/struct.Proxy.html#method.all +/// [issue 223]: https://github.com/teloxide/teloxide/issues/223 +/// +/// ## Panics +/// +/// If `TELOXIDE_PROXY` exists, but isn't correct url. +pub fn client_from_env() -> reqwest::Client { + use crate::bot::{sound_bot, TELOXIDE_PROXY}; + use reqwest::Proxy; + + let builder = sound_bot(); + + match std::env::var(TELOXIDE_PROXY).ok() { + Some(proxy) => builder.proxy(Proxy::all(&proxy).expect("creating reqwest::Proxy")), + None => builder, + } + .build() + .expect("creating reqwest::Client") +} + /// Creates URL for making HTTPS requests. See the [Telegram documentation]. /// /// [Telegram documentation]: https://core.telegram.org/bots/api#making-requests diff --git a/src/payloads/mod.rs b/src/payloads/mod.rs index abfebc1b..cf31ea9d 100644 --- a/src/payloads/mod.rs +++ b/src/payloads/mod.rs @@ -1,7 +1,8 @@ //! Request data sent to Telegram. -/// This module re-exports all the setters traits as `_`. When used with a glob -/// import: +/// This module re-exports all the setters traits as `_`. +/// +/// When used with a glob import: /// /// ``` /// use teloxide_core::payloads::setters::*; diff --git a/src/prelude.rs b/src/prelude.rs index 3ea2b60c..ed53d1d7 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -1,3 +1,7 @@ //! Commonly used items. -pub use crate::requests::Requester; +pub use crate::{ + payloads::setters::*, + requests::{Request, Requester, RequesterExt}, + Bot, +}; diff --git a/src/requests/request.rs b/src/requests/request.rs index b719abf5..8517daa2 100644 --- a/src/requests/request.rs +++ b/src/requests/request.rs @@ -13,7 +13,7 @@ use crate::requests::{HasPayload, Output}; /// /// This is crucial for request wrappers which may want to cancel and/or never /// send the underlying request. E.g.: [`Throttle`]'s `send_ref` calls -/// `B::send_ref` while _not_ meaning to really send the request right now. +/// `B::send_ref` while _not_ meaning to really send the request at the moment. /// /// [`Throttle`]: crate::adaptors::Throttle #[cfg_attr(all(docsrs, feature = "nightly"), doc(spotlight))] @@ -32,8 +32,8 @@ pub trait Request: HasPayload { /// A type of the future returned by the [`send_ref`](Request::send_ref) /// method. - // Note: it intentionally forbids borrowing from `self` though anyway we - // couldn't allow borrowing without GATs. + // Note: it intentionally forbids borrowing from `self` though we couldn't allow + // borrowing without GATs anyway. type SendRef: Future, Self::Err>> + Send; /// Send this request. @@ -49,6 +49,8 @@ pub trait Request: HasPayload { /// }; /// /// let bot = Bot::new("TOKEN"); + /// + /// // Note: it's recommended to `Requester` instead of creating requests directly /// let method = GetMe::new(); /// let request = JsonRequest::new(bot, method); /// let _: User = request.send().await.unwrap(); diff --git a/src/requests/requester.rs b/src/requests/requester.rs index 0de68ad5..e8a32943 100644 --- a/src/requests/requester.rs +++ b/src/requests/requester.rs @@ -15,9 +15,42 @@ use crate::{ /// /// This trait is implemented by all bots & bot adaptors. /// -/// _This trait is included in the crate's [`prelude`](crate::prelude)_. +/// ## Examples +/// +/// Calling TBA methods: +/// +/// ``` +/// # async { +/// use teloxide_core::{prelude::*, types::ParseMode}; +/// +/// // Bot implements `Requester` +/// let bot = Bot::new("TOKEN"); +/// +/// // Required parameters are supplied to the `Requester` methods: +/// bot.send_message(0, "Text") +/// // Optional parameters can be supplied by calling setters +/// .parse_mode(ParseMode::HTML) +/// // To send request to telegram you need to call `.send()` and await the resulting future +/// .send() +/// .await?; +/// # Ok::<_, teloxide_core::RequestError>(()) }; +/// ``` +/// +/// Using `Requester` in a generic context: +/// +/// ``` +/// use teloxide_core::{prelude::*, types::Message}; +/// +/// async fn send_hi(bot: R, chat: i64) -> Message +/// where +/// R: Requester, +/// { +/// bot.send_message(chat, "hi").send().await.expect("error") +/// } +/// ``` #[cfg_attr(all(docsrs, feature = "nightly"), doc(spotlight))] pub trait Requester { + /// Error type returned by all requests. type Err: std::error::Error + Send; // This block is auto generated by `cg` (be02d84). diff --git a/src/types/animation.rs b/src/types/animation.rs index 9cf1b0e5..ae0c9de5 100644 --- a/src/types/animation.rs +++ b/src/types/animation.rs @@ -1,6 +1,7 @@ +use mime::Mime; use serde::{Deserialize, Serialize}; -use crate::types::{MimeWrapper, PhotoSize}; +use crate::types::PhotoSize; /// This object represents an animation file (GIF or H.264/MPEG-4 AVC video /// without sound). @@ -33,7 +34,8 @@ pub struct Animation { pub file_name: Option, /// A MIME type of the file as defined by a sender. - pub mime_type: Option, + #[serde(with = "crate::types::non_telegram_types::mime::opt_deser")] + pub mime_type: Option, /// A size of a file. pub file_size: Option, @@ -108,7 +110,7 @@ impl Animation { self } - pub fn mime_type(mut self, val: MimeWrapper) -> Self { + pub fn mime_type(mut self, val: Mime) -> Self { self.mime_type = Some(val); self } @@ -155,7 +157,7 @@ mod tests { file_size: Some(3452), }), file_name: Some("some".to_string()), - mime_type: Some(MimeWrapper("video/gif".parse().unwrap())), + mime_type: Some("video/gif".parse().unwrap()), file_size: Some(6500), }; let actual = serde_json::from_str::(json).unwrap(); diff --git a/src/types/audio.rs b/src/types/audio.rs index b9d6279d..23225eba 100644 --- a/src/types/audio.rs +++ b/src/types/audio.rs @@ -1,6 +1,7 @@ +use mime::Mime; use serde::{Deserialize, Serialize}; -use crate::types::{MimeWrapper, PhotoSize}; +use crate::types::PhotoSize; /// This object represents an audio file to be treated as music by the Telegram /// clients. @@ -27,7 +28,8 @@ pub struct Audio { pub title: Option, /// A MIME type of the file as defined by a sender. - pub mime_type: Option, + #[serde(with = "crate::types::non_telegram_types::mime::opt_deser")] + pub mime_type: Option, /// A size of a file. pub file_size: Option, @@ -91,7 +93,7 @@ impl Audio { self } - pub fn mime_type(mut self, val: MimeWrapper) -> Self { + pub fn mime_type(mut self, val: Mime) -> Self { self.mime_type = Some(val); self } @@ -135,7 +137,7 @@ mod tests { duration: 60, performer: Some("Performer".to_string()), title: Some("Title".to_string()), - mime_type: Some(serde_json::from_str("\"application/zip\"").unwrap()), + mime_type: Some("application/zip".parse().unwrap()), file_size: Some(123_456), thumb: Some(PhotoSize { file_id: "id".to_string(), diff --git a/src/types/document.rs b/src/types/document.rs index aa60c2db..75e28918 100644 --- a/src/types/document.rs +++ b/src/types/document.rs @@ -1,6 +1,7 @@ +use mime::Mime; use serde::{Deserialize, Serialize}; -use crate::types::{MimeWrapper, PhotoSize}; +use crate::types::PhotoSize; /// This object represents a general file (as opposed to [photos], [voice /// messages] and [audio files]). @@ -28,7 +29,8 @@ pub struct Document { pub file_name: Option, /// A MIME type of the file as defined by a sender. - pub mime_type: Option, + #[serde(with = "crate::types::non_telegram_types::mime::opt_deser")] + pub mime_type: Option, /// A size of a file. pub file_size: Option, @@ -79,7 +81,7 @@ impl Document { self } - pub fn mime_type(mut self, val: MimeWrapper) -> Self { + pub fn mime_type(mut self, val: Mime) -> Self { self.mime_type = Some(val); self } diff --git a/src/types/inline_query_result_document.rs b/src/types/inline_query_result_document.rs index 7cf0a5f2..780e2cb7 100644 --- a/src/types/inline_query_result_document.rs +++ b/src/types/inline_query_result_document.rs @@ -1,6 +1,7 @@ +use mime::Mime; use serde::{Deserialize, Serialize}; -use crate::types::{InlineKeyboardMarkup, InputMessageContent, MimeWrapper, ParseMode}; +use crate::types::{InlineKeyboardMarkup, InputMessageContent, ParseMode}; /// Represents a link to a file. /// @@ -35,7 +36,8 @@ pub struct InlineQueryResultDocument { /// Mime type of the content of the file, either `application/pdf` or /// `application/zip`. - pub mime_type: MimeWrapper, + #[serde(with = "crate::types::non_telegram_types::mime::deser")] + pub mime_type: Mime, /// Short description of the result. pub description: Option, @@ -94,7 +96,7 @@ impl InlineQueryResultDocument { self } - pub fn mime_type(mut self, val: MimeWrapper) -> Self { + pub fn mime_type(mut self, val: Mime) -> Self { self.mime_type = val; self } diff --git a/src/types/inline_query_result_video.rs b/src/types/inline_query_result_video.rs index 7d190473..a9d9030f 100644 --- a/src/types/inline_query_result_video.rs +++ b/src/types/inline_query_result_video.rs @@ -1,6 +1,7 @@ +use mime::Mime; use serde::{Deserialize, Serialize}; -use crate::types::{InlineKeyboardMarkup, InputMessageContent, MimeWrapper, ParseMode}; +use crate::types::{InlineKeyboardMarkup, InputMessageContent, ParseMode}; /// Represents a link to a page containing an embedded video player or a video /// file. @@ -20,7 +21,8 @@ pub struct InlineQueryResultVideo { pub video_url: String, /// Mime type of the content of video url, `text/html` or `video/mp4`. - pub mime_type: MimeWrapper, + #[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, @@ -69,7 +71,7 @@ impl InlineQueryResultVideo { pub fn new( id: S1, video_url: S2, - mime_type: MimeWrapper, + mime_type: Mime, thumb_url: S3, title: S4, ) -> Self @@ -112,7 +114,7 @@ impl InlineQueryResultVideo { self } - pub fn mime_type(mut self, val: MimeWrapper) -> Self { + pub fn mime_type(mut self, val: Mime) -> Self { self.mime_type = val; self } diff --git a/src/types/non_telegram_types/country_code.rs b/src/types/non_telegram_types/country_code.rs index 4f7705ac..ddb4154e 100644 --- a/src/types/non_telegram_types/country_code.rs +++ b/src/types/non_telegram_types/country_code.rs @@ -1,254 +1,504 @@ use serde::{Deserialize, Serialize}; +/// ISO 3166-1 alpha-2 language code. #[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] pub enum CountryCode { + /// Andorra AD, + /// United Arab Emirates AE, + /// Afghanistan AF, + /// Antigua and Barbuda AG, + /// Anguilla AI, + /// Albania AL, + /// Armenia AM, + /// Angola AO, + /// Antarctica AQ, + /// Argentina AR, + /// American Samoa AS, + /// Austria AT, + /// Australia AU, + /// Aruba AW, + /// Åland Islands AX, + /// Azerbaijan AZ, + /// Bosnia and Herzegovina BA, + /// Barbados BB, + /// Bangladesh BD, + /// Belgium BE, + /// Burkina Faso BF, + /// Bulgaria BG, + /// Bahrain BH, + /// Burundi BI, + /// Benin BJ, + /// Saint Barthélemy BL, + /// Bermuda BM, + /// Brunei Darussalam BN, + /// Bolivia (Plurinational State of) BO, + /// Bonaire, Sint Eustatius and Saba BQ, + /// Brazil BR, + /// Bahamas BS, + /// Bhutan BT, + /// Bouvet Island BV, + /// Botswana BW, + /// Belarus BY, + /// Belize BZ, + /// Canada CA, + /// Cocos (Keeling) Islands CC, + /// Congo, Democratic Republic of the CD, + /// Central African Republic CF, + /// Congo CG, + /// Switzerland CH, + /// Côte d'Ivoire CI, + /// Cook Islands CK, + /// Chile CL, + /// Cameroon CM, + /// China CN, + /// Colombia CO, + /// Costa Rica CR, + /// Cuba CU, + /// Cabo Verde CV, + /// Curaçao CW, + /// Christmas Island CX, + /// Cyprus CY, + /// Czechia CZ, + /// Germany DE, + /// Djibouti DJ, + /// Denmark DK, + /// Dominica DM, + /// Dominican Republic DO, + /// Algeria DZ, + /// Ecuador EC, + /// Estonia EE, + /// Egypt EG, + /// Western Sahara EH, + /// Eritrea ER, + /// Spain ES, + /// Ethiopia ET, + /// Finland FI, + /// Fiji FJ, + /// Falkland Islands (Malvinas) FK, + /// Micronesia (Federated States of) FM, + /// Faroe Islands FO, + /// France FR, + /// Gabon GA, + /// United Kingdom of Great Britain and Northern Ireland GB, + /// Grenada GD, + /// Georgia GE, + /// French Guiana GF, + /// Guernsey GG, + /// Ghana GH, + /// Gibraltar GI, + /// Greenland GL, + /// Gambia GM, + /// Guinea GN, + /// Guadeloupe GP, + /// Equatorial Guinea GQ, + /// Greece GR, + /// South Georgia and the South Sandwich Islands GS, + /// Guatemala GT, + /// Guam GU, + /// Guinea-Bissau GW, + /// Guyana GY, + /// Hong Kong HK, + /// Heard Island and McDonald Islands HM, + /// Honduras HN, + /// Croatia HR, + /// Haiti HT, + /// Hungary HU, + /// Indonesia ID, + /// Ireland IE, + /// Israel IL, + /// Isle of Man IM, + /// India IN, + /// British Indian Ocean Territory IO, + /// Iraq IQ, + /// Iran (Islamic Republic of) IR, + /// Iceland IS, + /// Italy IT, + /// Jersey JE, + /// Jamaica JM, + /// Jordan JO, + /// Japan JP, + /// Kenya KE, + /// Kyrgyzstan KG, + /// Cambodia KH, + /// Kiribati KI, + /// Comoros KM, + /// Saint Kitts and Nevis KN, + /// Korea (Democratic People's Republic of) KP, + /// Korea, Republic of KR, + /// Kuwait KW, + /// Cayman Islands KY, + /// Kazakhstan KZ, + /// Lao People's Democratic Republic LA, + /// Lebanon LB, + /// Saint Lucia LC, + /// Liechtenstein LI, + /// Sri Lanka LK, + /// Liberia LR, + /// Lesotho LS, + /// Lithuania LT, + /// Luxembourg LU, + /// Latvia LV, + /// Libya LY, + /// Morocco MA, + /// Monaco MC, + /// Moldova, Republic of MD, + /// Montenegro ME, + /// Saint Martin (French part) MF, + /// Madagascar MG, + /// Marshall Islands MH, + /// North Macedonia MK, + /// Mali ML, + /// Myanmar MM, + /// Mongolia MN, + /// Macao MO, + /// Northern Mariana Islands MP, + /// Martinique MQ, + /// Mauritania MR, + /// Montserrat MS, + /// Malta MT, + /// Mauritius MU, + /// Maldives MV, + /// Malawi MW, + /// Mexico MX, + /// Malaysia MY, + /// Mozambique MZ, + /// Namibia NA, + /// New Caledonia NC, + /// Niger NE, + /// Norfolk Island NF, + /// Nigeria NG, + /// Nicaragua NI, + /// Netherlands NL, + /// Norway NO, + /// Nepal NP, + /// Nauru NR, + /// Niue NU, + /// New Zealand NZ, + /// Oman OM, + /// Panama PA, + /// Peru PE, + /// French Polynesia PF, + /// Papua New Guinea PG, + /// Philippines PH, + /// Pakistan PK, + /// Poland PL, + /// Saint Pierre and Miquelon PM, + /// Pitcairn PN, + /// Puerto Rico PR, + /// Palestine, State of PS, + /// Portugal PT, + /// Palau PW, + /// Paraguay PY, + /// Qatar QA, + /// Réunion RE, + /// Romania RO, + /// Serbia RS, + /// Russian Federation RU, + /// Rwanda RW, + /// Saudi Arabia SA, + /// Solomon Islands SB, + /// Seychelles SC, + /// Sudan SD, + /// Sweden SE, + /// Singapore SG, + /// Saint Helena, Ascension and Tristan da Cunha SH, + /// Slovenia SI, + /// Svalbard and Jan Mayen SJ, + /// Slovakia SK, + /// Sierra Leone SL, + /// San Marino SM, + /// Senegal SN, + /// Somalia SO, + /// Suriname SR, + /// South Sudan SS, + /// Sao Tome and Principe ST, + /// El Salvador SV, + /// Sint Maarten (Dutch part) SX, + /// Syrian Arab Republic SY, + /// Eswatini SZ, + /// Turks and Caicos Islands TC, + /// Chad TD, + /// French Southern Territories TF, + /// Togo TG, + /// Thailand TH, + /// Tajikistan TJ, + /// Tokelau TK, + /// Timor-Leste TL, + /// Turkmenistan TM, + /// Tunisia TN, + /// Tonga TO, + /// Turkey TR, + /// Trinidad and Tobago TT, + /// Tuvalu TV, + /// Taiwan, Province of China TW, + /// Tanzania, United Republic of TZ, + /// Ukraine UA, + /// Uganda UG, + /// United States Minor Outlying Islands UM, + /// United States of America US, + /// Uruguay UY, + /// Uzbekistan UZ, + /// Holy See VA, + /// Saint Vincent and the Grenadines VC, + /// Venezuela (Bolivarian Republic of) VE, + /// Virgin Islands (British) VG, + /// Virgin Islands (U.S.) VI, + /// Viet Nam VN, + /// Vanuatu VU, + /// Wallis and Futuna WF, + /// Samoa WS, + /// Yemen YE, + /// Mayotte YT, + /// South Africa ZA, + /// Zambia ZM, + /// Zimbabwe ZW, } diff --git a/src/types/non_telegram_types/currency.rs b/src/types/non_telegram_types/currency.rs index 0cdebce6..dcdec729 100644 --- a/src/types/non_telegram_types/currency.rs +++ b/src/types/non_telegram_types/currency.rs @@ -1,89 +1,364 @@ use serde::{Deserialize, Serialize}; +/// ISO 4217 currency. #[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] pub enum Currency { + /// United Arab Emirates dirham AED, + /// Afghan afghani AFN, + /// Albanian lek ALL, + /// Armenian dram AMD, + /// Netherlands Antillean guilder + ANG, + /// Angolan kwanza + AOA, + /// Argentine peso ARS, + /// Australian dollar AUD, + /// Aruban florin + AWG, + /// Azerbaijani manat AZN, + /// Bosnia and Herzegovina convertible mark BAM, + /// Barbados dollar + BBD, + /// Bangladeshi taka BDT, + /// Bulgarian lev BGN, + /// Bahraini dinar + BHD, + /// Burundian franc + BIF, + /// Bermudian dollar + BMD, + /// Brunei dollar BND, + /// Boliviano BOB, + /// Bolivian Mvdol (funds code) + BOV, + /// Brazilian real BRL, + /// Bahamian dollar + BSD, + /// Bhutanese ngultrum + BTN, + /// Botswana pula + BWP, + /// Belarusian ruble + BYN, + /// Belize dollar + BZD, + /// Canadian dollar CAD, + /// Congolese franc + CDF, + /// WIR euro (complementary currency) + CHE, + /// Swiss franc CHF, + /// WIR franc (complementary currency) + CHW, + /// Unidad de Fomento (funds code) + CLF, + /// Chilean peso CLP, + /// Chinese yuan CNY, + /// Colombian peso COP, + /// Unidad de Valor Real (UVR) (funds code) + COU, + /// Costa Rican colon CRC, + /// Cuban convertible peso + CUC, + /// Cuban peso + CUP, + /// Cape Verdean escudo + CVE, + /// Czech koruna CZK, + /// Djiboutian franc + DJF, + /// Danish krone DKK, + /// Dominican peso DOP, + /// Algerian dinar DZD, + /// Egyptian pound EGP, + /// Eritrean nakfa + ERN, + /// Ethiopian birr + ETB, + /// Euro EUR, + /// Fiji dollar + FJD, + /// Falkland Islands pound + FKP, + /// Pound sterling GBP, + /// Georgian lari GEL, + /// Ghanaian cedi + GHS, + /// Gibraltar pound + GIP, + /// Gambian dalasi + GMD, + /// Guinean franc + GNF, + /// Guatemalan quetzal GTQ, + /// Guyanese dollar + GYD, + /// Hong Kong dollar HKD, + /// Honduran lempira HNL, + /// Croatian kuna HRK, + /// Haitian gourde + HTG, + /// Hungarian forint HUF, + /// Indonesian rupiah IDR, + /// Israeli new shekel ILS, + /// Indian rupee INR, + /// Iraqi dinar + IQD, + /// Iranian rial + IRR, + /// Icelandic króna ISK, + /// Jamaican dollar JMD, + /// Jordanian dinar + JOD, + /// Japanese yen JPY, + /// Kenyan shilling KES, + /// Kyrgyzstani som KGS, + /// Cambodian riel + KHR, + /// Comoro franc + KMF, + /// North Korean won + KPW, + /// South Korean won KRW, + /// Kuwaiti dinar + KWD, + /// Cayman Islands dollar + KYD, + /// Kazakhstani tenge KZT, + /// Lao kip + LAK, + /// Lebanese pound LBP, + /// Sri Lankan rupee LKR, + /// Liberian dollar + LRD, + /// Lesotho loti + LSL, + /// Libyan dinar + LYD, + /// Moroccan dirham MAD, + /// Moldovan leu MDL, + /// Malagasy ariary + MGA, + /// Macedonian denar + MKD, + /// Myanmar kyat + MMK, + /// Mongolian tögrög MNT, + /// Macanese pataca + MOP, + /// Mauritanian ouguiya + MRU, + /// Mauritian rupee MUR, + /// Maldivian rufiyaa MVR, + /// Malawian kwacha + MWK, + /// Mexican peso MXN, + /// Mexican Unidad de Inversion (UDI) (funds code) + MXV, + /// Malaysian ringgit MYR, + /// Mozambican metical MZN, + /// Namibian dollar + NAD, + /// Nigerian naira NGN, + /// Nicaraguan córdoba NIO, + /// Norwegian krone NOK, + /// Nepalese rupee NPR, + /// New Zealand dollar NZD, + /// Omani rial + OMR, + /// Panamanian balboa PAB, + /// Peruvian sol PEN, + /// Papua New Guinean kina + PGK, + /// Philippine peso PHP, + /// Pakistani rupee PKR, + /// Polish złoty PLN, + /// Paraguayan guaraní PYG, + /// Qatari riyal QAR, + /// Romanian leu RON, + /// Serbian dinar RSD, + /// Russian ruble RUB, + /// Rwandan franc + RWF, + /// Saudi riyal SAR, + /// Solomon Islands dollar + SBD, + /// Seychelles rupee + SCR, + /// Sudanese pound + SDG, + /// Swedish krona/kronor SEK, + /// Singapore dollar SGD, + /// Saint Helena pound + SHP, + /// Sierra Leonean leone + SLL, + /// Somali shilling + SOS, + /// Surinamese dollar + SRD, + /// South Sudanese pound + SSP, + /// São Tomé and Príncipe dobra + STN, + /// Salvadoran colón + SVC, + /// Syrian pound + SYP, + /// Swazi lilangeni + SZL, + /// Thai baht THB, + /// Tajikistani somoni TJS, + /// Turkmenistan manat + TMT, + /// Tunisian dinar + TND, + /// Tongan paʻanga + TOP, + /// Turkish lira TRY, + /// Trinidad and Tobago dollar TTD, + /// New Taiwan dollar TWD, + /// Tanzanian shilling TZS, + /// Ukrainian hryvnia UAH, + /// Ugandan shilling UGX, + /// United States dollar USD, + /// United States dollar (next day) (funds code) + USN, + /// Uruguay Peso en Unidades Indexadas (URUIURUI) (funds code) + UYI, + /// Uruguayan peso UYU, + /// Unidad previsional + UYW, + /// Uzbekistan som UZS, + /// Venezuelan bolívar soberano + VES, + /// Vietnamese đồng VND, + /// Vanuatu vatu + VUV, + /// Samoan tala + WST, + /// CFA franc BEAC + XAF, + /// Silver (one troy ounce) + XAG, + /// Gold (one troy ounce) + XAU, + /// European Composite Unit (EURCO) (bond market unit) + XBA, + /// European Monetary Unit (E.M.U.-6) (bond market unit) + XBB, + /// European Unit of Account 9 (E.U.A.-9) (bond market unit) + XBC, + /// European Unit of Account 17 (E.U.A.-17) (bond market unit) + XBD, + /// East Caribbean dollar + XCD, + /// Special drawing rights + XDR, + /// CFA franc BCEAO + XOF, + /// Palladium (one troy ounce) + XPD, + /// CFP franc (franc Pacifique) + XPF, + /// Platinum (one troy ounce) + XPT, + /// SUCRE + XSU, + /// Code reserved for testing + XTS, + /// ADB Unit of Account + XUA, + /// No currency + XXX, + /// Yemeni rial YER, + /// South African rand ZAR, + /// Zambian kwacha + ZMW, + /// Zimbabwean dollar + ZWL, } diff --git a/src/types/non_telegram_types/mime.rs b/src/types/non_telegram_types/mime.rs new file mode 100644 index 00000000..34422e34 --- /dev/null +++ b/src/types/non_telegram_types/mime.rs @@ -0,0 +1,97 @@ +use std::fmt; + +use mime::Mime; +use serde::{de::Visitor, Deserialize, Deserializer, Serialize, Serializer}; + +pub(crate) mod deser { + use mime::Mime; + use serde::{Deserialize, Deserializer, Serialize, Serializer}; + + use super::{MimeDe, MimeSer}; + + pub(crate) fn serialize( + this: &Mime, + serializer: S, + ) -> Result<::Ok, ::Error> + where + S: Serializer, + { + MimeSer(this).serialize(serializer) + } + + pub(crate) fn deserialize<'de, D>( + deserializer: D, + ) -> Result>::Error> + where + D: Deserializer<'de>, + { + MimeDe::deserialize(deserializer).map(|MimeDe(m)| m) + } +} + +pub(crate) mod opt_deser { + use mime::Mime; + use serde::{Deserialize, Deserializer, Serialize, Serializer}; + + use super::{MimeDe, MimeSer}; + + pub(crate) fn serialize( + this: &Option, + serializer: S, + ) -> Result<::Ok, ::Error> + where + S: Serializer, + { + this.as_ref().map(MimeSer).serialize(serializer) + } + + pub(crate) fn deserialize<'de, D>( + deserializer: D, + ) -> Result, >::Error> + where + D: Deserializer<'de>, + { + Option::::deserialize(deserializer).map(|opt| opt.map(|MimeDe(m)| m)) + } +} + +struct MimeSer<'a>(&'a Mime); + +impl Serialize for MimeSer<'_> { + fn serialize(&self, serializer: S) -> Result<::Ok, ::Error> + where + S: Serializer, + { + serializer.serialize_str(self.0.as_ref()) + } +} + +struct MimeVisitor; +impl<'a> Visitor<'a> for MimeVisitor { + type Value = MimeDe; + + fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + formatter.write_str("mime type") + } + + fn visit_str(self, v: &str) -> Result + where + E: serde::de::Error, + { + match v.parse::() { + Ok(mime_type) => Ok(MimeDe(mime_type)), + Err(e) => Err(E::custom(e)), + } + } +} + +struct MimeDe(Mime); + +impl<'de> Deserialize<'de> for MimeDe { + fn deserialize(deserializer: D) -> Result>::Error> + where + D: Deserializer<'de>, + { + deserializer.deserialize_str(MimeVisitor) + } +} diff --git a/src/types/non_telegram_types/mime_wrapper.rs b/src/types/non_telegram_types/mime_wrapper.rs deleted file mode 100644 index 43326de6..00000000 --- a/src/types/non_telegram_types/mime_wrapper.rs +++ /dev/null @@ -1,44 +0,0 @@ -use derive_more::From; -use mime::Mime; -use serde::{de::Visitor, export::Formatter, Deserialize, Deserializer, Serialize, Serializer}; - -/// Serializable & deserializable `MIME` wrapper. -#[derive(Clone, Debug, Eq, Hash, PartialEq, From)] -pub struct MimeWrapper(pub Mime); - -impl Serialize for MimeWrapper { - fn serialize(&self, serializer: S) -> Result<::Ok, ::Error> - where - S: Serializer, - { - serializer.serialize_str(self.0.as_ref()) - } -} - -struct MimeVisitor; -impl<'a> Visitor<'a> for MimeVisitor { - type Value = MimeWrapper; - - fn expecting(&self, formatter: &mut Formatter<'_>) -> Result<(), serde::export::fmt::Error> { - formatter.write_str("mime type") - } - - fn visit_str(self, v: &str) -> Result - where - E: serde::de::Error, - { - match v.parse::() { - Ok(mime_type) => Ok(MimeWrapper(mime_type)), - Err(e) => Err(E::custom(e)), - } - } -} - -impl<'de> Deserialize<'de> for MimeWrapper { - fn deserialize(deserializer: D) -> Result>::Error> - where - D: Deserializer<'de>, - { - deserializer.deserialize_str(MimeVisitor) - } -} diff --git a/src/types/non_telegram_types/mod.rs b/src/types/non_telegram_types/mod.rs index 6c8e1636..49360c4f 100644 --- a/src/types/non_telegram_types/mod.rs +++ b/src/types/non_telegram_types/mod.rs @@ -1,9 +1,9 @@ pub use country_code::*; pub use currency::*; -pub use mime_wrapper::*; pub use non_strict_vec::*; mod country_code; mod currency; -mod mime_wrapper; mod non_strict_vec; + +pub(crate) mod mime; diff --git a/src/types/video.rs b/src/types/video.rs index 98241c49..1db5737f 100644 --- a/src/types/video.rs +++ b/src/types/video.rs @@ -1,6 +1,7 @@ +use mime::Mime; use serde::{Deserialize, Serialize}; -use crate::types::{MimeWrapper, PhotoSize}; +use crate::types::PhotoSize; /// This object represents a video file. /// @@ -29,7 +30,8 @@ pub struct Video { pub thumb: Option, /// Mime type of a file as defined by sender. - pub mime_type: Option, + #[serde(with = "crate::types::non_telegram_types::mime::opt_deser")] + pub mime_type: Option, /// File size. pub file_size: Option, @@ -95,7 +97,7 @@ impl Video { self } - pub fn mime_type(mut self, val: MimeWrapper) -> Self { + pub fn mime_type(mut self, val: Mime) -> Self { self.mime_type = Some(val); self } diff --git a/src/types/voice.rs b/src/types/voice.rs index f60cc204..2fc7a5b9 100644 --- a/src/types/voice.rs +++ b/src/types/voice.rs @@ -1,4 +1,4 @@ -use crate::types::MimeWrapper; +use mime::Mime; use serde::{Deserialize, Serialize}; /// This object represents a voice note. @@ -19,7 +19,8 @@ pub struct Voice { pub duration: u32, /// MIME type of the file as defined by sender. - pub mime_type: Option, + #[serde(with = "crate::types::non_telegram_types::mime::opt_deser")] + pub mime_type: Option, /// File size. pub file_size: Option, @@ -61,7 +62,7 @@ impl Voice { self } - pub fn mime_type(mut self, val: MimeWrapper) -> Self { + pub fn mime_type(mut self, val: Mime) -> Self { self.mime_type = Some(val); self }