diff --git a/src/network/download.rs b/src/network/download.rs new file mode 100644 index 00000000..7345e87f --- /dev/null +++ b/src/network/download.rs @@ -0,0 +1,39 @@ +use reqwest::r#async::{Client, Chunk}; +use tokio::{ + stream::Stream, + io::{AsyncWrite, AsyncWriteExt}, +}; + +use crate::{ + DownloadError, + network::{TELEGRAM_API_URL, file_url}, +}; + + +pub async fn download_file( + client: &Client, + token: &str, + path: &str, + destination: &mut D, +) -> Result<(), DownloadError> + where + D: AsyncWrite + Unpin, +{ + let mut stream = download_file_stream(client, token, path).await?; + + while let Some(chunk) = stream.next().await { + destination.write_all(chunk?.bytes()).await?; + } + + Ok(()) +} + +pub(crate) async fn download_file_stream( + client: &Client, + token: &str, + path: &str, +) -> Result>, reqwest::Error> { + let url = file_url(TELEGRAM_API_URL, token, path); + let resp = client.get(&url).send().await?.error_for_status()?; + Ok(resp.into_body()) +} \ No newline at end of file diff --git a/src/network/mod.rs b/src/network/mod.rs index ff241d5e..516912f8 100644 --- a/src/network/mod.rs +++ b/src/network/mod.rs @@ -1,23 +1,26 @@ +mod download; +mod request; +mod telegram_response; + +use apply::Apply; +use bytes::Buf; use futures::StreamExt; +use reqwest::{ + r#async::{multipart::Form, Chunk, Client, Response}, + StatusCode, +}; use serde::{de::DeserializeOwned, Serialize}; use tokio::{ - stream::Stream, io::{AsyncWrite, AsyncWriteExt}, + stream::Stream, }; -use reqwest::{ - StatusCode, - r#async::{multipart::Form, Client, Response, Chunk}, -}; -use bytes::Buf; -use apply::Apply; use crate::{ - DownloadError, RequestError, - requests::ResponseResult, types::ResponseParameters, + requests::ResponseResult, types::ResponseParameters, DownloadError, + RequestError, }; - -const TELEGRAM_API_URL: &str = "https://api.telegram.org"; +pub const TELEGRAM_API_URL: &str = "https://api.telegram.org"; /// Creates URL for making HTTPS requests. See the [Telegram documentation]. /// @@ -43,133 +46,6 @@ fn file_url(base: &str, token: &str, file_path: &str) -> String { ) } -pub async fn request_multipart( - client: &Client, - token: &str, - method_name: &str, - params: Option
, -) -> ResponseResult { - process_response( - client - .post(&method_url(TELEGRAM_API_URL, token, method_name)) - .apply(|request_builder| match params { - Some(params) => request_builder.multipart(params), - None => request_builder, - }) - .send() - .await - .map_err(RequestError::NetworkError)?, - ) - .await -} - -pub async fn request_json( - client: &Client, - token: &str, - method_name: &str, - params: &P, -) -> ResponseResult { - process_response( - client - .post(&method_url(TELEGRAM_API_URL, token, method_name)) - .json(params) - .send() - .await - .map_err(RequestError::NetworkError)?, - ) - .await -} - -async fn process_response( - mut response: Response, -) -> ResponseResult { - let response = serde_json::from_str::>( - &response.text().await.map_err(RequestError::NetworkError)?, - ) - .map_err(RequestError::InvalidJson)?; - - response.into() -} - -#[derive(Deserialize)] -#[serde(untagged)] -enum TelegramResponse { - Ok { - /// A dummy field. Used only for deserialization. - #[allow(dead_code)] - ok: bool, // TODO: True type - - result: R, - }, - Err { - /// A dummy field. Used only for deserialization. - #[allow(dead_code)] - ok: bool, // TODO: False type - - description: String, - error_code: u16, - response_parameters: Option, - }, -} - -pub async fn download_file( - client: &Client, - token: &str, - path: &str, - destination: &mut D, -) -> Result<(), DownloadError> -where - D: AsyncWrite + Unpin, -{ - let mut stream = download_file_stream(client, token, path).await?; - - while let Some(chunk) = stream.next().await { - destination.write_all(chunk?.bytes()).await?; - } - - Ok(()) -} - -pub(crate) async fn download_file_stream( - client: &Client, - token: &str, - path: &str, -) -> Result>, reqwest::Error> { - let url = file_url(TELEGRAM_API_URL, token, path); - let resp = client.get(&url).send().await?.error_for_status()?; - Ok(resp.into_body()) -} - -impl Into> for TelegramResponse { - fn into(self) -> Result { - match self { - TelegramResponse::Ok { result, .. } => Ok(result), - TelegramResponse::Err { - description, - error_code, - response_parameters, - .. - } => { - if let Some(params) = response_parameters { - match params { - ResponseParameters::RetryAfter(i) => { - Err(RequestError::RetryAfter(i)) - } - ResponseParameters::MigrateToChatId(to) => { - Err(RequestError::MigrateToChatId(to)) - } - } - } else { - Err(RequestError::ApiError { - description, - status_code: StatusCode::from_u16(error_code).unwrap(), - }) - } - } - } - } -} - #[cfg(test)] mod tests { use super::*; diff --git a/src/network/request.rs b/src/network/request.rs new file mode 100644 index 00000000..90e10c21 --- /dev/null +++ b/src/network/request.rs @@ -0,0 +1,60 @@ +use serde::{de::DeserializeOwned, Serialize}; +use reqwest::r#async::{ + Client, multipart::Form, Response, +}; +use apply::Apply; + +use crate::{ + RequestError, + requests::ResponseResult, + network::{method_url, TELEGRAM_API_URL}, +}; + + +pub async fn request_multipart( + client: &Client, + token: &str, + method_name: &str, + params: Option, +) -> ResponseResult where T: DeserializeOwned { + process_response( + client + .post(&method_url(TELEGRAM_API_URL, token, method_name)) + .apply(|request_builder| match params { + Some(params) => request_builder.multipart(params), + None => request_builder, + }) + .send() + .await + .map_err(RequestError::NetworkError)?, + ) + .await +} + +pub async fn request_json( + client: &Client, + token: &str, + method_name: &str, + params: &P, +) -> ResponseResult { + process_response( + client + .post(&method_url(TELEGRAM_API_URL, token, method_name)) + .json(params) + .send() + .await + .map_err(RequestError::NetworkError)?, + ) + .await +} + +async fn process_response( + mut response: Response, +) -> ResponseResult { + let response = serde_json::from_str::>( + &response.text().await.map_err(RequestError::NetworkError)?, + ) + .map_err(RequestError::InvalidJson)?; + + response.into() +} diff --git a/src/network/telegram_response.rs b/src/network/telegram_response.rs new file mode 100644 index 00000000..b6ac81c8 --- /dev/null +++ b/src/network/telegram_response.rs @@ -0,0 +1,56 @@ +use reqwest::StatusCode; + +use crate::{ + requests::ResponseResult, types::ResponseParameters, RequestError, +}; + +#[derive(Deserialize)] +#[serde(untagged)] +enum TelegramResponse { + Ok { + /// A dummy field. Used only for deserialization. + #[allow(dead_code)] + ok: bool, // TODO: True type + + result: R, + }, + Err { + /// A dummy field. Used only for deserialization. + #[allow(dead_code)] + ok: bool, // TODO: False type + + description: String, + error_code: u16, + response_parameters: Option, + }, +} + +impl Into> for TelegramResponse { + fn into(self) -> Result { + match self { + TelegramResponse::Ok { result, .. } => Ok(result), + TelegramResponse::Err { + description, + error_code, + response_parameters, + .. + } => { + if let Some(params) = response_parameters { + match params { + ResponseParameters::RetryAfter(i) => { + Err(RequestError::RetryAfter(i)) + } + ResponseParameters::MigrateToChatId(to) => { + Err(RequestError::MigrateToChatId(to)) + } + } + } else { + Err(RequestError::ApiError { + description, + status_code: StatusCode::from_u16(error_code).unwrap(), + }) + } + } + } + } +}