From 8d85180f4247026cea5c3d0f29ddcf8bc3639c8b Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Wed, 6 Jul 2022 15:37:07 +0300 Subject: [PATCH] Use builders for api clients (#77) * Use builders for api clients - Use builders for api clients - Add request timeout to reqwest client * fix async tests * export ureq * fix default value for reqwest client * fix client in async tests * address code review comments * address comments * add comments * bump version * fix changelog entry * Update src/api/async_telegram_api_impl.rs Co-authored-by: EdJoPaTo * Update src/api/async_telegram_api_impl.rs Co-authored-by: EdJoPaTo * Update src/api/telegram_api_impl.rs Co-authored-by: EdJoPaTo * Update src/api/telegram_api_impl.rs Co-authored-by: EdJoPaTo Co-authored-by: EdJoPaTo --- CHANGELOG.md | 4 +++ Cargo.toml | 10 ++++++- README.md | 47 ++++++++++++++++++++++++++---- examples/async_custom_client.rs | 37 +++++++++++++++++++++++ examples/custom_client.rs | 35 ++++++++++++++++++++++ src/api/async_telegram_api_impl.rs | 24 ++++++++------- src/api/telegram_api_impl.rs | 29 +++++++----------- src/lib.rs | 4 +++ 8 files changed, 154 insertions(+), 36 deletions(-) create mode 100644 examples/async_custom_client.rs create mode 100644 examples/custom_client.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 2e0e7e0..1a9c112 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.19.0 (2022-07-06) + + * Add builders for api clients [#77](https://github.com/ayrat555/frankenstein/pull/77) + ## 0.18.0 (2022-06-21) ### [Bot API 6.1](https://core.telegram.org/bots/api-changelog#june-20-2022) - [#73](https://github.com/ayrat555/frankenstein/pull/73) diff --git a/Cargo.toml b/Cargo.toml index 8db7a51..7a0bd7f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "frankenstein" -version = "0.18.0" +version = "0.19.0" authors = ["Ayrat Badykov "] description = "Telegram bot API client for Rust" edition = "2018" @@ -26,6 +26,10 @@ required-features = ["http-client"] name = "inline_keyboard" required-features = ["http-client"] +[[example]] +name = "custom_client" +required-features = ["http-client"] + [[example]] name = "async_get_me" required-features = ["async-http-client"] @@ -38,6 +42,10 @@ required-features = ["async-http-client"] name = "async_file_upload" required-features = ["async-http-client"] +[[example]] +name = "async_custom_client" +required-features = ["async-http-client"] + [[example]] name = "api_trait_implementation" required-features = ["telegram-trait"] diff --git a/README.md b/README.md index 8030ee2..4369a0e 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ Add this to your Cargo.toml ```toml [dependencies] -frankenstein = "0.18" +frankenstein = "0.19" ``` ## Features @@ -35,13 +35,13 @@ frankenstein = "0.18" To use the async client add the following line to your `Cargo.toml` file: ```toml -frankenstein = { version = "0.18", default-features = false, features = ["async-http-client"] } +frankenstein = { version = "0.19", default-features = false, features = ["async-http-client"] } ``` You can also disable all features: ```toml -frankenstein = { version = "0.18", default-features = false } +frankenstein = { version = "0.19", default-features = false } ``` In this case the crate will ship only with telegram types @@ -156,9 +156,46 @@ It has two variants: - `File::InputFile` is used to upload a new file using multipart upload. +### Customizing http clients + +Both the async (`reqwest`) and the blocking (`ureq`) HTTP clients can be customized with their builders. + +Customizing the blocking client: + +```rust +use frankenstein::ureq; +use frankenstein::Api; +use std::time::Duration; + +let request_agent = ureq::builder().timeout(Duration::from_secs(100)).build(); +let api_url = format!("{}{}", BASE_API_URL, TOKEN); + +Api::builder() + .api_url(api_url) + .request_agent(request_agent) + .build() +``` + +Customizing the async client: + +```rust +use frankenstein::reqwest; +use frankenstein::AsyncApi; +use std::time::Duration; + +let client = reqwest::ClientBuilder::new() + .connect_timeout(Duration::from_secs(100)) + .timeout(Duration::from_secs(100)) + .build() + .unwrap(); +let api_url = format!("{}{}", BASE_API_URL, TOKEN); + +AsyncApi::builder().api_url(api_url).client(client).build() +``` + ### Documentation -Frankenstein implements all telegram bot api methods. To see which parameters you should pass, check [docs.rs](https://docs.rs/frankenstein/0.18.0/frankenstein/api_traits/telegram_api/trait.TelegramApi.html#provided-methods) +Frankenstein implements all telegram bot api methods. To see which parameters you should pass, check [docs.rs](https://docs.rs/frankenstein/0.19.0/frankenstein/api_traits/telegram_api/trait.TelegramApi.html#provided-methods) You can check out a real world bot created using this library - [El Monitorro](https://github.com/ayrat555/el_monitorro). El Monitorro is a feed reader bot. @@ -170,7 +207,7 @@ The library uses `ureq` http client by default, but it can be easily replaced wi 1. `ureq` comes with a default feature (`impl`). So the feature should be disabled: ```toml -frankenstein = { version = "0.18", default-features = false, features = ["telegram-trait"] } +frankenstein = { version = "0.19", default-features = false, features = ["telegram-trait"] } ``` 2. Implement `TelegramApi` trait which requires two functions: diff --git a/examples/async_custom_client.rs b/examples/async_custom_client.rs new file mode 100644 index 0000000..83f57fe --- /dev/null +++ b/examples/async_custom_client.rs @@ -0,0 +1,37 @@ +use frankenstein::reqwest; +use frankenstein::AsyncApi; +use frankenstein::AsyncTelegramApi; +use std::time::Duration; + +static TOKEN: &str = "API_TOKEN"; +static BASE_API_URL: &str = "https://api.telegram.org/bot"; + +#[tokio::main] +async fn main() { + let api = custom_client(); + + match api.get_me().await { + Ok(response) => { + let user = response.result; + println!( + "Hello, I'm @{}, https://t.me/{}", + user.first_name, + user.username.expect("The bot must have a username.") + ); + } + Err(error) => { + eprintln!("Failed to get me: {:?}", error); + } + } +} + +fn custom_client() -> AsyncApi { + let client = reqwest::ClientBuilder::new() + .connect_timeout(Duration::from_secs(100)) + .timeout(Duration::from_secs(100)) + .build() + .unwrap(); + let api_url = format!("{}{}", BASE_API_URL, TOKEN); + + AsyncApi::builder().api_url(api_url).client(client).build() +} diff --git a/examples/custom_client.rs b/examples/custom_client.rs new file mode 100644 index 0000000..9092895 --- /dev/null +++ b/examples/custom_client.rs @@ -0,0 +1,35 @@ +use frankenstein::ureq; +use frankenstein::Api; +use frankenstein::TelegramApi; +use std::time::Duration; + +static TOKEN: &str = "API_TOKEN"; +static BASE_API_URL: &str = "https://api.telegram.org/bot"; + +fn main() { + let api = custom_client(); + + match api.get_me() { + Ok(response) => { + let user = response.result; + println!( + "Hello, I'm @{}, https://t.me/{}", + user.first_name, + user.username.expect("The bot must have a username.") + ); + } + Err(error) => { + eprintln!("Failed to get me: {:?}", error); + } + } +} + +fn custom_client() -> Api { + let request_agent = ureq::builder().timeout(Duration::from_secs(100)).build(); + let api_url = format!("{}{}", BASE_API_URL, TOKEN); + + Api::builder() + .api_url(api_url) + .request_agent(request_agent) + .build() +} diff --git a/src/api/async_telegram_api_impl.rs b/src/api/async_telegram_api_impl.rs index 42a5672..646b128 100644 --- a/src/api/async_telegram_api_impl.rs +++ b/src/api/async_telegram_api_impl.rs @@ -6,28 +6,30 @@ use async_trait::async_trait; use reqwest::multipart; use serde_json::Value; use std::path::PathBuf; +use std::time::Duration; use tokio::fs::File; +use typed_builder::TypedBuilder; -#[derive(Debug, Clone)] +#[derive(Debug, Clone, TypedBuilder)] pub struct AsyncApi { + #[builder(setter(into))] pub api_url: String, - client: reqwest::Client, + #[builder( + default_code = "reqwest::ClientBuilder::new().connect_timeout(Duration::from_secs(10)).timeout(Duration::from_secs(10)).build().unwrap()" + )] + pub client: reqwest::Client, } impl AsyncApi { + /// Create a new `AsyncApi`. You can use `AsyncApi::builder()` for more options. pub fn new(api_key: &str) -> Self { let api_url = format!("{}{}", super::BASE_API_URL, api_key); - let client = reqwest::Client::new(); - Self { api_url, client } + Self::builder().api_url(api_url).build() } - pub const fn new_with_client(client: reqwest::Client, api_url: String) -> Self { - Self { api_url, client } - } - - pub fn new_url(api_url: String) -> Self { - let client = reqwest::Client::new(); - Self { api_url, client } + /// Create a new `AsyncApi`. You can use `AsyncApi::builder()` for more options. + pub fn new_url>(api_url: T) -> Self { + Self::builder().api_url(api_url).build() } pub fn encode_params( diff --git a/src/api/telegram_api_impl.rs b/src/api/telegram_api_impl.rs index 031e5ea..6f624e3 100644 --- a/src/api/telegram_api_impl.rs +++ b/src/api/telegram_api_impl.rs @@ -6,36 +6,27 @@ use multipart::client::lazy::Multipart; use serde_json::Value; use std::path::PathBuf; use std::time::Duration; +use typed_builder::TypedBuilder; use ureq::Response; -#[derive(Debug, Clone)] +#[derive(Debug, Clone, TypedBuilder)] pub struct Api { + #[builder(setter(into))] pub api_url: String, - request_agent: ureq::Agent, + #[builder(default_code = "ureq::builder().timeout(Duration::from_secs(10)).build()")] + pub request_agent: ureq::Agent, } impl Api { + /// Create a new `Api`. You can use `Api::builder()` for more options. pub fn new(api_key: &str) -> Self { let api_url = format!("{}{}", super::BASE_API_URL, api_key); - - let request_agent = ureq::builder().timeout(Duration::from_secs(60)).build(); - Self { - api_url, - request_agent, - } - } - - pub fn new_url(api_url: String) -> Self { - let request_agent = ureq::builder().timeout(Duration::from_secs(60)).build(); - Self { - api_url, - request_agent, - } + Self::builder().api_url(api_url).build() } - pub fn with_timeout(&mut self, timeout: Duration) { - let request_agent = ureq::builder().timeout(timeout).build(); - self.request_agent = request_agent; + /// Create a new `Api`. You can use `Api::builder()` for more options. + pub fn new_url>(api_url: T) -> Self { + Api::builder().api_url(api_url).build() } pub fn encode_params( diff --git a/src/lib.rs b/src/lib.rs index b0d31d9..5b799cd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -32,6 +32,10 @@ pub use api_traits::*; #[cfg(feature = "async-http-client")] pub use reqwest; +#[doc(hidden)] +#[cfg(feature = "http-client")] +pub use ureq; + pub mod api_params; pub mod objects; mod parse_mode;