1use std::sync::Arc;
2
3use derive_builder::Builder;
4use mangadex_api_schema::v5::oauth::ClientInfo;
5use mangadex_api_schema::ApiResult;
6use reqwest::{Client, Response, StatusCode};
7use serde::de::DeserializeOwned;
8use tokio::sync::RwLock;
9use url::Url;
10
11use crate::error::Error;
12use crate::rate_limit::Limited;
13use crate::v5::AuthTokens;
14use crate::{
15 traits::{Endpoint, FromResponse, UrlSerdeQS},
16 Result,
17};
18use crate::{API_DEV_URL, API_URL};
19
20pub type HttpClientRef = Arc<RwLock<HttpClient>>;
21
22#[derive(Debug, Builder, Clone)]
23#[builder(
24 setter(into, strip_option),
25 default,
26 build_fn(error = "crate::error::BuilderError")
27)]
28pub struct HttpClient {
29 pub client: Client,
30 pub base_url: Url,
31 auth_tokens: Option<AuthTokens>,
32 captcha: Option<String>,
33 #[cfg(feature = "oauth")]
34 client_info: Option<ClientInfo>,
35}
36
37impl Default for HttpClient {
38 fn default() -> Self {
39 Self {
40 client: crate::get_default_client_api(),
41 base_url: Url::parse(API_URL).expect("error parsing the base url"),
42 auth_tokens: None,
43 captcha: None,
44 #[cfg(feature = "oauth")]
45 client_info: None,
46 }
47 }
48}
49
50impl HttpClient {
51 pub fn new(client: Client) -> Self {
53 Self {
54 client,
55 base_url: Url::parse(API_URL).expect("error parsing the base url"),
56 ..Default::default()
57 }
58 }
59
60 pub fn builder() -> HttpClientBuilder {
79 HttpClientBuilder::default()
80 .client(crate::get_default_client_api())
81 .base_url(Url::parse(API_URL).expect("error parsing the base url"))
82 .clone()
83 }
84
85 pub(crate) async fn send_request_without_deserializing_with_other_base_url<E>(
90 &self,
91 endpoint: &E,
92 base_url: &url::Url,
93 ) -> Result<reqwest::Response>
94 where
95 E: Endpoint,
96 {
97 let mut endpoint_url = base_url.join(&endpoint.path())?;
98 if let Some(query) = endpoint.query() {
99 endpoint_url = endpoint_url.query_qs(query);
100 }
101
102 let mut req = self.client.request(endpoint.method(), endpoint_url);
103
104 if let Some(body) = endpoint.body() {
105 req = req.json(body);
106 }
107
108 if let Some(multipart) = endpoint.multipart() {
109 req = req.multipart(multipart);
110 }
111 if endpoint.require_auth() {
112 let tokens = self.get_tokens().ok_or(Error::MissingTokens)?;
113 req = req.bearer_auth(&tokens.session);
114 }
115 if let Some(captcha) = self.get_captcha() {
116 req = req.header("X-Captcha-Result", captcha);
117 }
118
119 Ok(req.send().await?)
120 }
121
122 pub(crate) async fn send_request_without_deserializing<E>(
127 &self,
128 endpoint: &E,
129 ) -> Result<reqwest::Response>
130 where
131 E: Endpoint,
132 {
133 self.send_request_without_deserializing_with_other_base_url(endpoint, &self.base_url)
134 .await
135 }
136
137 pub(crate) async fn send_request_with_checks<E>(
138 &self,
139 endpoint: &E,
140 ) -> Result<reqwest::Response>
141 where
142 E: Endpoint,
143 {
144 let res = self.send_request_without_deserializing(endpoint).await?;
145
146 let status_code = res.status();
147
148 if status_code.as_u16() == 429 {
149 return Err(Error::RateLimitExcedeed);
150 }
151 if status_code == StatusCode::SERVICE_UNAVAILABLE {
152 return Err(Error::ServiceUnavailable(res.text().await.ok()));
153 }
154 if status_code.is_server_error() {
155 return Err(Error::ServerError(status_code.as_u16(), res.text().await?));
156 }
157 Ok(res)
158 }
159
160 pub(crate) async fn handle_result<T>(&self, res: Response) -> Result<T>
161 where
162 T: DeserializeOwned,
163 {
164 Ok(res.json::<ApiResult<T>>().await?.into_result()?)
171 }
172
173 pub(crate) async fn send_request<E>(&self, endpoint: &E) -> Result<E::Response>
175 where
176 E: Endpoint,
177 <<E as Endpoint>::Response as FromResponse>::Response: DeserializeOwned,
178 {
179 let res = self.send_request_with_checks(endpoint).await?;
180
181 let res = res
182 .json::<<E::Response as FromResponse>::Response>()
183 .await?;
184
185 Ok(FromResponse::from_response(res))
186 }
187
188 pub(crate) async fn send_request_with_rate_limit<E>(
190 &self,
191 endpoint: &E,
192 ) -> Result<Limited<E::Response>>
193 where
194 E: Endpoint,
195 <<E as Endpoint>::Response as FromResponse>::Response: DeserializeOwned,
196 {
197 use crate::rate_limit::RateLimit;
198
199 let resp = self.send_request_with_checks(endpoint).await?;
200
201 let some_rate_limit = <RateLimit as TryFrom<&Response>>::try_from(&resp);
202
203 let res = self
204 .handle_result::<<E::Response as FromResponse>::Response>(resp)
205 .await?;
206
207 Ok(Limited {
208 rate_limit: some_rate_limit?,
209 body: FromResponse::from_response(res),
210 })
211 }
212
213 pub fn get_tokens(&self) -> Option<&AuthTokens> {
215 self.auth_tokens.as_ref()
216 }
217
218 pub fn set_auth_tokens(&mut self, auth_tokens: &AuthTokens) {
220 self.auth_tokens = Some(auth_tokens.clone());
221 }
222
223 pub fn clear_auth_tokens(&mut self) {
228 self.auth_tokens = None;
229 }
230
231 pub fn get_captcha(&self) -> Option<&String> {
233 self.captcha.as_ref()
234 }
235
236 pub fn set_captcha<T: Into<String>>(&mut self, captcha: T) {
242 self.captcha = Some(captcha.into());
243 }
244
245 pub fn clear_captcha(&mut self) {
247 self.captcha = None;
248 }
249
250 #[cfg(feature = "oauth")]
251 #[cfg_attr(docsrs, doc(cfg(feature = "oauth")))]
252 pub fn set_client_info(&mut self, client_info: &ClientInfo) {
253 self.client_info = Some(client_info.clone());
254 }
255
256 #[cfg(feature = "oauth")]
257 #[cfg_attr(docsrs, doc(cfg(feature = "oauth")))]
258 pub fn get_client_info(&self) -> Option<&ClientInfo> {
259 self.client_info.as_ref()
260 }
261
262 #[cfg(feature = "oauth")]
263 #[cfg_attr(docsrs, doc(cfg(feature = "oauth")))]
264 pub fn clear_client_info(&mut self) {
265 self.client_info = None;
266 }
267
268 pub fn api_dev_client() -> Self {
269 Self {
270 client: Client::new(),
271 base_url: Url::parse(API_DEV_URL).expect("error parsing the base url"),
272 auth_tokens: None,
273 captcha: None,
274 #[cfg(feature = "oauth")]
275 client_info: None,
276 }
277 }
278}
279
280macro_rules! builder_send {
286 {
287 #[$builder:ident] $typ:ty,
288 $(#[$out_res:ident])? $out_type:ty
289 } => {
290 builder_send! { @send $(:$out_res)?, $typ, $out_type }
291 };
292 { @send, $typ:ty, $out_type:ty } => {
293 impl $typ {
294 pub async fn send(&self) -> crate::Result<$out_type>{
295 self.build()?.send().await
296 }
297 }
298 };
299 { @send:discard_result, $typ:ty, $out_type:ty } => {
300 impl $typ {
301 pub async fn send(&self) -> crate::Result<()>{
302 self.build()?.send().await?;
303 Ok(())
304 }
305 }
306 };
307 { @send:flatten_result, $typ:ty, $out_type:ty } => {
308 impl $typ {
309 pub async fn send(&self) -> $out_type{
310 self.build()?.send().await
311 }
312 }
313 };
314 { @send:rate_limited, $typ:ty, $out_type:ty } => {
315 impl $typ {
316
317 pub async fn send(&self) -> crate::Result<crate::rate_limit::Limited<$out_type>>{
318 self.build()?.send().await
319 }
320 }
321 };
322 { @send:no_send, $typ:ty, $out_type:ty } => {
323 impl $typ {
324 pub async fn send(&self) -> $out_type{
325 self.build()?.send().await
326 }
327 }
328 };
329}
330
331macro_rules! endpoint {
393 {
394 $method:ident $path:tt,
395 #[$payload:ident $($auth:ident $(=> $field:ident)?)?] $typ:ty,
396 $(#[$out_res:ident])? $out:ty
397 $(,$builder_ty:ty)?
398 } => {
399 impl crate::traits::Endpoint for $typ {
400 type Response = $out;
402
403 fn method(&self) -> reqwest::Method {
405 reqwest::Method::$method
406 }
407
408 endpoint! { @path $path }
409 endpoint! { @payload $payload }
410 $(endpoint! { @$auth $(, $field)? })?
412 }
413
414 endpoint! { @send $(:$out_res)?, $typ, $out $(,$builder_ty)? }
415
416 };
417
418 { @path ($path:expr, $($arg:ident),+) } => {
419 fn path(&'_ self) -> std::borrow::Cow<'_, str> {
421 std::borrow::Cow::Owned(format!($path, $(self.$arg),+))
422 }
423 };
424 { @path $path:expr } => {
425 fn path(&'_ self) -> std::borrow::Cow<'_, str> {
427 std::borrow::Cow::Borrowed($path)
428 }
429 };
430
431 { @payload query } => {
433 type Query = Self;
434 type Body = ();
435
436 fn query(&self) -> Option<&Self::Query> {
438 Some(&self)
439 }
440 };
441 { @payload body } => {
443 type Query = ();
444 type Body = Self;
445
446 fn body(&self) -> Option<&Self::Body> {
448 Some(&self)
449 }
450 };
451 { @payload no_data } => {
453 type Query = ();
454 type Body = ();
455 };
456
457 { @auth } => {
458 fn require_auth(&self) -> bool {
460 true
461 }
462 };
463
464 { @auth, $field:ident } => {
465 fn require_auth(&self) -> bool {
467 self.$field
468 }
469 };
470
471 { @send, $typ:ty, $out:ty $(,$builder_ty:ty)? } => {
473 impl $typ {
474 pub async fn send(&self) -> crate::Result<$out> {
476 #[cfg(all(not(feature = "multi-thread"), not(feature = "tokio-multi-thread"), not(feature = "rw-multi-thread")))]
477 {
478 self.http_client.try_borrow()?.send_request(self).await
479 }
480 #[cfg(any(feature = "multi-thread", feature = "tokio-multi-thread"))]
481 {
482 self.http_client.lock().await.send_request(self).await
483 }
484 #[cfg(feature = "rw-multi-thread")]
485 {
486 self.http_client.read().await.send_request(self).await
487 }
488 }
489 }
490
491 $(
492 builder_send! {
493 #[builder] $builder_ty,
494 $out
495 }
496 )?
497 };
498 { @send:rate_limited, $typ:ty, $out:ty $(,$builder_ty:ty)? } => {
500 impl $typ {
501 pub async fn send(&self) -> crate::Result<crate::rate_limit::Limited<$out>> {
503
504 self.http_client.read().await.send_request_with_rate_limit(self).await
505
506 }
507 }
508
509 $(
510 builder_send! {
511 #[builder] $builder_ty,
512 #[rate_limited] $out
513 }
514 )?
515 };
516 { @send:flatten_result, $typ:ty, $out:ty $(,$builder_ty:ty)? } => {
518 impl $typ {
519 #[allow(dead_code)]
521 pub async fn send(&self) -> $out {
522 self.http_client.read().await.send_request(self).await?
523 }
524 }
525
526 $(
527 builder_send! {
528 #[builder] $builder_ty,
529 #[flatten_result] $out
530 }
531 )?
532 };
533 { @send:discard_result, $typ:ty, $out:ty $(,$builder_ty:ty)? } => {
535 impl $typ {
536 #[allow(dead_code)]
538 pub async fn send(&self) -> crate::Result<()> {
539 self.http_client.read().await.send_request(self).await??;
540 Ok(())
541 }
542 }
543
544 $(
545 builder_send! {
546 #[builder] $builder_ty,
547 #[discard_result] $out
548 }
549 )?
550 };
551 { @send:no_send, $typ:ty, $out:ty $(,$builder_ty:ty)? } => {
553 $(
554 builder_send! {
555 #[builder] $builder_ty,
556 #[no_send] $out
557 }
558 )?
559 };
560
561}
562
563macro_rules! create_endpoint_node {
564 {
565 #[$name:ident] $sname:ident $tname:ident,
566 #[$args:ident] {$($arg_name:ident: $arg_ty:ty,)+},
567 #[$methods:ident] {$($func:ident($($farg_name:ident: $farg_ty:ty,)*) -> $output:ty;)*}
568 } => {
569 #[derive(Debug)]
570 pub struct $sname {
571 $( $arg_name: $arg_ty, )+
572 }
573 trait $tname {
574 $(
575 fn $func(&self, $( $farg_name: $farg_ty, )*) -> $output;
576 )*
577 }
578 impl $sname {
579 pub fn new($( $arg_name: $arg_ty, )+) -> Self {
580 Self {
581 $( $arg_name, )+
582 }
583 }
584 $(
585 pub fn $func(&self, $( $farg_name: $farg_ty, )*) -> $output {
586 <Self as $tname>::$func(&self, $( $farg_name,)*)
587 }
588 )*
589 }
590 $(
591 impl From<&$sname> for $arg_ty {
592 fn from(value: &$sname) -> Self {
593 value.$arg_name.clone()
594 }
595 }
596 )+
597 }
598}