use std::marker::PhantomData;
use crate::api::ApiResponse;
use crate::body::ToMultipart;
use crate::error::ApiError;
use crate::traits::FromBytes;
use reqwest::header::{ACCEPT, AUTHORIZATION, CONTENT_TYPE, HeaderMap, HeaderName, HeaderValue};
use reqwest::multipart::Form;
use reqwest::{Client, Method};
use serde::{Serialize, de::DeserializeOwned};
pub trait EndpointSpec {
type Req: serde::Serialize;
type Resp: serde::de::DeserializeOwned;
const PATH: &'static str;
const METHOD: Method = Method::POST;
}
pub struct Endpoint<'c, S: EndpointSpec> {
client: &'c TorboxClient,
_path: Option<String>,
_marker: PhantomData<S>,
}
impl<'c, S: EndpointSpec> Endpoint<'c, S> {
pub fn new(client: &'c TorboxClient) -> Self {
Self {
client,
_path: None,
_marker: PhantomData,
}
}
pub fn new_with_url(client: &'c TorboxClient, full_url: impl Into<String>) -> Self {
Self {
client,
_path: Some(full_url.into()),
_marker: std::marker::PhantomData,
}
}
pub async fn call_no_body(&self, url_suffix: &str) -> Result<ApiResponse<S::Resp>, ApiError>
where
S::Resp:,
<S as EndpointSpec>::Resp: std::fmt::Debug,
{
self.client.request(S::METHOD, url_suffix).await
}
pub async fn call_json(&self, body: S::Req) -> Result<ApiResponse<S::Resp>, ApiError> {
self.client
.request_with_json(S::METHOD, S::PATH, body)
.await
}
pub async fn call_query(&self, query: S::Req) -> Result<ApiResponse<S::Resp>, ApiError>
where
S::Req: Serialize,
{
self.client
.request_with_query(S::METHOD, S::PATH, &query)
.await
}
pub async fn call_multipart(&self, body: S::Req) -> Result<ApiResponse<S::Resp>, ApiError>
where
S::Req: ToMultipart + Send + Sync,
{
let form = body.to_multipart().await;
self.client
.request_multipart(S::METHOD, S::PATH, form)
.await
}
pub async fn call_query_json<Q, B>(
self,
query: Q,
body: B,
) -> Result<ApiResponse<S::Resp>, ApiError>
where
Q: Serialize,
B: Serialize,
{
let url = format!("{}/{}", self.client.base_url, S::PATH);
let req = self
.client
.client
.request(S::METHOD, &url)
.headers(self.client.headers("application/json"))
.query(&query)
.json(&body);
let res = req.send().await?;
self.client
.parse_response::<ApiResponse<S::Resp>>(res)
.await
}
pub async fn call_query_bytes(&self, query: S::Req) -> Result<Vec<u8>, ApiError>
where
S::Req: Serialize,
{
let url = format!("{}/{}", self.client.base_url, S::PATH);
let response = self
.client
.client
.request(S::METHOD, &url)
.headers(self.client.headers("application/json"))
.query(&query)
.send()
.await?;
Ok(response.bytes().await?.to_vec())
}
pub async fn call_query_raw<T>(&self, query: S::Req, content_type: &[u8]) -> Result<T, ApiError>
where
T: DeserializeOwned + FromBytes,
S::Req: Serialize,
{
let mut header_map = HeaderMap::new();
header_map.insert(
CONTENT_TYPE,
HeaderValue::from_bytes(content_type)
.expect("Wrong CONTENT_TYPE, if you are unsure refer to the developer.mozilla.org documentation or use the provided CONTENT_XML or CONTENT_JSON constants")
);
let res = self
.client
.client
.request(S::METHOD, format!("{}/{}", self.client.base_url, S::PATH))
.headers(header_map)
.query(&query)
.send()
.await?;
self.client.parse_response::<T>(res).await
}
}
#[derive(Clone)]
#[cfg_attr(feature = "specta", derive(specta::Type))]
pub struct TorboxClient {
#[cfg_attr(feature = "specta", specta(skip))]
pub client: Client,
pub(crate) token: String,
pub base_url: String,
}
impl TorboxClient {
pub fn new(token: String) -> Self {
let client = Client::builder()
.redirect(reqwest::redirect::Policy::none())
.build()
.unwrap();
Self {
client,
token,
base_url: "https://api.torbox.app/v1".to_string(),
}
}
pub fn with_base_url(&self, new_base: impl Into<String>) -> Self {
let mut new = self.clone();
new.base_url = new_base.into();
new
}
pub fn token(&self) -> &str {
&self.token
}
async fn parse_response<T>(&self, res: reqwest::Response) -> Result<T, ApiError>
where
T: DeserializeOwned + FromBytes,
{
let content_type = res
.headers()
.get(reqwest::header::CONTENT_TYPE)
.and_then(|v| v.to_str().ok())
.unwrap_or("");
if content_type.starts_with("application/json") {
let text = res.text().await?;
serde_json::from_str::<T>(&text).map_err(ApiError::from)
} else {
let bytes = res.bytes().await?.to_vec();
T::from_bytes(bytes)
}
}
fn headers(&self, _content_type: &'static str) -> HeaderMap {
let mut headers = HeaderMap::new();
headers.insert(ACCEPT, HeaderValue::from_static("application/json"));
headers.insert(
AUTHORIZATION,
HeaderValue::from_str(&format!("Bearer {}", self.token)).unwrap(),
);
headers
}
pub async fn request_multipart<T: DeserializeOwned>(
&self,
method: Method,
endpoint: &str,
form: Form,
) -> Result<T, ApiError> {
let url = format!("{}/{}", self.base_url, endpoint);
let res = self
.client
.request(method, url)
.headers(self.headers("multipart/form-data"))
.multipart(form)
.send()
.await?;
let text = res.text().await?;
let parsed = serde_json::from_str::<T>(&text)?;
Ok(parsed)
}
pub async fn request<T: DeserializeOwned + FromBytes>(
&self,
method: Method,
endpoint: &str,
) -> Result<T, ApiError> {
let res = self
.client
.request(method, format!("{}/{}", self.base_url, endpoint))
.headers(self.headers("application/json"))
.send()
.await?;
self.parse_response::<T>(res).await
}
pub async fn request_with_json<T: DeserializeOwned, B: Serialize>(
&self,
method: Method,
endpoint: &str,
body: B,
) -> Result<T, ApiError> {
let res = self
.client
.request(method, format!("{}/{}", self.base_url, endpoint))
.headers(self.headers("application/json"))
.json(&body)
.send()
.await?;
let text = res.text().await?;
let parsed = serde_json::from_str::<T>(&text)?;
Ok(parsed)
}
pub async fn request_with_query<T: DeserializeOwned, Q: Serialize>(
&self,
method: Method,
endpoint: &str,
query: &Q,
) -> Result<T, ApiError> {
let res = self
.client
.request(method, format!("{}/{}", self.base_url, endpoint))
.headers(self.headers("application/json"))
.query(query)
.send()
.await?;
let text = res.text().await?;
let parsed = serde_json::from_str::<T>(&text)?;
Ok(parsed)
}
}