use std::{fmt::Debug, path::PathBuf};
use bon::Builder;
use compio::fs::File;
use cyper::{
multipart::{Form, Part},
Client, Error as CyperError, Response,
};
use serde::{de::DeserializeOwned, Serialize};
use super::{json::encode_object, trait_async::AsyncTelegramApi, Error, BASE_API_URL};
#[derive(Debug, Clone, Builder)]
#[must_use = "Bot needs to be used in order to be useful"]
pub struct Bot {
#[builder(into)]
pub api_url: String,
#[builder(default = Client::new().expect("Failed to create HTTP client"))]
pub client: Client,
}
impl Bot {
pub fn new(api_key: &str) -> Self {
Self::new_url(format!("{BASE_API_URL}{api_key}"))
}
pub fn new_url<S: Into<String>>(api_url: S) -> Self {
Self::builder().api_url(api_url).build()
}
async fn decode_response<Output>(response: Response) -> Result<Output, Error>
where
Output: DeserializeOwned,
{
let success = response.status().is_success();
if success {
Ok(response.json().await?)
} else {
Err(Error::Api(response.json().await?))
}
}
}
impl From<CyperError> for Error {
fn from(error: CyperError) -> Self {
Self::HttpCyper(error)
}
}
impl AsyncTelegramApi for Bot {
type Error = Error;
async fn request<Params, Output>(
&self,
method: &str,
params: Option<Params>,
) -> Result<Output, Self::Error>
where
Params: Serialize + Debug + Send,
Output: DeserializeOwned,
{
let url = format!("{}/{method}", self.api_url);
let mut prepared_request = self
.client
.post(url)?
.header("Content-Type", "application/json")?;
if let Some(params) = params {
prepared_request = prepared_request.json(¶ms)?;
}
let response = prepared_request.send().await?;
Self::decode_response(response).await
}
async fn request_with_form_data<Params, Output>(
&self,
method: &str,
params: Params,
files: Vec<(&str, PathBuf)>,
) -> Result<Output, Self::Error>
where
Params: Serialize + Debug + Send,
Output: DeserializeOwned,
{
let json_struct = encode_object(¶ms)?;
let file_keys: Vec<&str> = files.iter().map(|(key, _)| *key).collect();
let mut form = Form::new();
for (key, val) in json_struct {
if !file_keys.contains(&key.as_str()) {
form = match val {
serde_json::Value::String(val) => form.text(key, val),
other => form.text(key, other.to_string()),
};
}
}
for (parameter_name, file_path) in files {
let file = File::open(&file_path).await.map_err(Error::ReadFile)?;
let file_name = file_path.file_name().unwrap().to_string_lossy().to_string();
let part = Part::stream(file).file_name(file_name);
form = form.part(parameter_name.to_owned(), part);
}
let url = format!("{}/{method}", self.api_url);
let response = self.client.post(url)?.multipart(form)?.send().await?;
Self::decode_response(response).await
}
}