Split network into different files

This commit is contained in:
Waffle 2019-09-21 00:56:25 +03:00
parent 5284d5e884
commit f37552536e
4 changed files with 169 additions and 138 deletions

39
src/network/download.rs Normal file
View file

@ -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<D>(
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<impl Stream<Item = Result<Chunk, reqwest::Error>>, 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())
}

View file

@ -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<T: DeserializeOwned>(
client: &Client,
token: &str,
method_name: &str,
params: Option<Form>,
) -> ResponseResult<T> {
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<T: DeserializeOwned, P: Serialize>(
client: &Client,
token: &str,
method_name: &str,
params: &P,
) -> ResponseResult<T> {
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<T: DeserializeOwned>(
mut response: Response,
) -> ResponseResult<T> {
let response = serde_json::from_str::<TelegramResponse<T>>(
&response.text().await.map_err(RequestError::NetworkError)?,
)
.map_err(RequestError::InvalidJson)?;
response.into()
}
#[derive(Deserialize)]
#[serde(untagged)]
enum TelegramResponse<R> {
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<ResponseParameters>,
},
}
pub async fn download_file<D>(
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<impl Stream<Item = Result<Chunk, reqwest::Error>>, 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<R> Into<ResponseResult<R>> for TelegramResponse<R> {
fn into(self) -> Result<R, RequestError> {
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::*;

60
src/network/request.rs Normal file
View file

@ -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<T>(
client: &Client,
token: &str,
method_name: &str,
params: Option<Form>,
) -> ResponseResult<T> 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<T: DeserializeOwned, P: Serialize>(
client: &Client,
token: &str,
method_name: &str,
params: &P,
) -> ResponseResult<T> {
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<T: DeserializeOwned>(
mut response: Response,
) -> ResponseResult<T> {
let response = serde_json::from_str::<TelegramResponse<T>>(
&response.text().await.map_err(RequestError::NetworkError)?,
)
.map_err(RequestError::InvalidJson)?;
response.into()
}

View file

@ -0,0 +1,56 @@
use reqwest::StatusCode;
use crate::{
requests::ResponseResult, types::ResponseParameters, RequestError,
};
#[derive(Deserialize)]
#[serde(untagged)]
enum TelegramResponse<R> {
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<ResponseParameters>,
},
}
impl<R> Into<ResponseResult<R>> for TelegramResponse<R> {
fn into(self) -> Result<R, RequestError> {
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(),
})
}
}
}
}
}