#[cfg(not(feature = "multi-thread"))]
use std::cell::RefCell;
#[cfg(not(feature = "multi-thread"))]
use std::rc::Rc;
#[cfg(feature = "multi-thread")]
use std::sync::Arc;
use derive_builder::Builder;
#[cfg(feature = "multi-thread")]
use futures::lock::Mutex;
use mangadex_api_schema::{Endpoint, FromResponse, UrlSerdeQS};
use mangadex_api_types::error::Error;
use reqwest::Client;
use serde::de::DeserializeOwned;
use url::Url;
use crate::v5::AuthTokens;
use crate::{Result, API_URL};
#[cfg(not(feature = "multi-thread"))]
pub type HttpClientRef = Rc<RefCell<HttpClient>>;
#[cfg(feature = "multi-thread")]
pub type HttpClientRef = Arc<Mutex<HttpClient>>;
#[derive(Debug, Builder, Clone)]
#[builder(setter(into, strip_option), default)]
pub struct HttpClient {
pub client: Client,
pub base_url: Url,
auth_tokens: Option<AuthTokens>,
captcha: Option<String>,
}
impl Default for HttpClient {
fn default() -> Self {
Self {
client: Client::new(),
base_url: Url::parse(API_URL).expect("error parsing the base url"),
auth_tokens: None,
captcha: None,
}
}
}
impl HttpClient {
pub fn new(client: Client) -> Self {
Self {
client,
..Default::default()
}
}
pub fn builder() -> HttpClientBuilder {
HttpClientBuilder::default()
}
pub(crate) async fn send_request_without_deserializing<E>(
&self,
endpoint: &E,
) -> Result<reqwest::Response>
where
E: Endpoint,
{
let mut endpoint_url = self.base_url.join(&endpoint.path())?;
if let Some(query) = endpoint.query() {
endpoint_url = endpoint_url.query_qs(query);
}
let mut req = self.client.request(endpoint.method(), endpoint_url);
if let Some(body) = endpoint.body() {
req = req.json(body);
}
if let Some(multipart) = endpoint.multipart() {
req = req.multipart(multipart);
}
if let Some(tokens) = self.get_tokens() {
req = req.bearer_auth(&tokens.session)
} else if endpoint.require_auth() {
return Err(Error::MissingTokens);
}
if let Some(captcha) = self.get_captcha() {
req = req.header("X-Captcha-Result", captcha);
}
Ok(req.send().await?)
}
pub(crate) async fn send_request<E>(&self, endpoint: &E) -> Result<E::Response>
where
E: Endpoint,
<<E as Endpoint>::Response as FromResponse>::Response: DeserializeOwned,
{
let res = self.send_request_without_deserializing(endpoint).await?;
let status_code = res.status();
if status_code.is_server_error() {
return Err(Error::ServerError(status_code.as_u16(), res.text().await?));
}
let res = res
.json::<<E::Response as FromResponse>::Response>()
.await?;
Ok(FromResponse::from_response(res))
}
pub fn get_tokens(&self) -> Option<&AuthTokens> {
self.auth_tokens.as_ref()
}
pub fn set_auth_tokens(&mut self, auth_tokens: &AuthTokens) {
self.auth_tokens = Some(auth_tokens.clone());
}
pub fn clear_auth_tokens(&mut self) {
self.auth_tokens = None;
}
pub fn get_captcha(&self) -> Option<&String> {
self.captcha.as_ref()
}
pub fn set_captcha<T: Into<String>>(&mut self, captcha: T) {
self.captcha = Some(captcha.into());
}
pub fn clear_captcha(&mut self) {
self.captcha = None;
}
}
macro_rules! endpoint {
{
$method:ident $path:tt,
#[$payload:ident $($auth:ident)?] $typ:ty,
$(#[$out_res:ident])? $out:ty
} => {
impl mangadex_api_schema::Endpoint for $typ {
type Response = $out;
fn method(&self) -> reqwest::Method {
reqwest::Method::$method
}
endpoint! { @path $path }
endpoint! { @payload $payload }
$(endpoint! { @$auth })?
}
endpoint! { @send $(:$out_res)?, $typ, $out }
};
{ @path ($path:expr, $($arg:ident),+) } => {
fn path(&self) -> std::borrow::Cow<str> {
std::borrow::Cow::Owned(format!($path, $(self.$arg),+))
}
};
{ @path $path:expr } => {
fn path(&self) -> std::borrow::Cow<str> {
std::borrow::Cow::Borrowed($path)
}
};
{ @payload query } => {
type Query = Self;
type Body = ();
fn query(&self) -> Option<&Self::Query> {
Some(&self)
}
};
{ @payload body } => {
type Query = ();
type Body = Self;
fn body(&self) -> Option<&Self::Body> {
Some(&self)
}
};
{ @payload no_data } => {
type Query = ();
type Body = ();
};
{ @auth } => {
fn require_auth(&self) -> bool {
true
}
};
{ @send, $typ:ty, $out:ty } => {
impl $typ {
pub async fn send(&self) -> $crate::Result<$out> {
#[cfg(not(feature = "multi-thread"))]
{
self.http_client.borrow().send_request(self).await
}
#[cfg(feature = "multi-thread")]
{
self.http_client.lock().await.send_request(self).await
}
}
}
};
{ @send:flatten_result, $typ:ty, $out:ty } => {
impl $typ {
pub async fn send(&self) -> $out {
#[cfg(not(feature = "multi-thread"))]
{
self.http_client.borrow().send_request(self).await?
}
#[cfg(feature = "multi-thread")]
{
self.http_client.lock().await.send_request(self).await?
}
}
}
};
{ @send:discard_result, $typ:ty, $out:ty } => {
impl $typ {
pub async fn send(&self) -> $crate::Result<()> {
#[cfg(not(feature = "multi-thread"))]
self.http_client.borrow().send_request(self).await??;
#[cfg(feature = "multi-thread")]
self.http_client.lock().await.send_request(self).await??;
Ok(())
}
}
};
{ @send:no_send, $typ:ty, $out:ty } => { };
}