diff --git a/src/bot/api_url.rs b/src/bot/api_url.rs new file mode 100644 index 00000000..8c33a38f --- /dev/null +++ b/src/bot/api_url.rs @@ -0,0 +1,17 @@ +#[derive(Debug, Clone)] +pub(crate) enum ApiUrl { + Default, + #[allow(dead_code)] + Custom(reqwest::Url), +} + +impl ApiUrl { + pub(crate) fn get(&self) -> reqwest::Url { + match self { + // FIXME(waffle): parse once + ApiUrl::Default => reqwest::Url::parse(crate::net::TELEGRAM_API_URL) + .expect("failed to parse default url"), + ApiUrl::Custom(url) => url.clone(), + } + } +} diff --git a/src/bot/mod.rs b/src/bot/mod.rs index 56ef13b3..bdba198b 100644 --- a/src/bot/mod.rs +++ b/src/bot/mod.rs @@ -7,6 +7,7 @@ use reqwest::{ use serde::{de::DeserializeOwned, Serialize}; use crate::{ + bot::api_url::ApiUrl, net, requests::{Payload, ResponseResult}, serde_multipart, @@ -14,6 +15,7 @@ use crate::{ }; mod api; +mod api_url; mod download; pub(crate) const TELOXIDE_TOKEN: &str = "TELOXIDE_TOKEN"; @@ -27,7 +29,7 @@ pub(crate) const TELOXIDE_PROXY: &str = "TELOXIDE_PROXY"; #[derive(Debug, Clone)] pub struct Bot { token: Arc, - api_url: Option>, + api_url: ApiUrl, client: Client, parse_mode: Option, } @@ -103,7 +105,7 @@ impl Bot { { Self { token: Into::>::into(Into::::into(token)), - api_url: None, + api_url: ApiUrl::Default, client, parse_mode: None, } @@ -128,9 +130,7 @@ impl Bot { .expect("serialization of request to be infallible"); // async move to capture client&token&api_url¶ms - async move { - net::request_json2(&client, token.as_ref(), api_url.as_deref(), P::NAME, params).await - } + async move { net::request_json2(&client, token.as_ref(), api_url.get(), P::NAME, params).await } } pub(crate) fn execute_multipart

( @@ -150,8 +150,7 @@ impl Bot { // async move to capture client&token&api_url¶ms async move { let params = params.await?; - net::request_multipart2(&client, token.as_ref(), api_url.as_deref(), P::NAME, params) - .await + net::request_multipart2(&client, token.as_ref(), api_url.get(), P::NAME, params).await } } } @@ -289,7 +288,7 @@ impl BotBuilder { pub fn build(self) -> Bot { Bot { token: self.token.unwrap_or_else(|| get_env(TELOXIDE_TOKEN)).into(), - api_url: None, + api_url: ApiUrl::Default, client: self.client.unwrap_or_else(crate::client_from_env), parse_mode: self.parse_mode, } diff --git a/src/net/download.rs b/src/net/download.rs index aa54f4f0..3a507ad2 100644 --- a/src/net/download.rs +++ b/src/net/download.rs @@ -3,8 +3,6 @@ use tokio::io::{AsyncWrite, AsyncWriteExt}; use crate::errors::DownloadError; -use super::TELEGRAM_API_URL; - pub async fn download_file( client: &Client, token: &str, @@ -15,7 +13,11 @@ where D: AsyncWrite + Unpin, { let mut res = client - .get(&super::file_url(TELEGRAM_API_URL, token, path)) + .get(crate::net::file_url( + reqwest::Url::parse(crate::net::TELEGRAM_API_URL).expect("failed to parse default url"), + token, + path, + )) .send() .await? .error_for_status()?; @@ -33,7 +35,11 @@ pub async fn download_file_stream( path: &str, ) -> Result>, reqwest::Error> { let res = client - .get(&super::file_url(TELEGRAM_API_URL, token, path)) + .get(crate::net::file_url( + reqwest::Url::parse(crate::net::TELEGRAM_API_URL).expect("failed to parse default url"), + token, + path, + )) .send() .await? .error_for_status()?; diff --git a/src/net/mod.rs b/src/net/mod.rs index ede6cc0b..88f857cb 100644 --- a/src/net/mod.rs +++ b/src/net/mod.rs @@ -8,36 +8,38 @@ mod download; mod request; mod telegram_response; -const TELEGRAM_API_URL: &str = "https://api.telegram.org"; +pub(crate) const TELEGRAM_API_URL: &str = "https://api.telegram.org"; /// Creates URL for making HTTPS requests. See the [Telegram documentation]. /// /// [Telegram documentation]: https://core.telegram.org/bots/api#making-requests -fn method_url(base: &str, token: &str, method_name: &str) -> String { - format!("{url}/bot{token}/{method}", url = base, token = token, method = method_name) +fn method_url(base: reqwest::Url, token: &str, method_name: &str) -> reqwest::Url { + base.join(&format!("/bot{token}/{method}", token = token, method = method_name)) + .expect("failed to format url") } /// Creates URL for downloading a file. See the [Telegram documentation]. /// /// [Telegram documentation]: https://core.telegram.org/bots/api#file -fn file_url(base: &str, token: &str, file_path: &str) -> String { - format!("{url}/file/bot{token}/{file}", url = base, token = token, file = file_path,) +fn file_url(base: reqwest::Url, token: &str, file_path: &str) -> reqwest::Url { + base.join(&format!("file/bot{token}/{file}", token = token, file = file_path)) + .expect("failed to format url") } #[cfg(test)] mod tests { - use super::*; + use crate::net::*; #[test] fn method_url_test() { let url = method_url( - TELEGRAM_API_URL, + reqwest::Url::parse(TELEGRAM_API_URL).unwrap(), "535362388:AAF7-g0gYncWnm5IyfZlpPRqRRv6kNAGlao", "methodName", ); assert_eq!( - url, + url.as_str(), "https://api.telegram.org/bot535362388:AAF7-g0gYncWnm5IyfZlpPRqRRv6kNAGlao/methodName" ); } @@ -45,13 +47,13 @@ mod tests { #[test] fn file_url_test() { let url = file_url( - TELEGRAM_API_URL, + reqwest::Url::parse(TELEGRAM_API_URL).unwrap(), "535362388:AAF7-g0gYncWnm5IyfZlpPRqRRv6kNAGlao", "AgADAgADyqoxG2g8aEsu_KjjVsGF4-zetw8ABAEAAwIAA20AA_8QAwABFgQ", ); assert_eq!( - url, + url.as_str(), "https://api.telegram.org/file/bot535362388:AAF7-g0gYncWnm5IyfZlpPRqRRv6kNAGlao/AgADAgADyqoxG2g8aEsu_KjjVsGF4-zetw8ABAEAAwIAA20AA_8QAwABFgQ" ); } diff --git a/src/net/request.rs b/src/net/request.rs index 6a2a65b1..887199e5 100644 --- a/src/net/request.rs +++ b/src/net/request.rs @@ -35,7 +35,11 @@ where }; let response = client - .post(&super::method_url(TELEGRAM_API_URL, token, method_name)) + .post(crate::net::method_url( + reqwest::Url::parse(TELEGRAM_API_URL).expect("failed to parse default url"), + token, + method_name, + )) .multipart(form) .send() .await @@ -55,7 +59,11 @@ where R: DeserializeOwned, { let response = client - .post(&super::method_url(TELEGRAM_API_URL, token, method_name)) + .post(crate::net::method_url( + reqwest::Url::parse(TELEGRAM_API_URL).expect("failed to parse default url"), + token, + method_name, + )) .json(params) .send() .await @@ -72,7 +80,7 @@ where pub async fn request_multipart2( client: &Client, token: &str, - api_url: Option<&str>, + api_url: reqwest::Url, method_name: &str, params: reqwest::multipart::Form, ) -> ResponseResult @@ -80,7 +88,7 @@ where T: DeserializeOwned, { let response = client - .post(&super::method_url(api_url.unwrap_or(TELEGRAM_API_URL), token, method_name)) + .post(crate::net::method_url(api_url, token, method_name)) .multipart(params) .send() .await @@ -92,7 +100,7 @@ where pub async fn request_json2( client: &Client, token: &str, - api_url: Option<&str>, + api_url: reqwest::Url, method_name: &str, params: Vec, ) -> ResponseResult @@ -100,7 +108,7 @@ where T: DeserializeOwned, { let response = client - .post(&super::method_url(api_url.unwrap_or(TELEGRAM_API_URL), token, method_name)) + .post(crate::net::method_url(api_url, token, method_name)) .header(CONTENT_TYPE, HeaderValue::from_static("application/json")) .body(params) .send()