1use super::{error::MangaApiError, requests::GetUserMangaList, responses::MangaListStatus};
2use async_trait::async_trait;
3use oauth2::{AccessToken, ClientId};
4use serde::{de::DeserializeOwned, Serialize};
5use std::marker::PhantomData;
6
7use crate::{
8 common::{struct_to_form_data, PagingIter},
9 manga::requests::{DeleteMyMangaListItem, UpdateMyMangaListStatus},
10 oauth::{Authenticated, MalClientId, OauthClient},
11 MANGA_URL, USER_URL,
12};
13
14use super::{
15 requests::{GetMangaDetails, GetMangaList, GetMangaRanking},
16 responses::{MangaDetails, MangaList, MangaRanking},
17};
18use reqwest;
19
20#[doc(hidden)]
21#[derive(Debug)]
22pub struct Client {}
23
24#[doc(hidden)]
25#[derive(Debug)]
26pub struct Oauth {}
27
28#[doc(hidden)]
29#[derive(Debug)]
30pub struct None {}
31
32#[derive(Debug, Clone)]
91pub struct MangaApiClient<State = None> {
92 client: reqwest::Client,
93 client_id: Option<String>,
94 access_token: Option<String>,
95 state: PhantomData<State>,
96}
97
98impl From<&AccessToken> for MangaApiClient<Oauth> {
99 fn from(value: &AccessToken) -> Self {
100 MangaApiClient::<Oauth> {
101 client: reqwest::Client::new(),
102 client_id: None,
103 access_token: Some(value.secret().clone()),
104 state: PhantomData::<Oauth>,
105 }
106 }
107}
108
109impl From<&ClientId> for MangaApiClient<Client> {
110 fn from(value: &ClientId) -> Self {
111 MangaApiClient::<Client> {
112 client: reqwest::Client::new(),
113 client_id: Some(value.clone().to_string()),
114 access_token: None,
115 state: PhantomData::<Client>,
116 }
117 }
118}
119
120impl From<&MalClientId> for MangaApiClient<Client> {
121 fn from(value: &MalClientId) -> Self {
122 MangaApiClient::<Client> {
123 client: reqwest::Client::new(),
124 client_id: Some(value.0.to_string()),
125 access_token: None,
126 state: PhantomData::<Client>,
127 }
128 }
129}
130
131impl From<&OauthClient<Authenticated>> for MangaApiClient<Oauth> {
132 fn from(value: &OauthClient<Authenticated>) -> Self {
133 MangaApiClient {
134 client: reqwest::Client::new(),
135 client_id: None,
136 access_token: Some(value.get_access_token().secret().clone()),
137 state: PhantomData::<Oauth>,
138 }
139 }
140}
141
142#[async_trait]
145pub trait Request {
146 async fn get<T>(&self, query: &T) -> Result<String, MangaApiError>
147 where
148 T: Serialize + Send + Sync;
149
150 async fn get_details(&self, query: &GetMangaDetails) -> Result<String, MangaApiError>;
151
152 async fn get_ranking(&self, query: &GetMangaRanking) -> Result<String, MangaApiError>;
153
154 async fn get_user(&self, query: &GetUserMangaList) -> Result<String, MangaApiError>;
155
156 async fn get_next_or_prev(&self, query: Option<&String>) -> Result<String, MangaApiError>;
157}
158
159#[async_trait]
160impl Request for MangaApiClient<Client> {
161 async fn get<T>(&self, query: &T) -> Result<String, MangaApiError>
162 where
163 T: Serialize + Send + Sync,
164 {
165 let response = self
166 .client
167 .get(MANGA_URL)
168 .header("X-MAL-CLIENT-ID", self.client_id.as_ref().unwrap())
169 .query(&query)
170 .send()
171 .await
172 .map_err(|err| MangaApiError::new(format!("Failed get request: {}", err)))?;
173
174 handle_response(response).await
175 }
176
177 async fn get_details(&self, query: &GetMangaDetails) -> Result<String, MangaApiError> {
178 let response = self
179 .client
180 .get(format!("{}/{}", MANGA_URL, query.manga_id))
181 .header("X-MAL-CLIENT-ID", self.client_id.as_ref().unwrap())
182 .query(&query)
183 .send()
184 .await
185 .map_err(|err| MangaApiError::new(format!("Failed get request: {}", err)))?;
186
187 handle_response(response).await
188 }
189
190 async fn get_ranking(&self, query: &GetMangaRanking) -> Result<String, MangaApiError> {
191 let response = self
192 .client
193 .get(format!("{}/ranking", MANGA_URL))
194 .header("X-MAL-CLIENT-ID", self.client_id.as_ref().unwrap())
195 .query(&query)
196 .send()
197 .await
198 .map_err(|err| MangaApiError::new(format!("Failed get request: {}", err)))?;
199
200 handle_response(response).await
201 }
202
203 async fn get_user(&self, query: &GetUserMangaList) -> Result<String, MangaApiError> {
204 let response = self
205 .client
206 .get(format!("{}/{}/mangalist", USER_URL, query.user_name))
207 .header("X-MAL-CLIENT-ID", self.client_id.as_ref().unwrap())
208 .query(&query)
209 .send()
210 .await
211 .map_err(|err| MangaApiError::new(format!("Failed get request: {}", err)))?;
212
213 handle_response(response).await
214 }
215
216 async fn get_next_or_prev(&self, query: Option<&String>) -> Result<String, MangaApiError> {
217 if let Some(itr) = query {
218 let response = self
219 .client
220 .get(itr)
221 .header("X-MAL-CLIENT-ID", self.client_id.as_ref().unwrap())
222 .send()
223 .await
224 .map_err(|err| MangaApiError::new(format!("Failed get request: {}", err)))?;
225
226 handle_response(response).await
227 } else {
228 Err(MangaApiError::new("Page does not exist".to_string()))
229 }
230 }
231}
232
233#[async_trait]
234impl Request for MangaApiClient<Oauth> {
235 async fn get<T>(&self, query: &T) -> Result<String, MangaApiError>
236 where
237 T: Serialize + std::marker::Send + std::marker::Sync,
238 {
239 let response = self
240 .client
241 .get(MANGA_URL)
242 .bearer_auth(self.access_token.as_ref().unwrap())
243 .query(&query)
244 .send()
245 .await
246 .map_err(|err| MangaApiError::new(format!("Failed get request: {}", err)))?;
247
248 handle_response(response).await
249 }
250
251 async fn get_details(&self, query: &GetMangaDetails) -> Result<String, MangaApiError> {
252 let response = self
253 .client
254 .get(format!("{}/{}", MANGA_URL, query.manga_id))
255 .bearer_auth(self.access_token.as_ref().unwrap())
256 .query(&query)
257 .send()
258 .await
259 .map_err(|err| MangaApiError::new(format!("Failed get request: {}", err)))?;
260
261 handle_response(response).await
262 }
263
264 async fn get_ranking(&self, query: &GetMangaRanking) -> Result<String, MangaApiError> {
265 let response = self
266 .client
267 .get(format!("{}/ranking", MANGA_URL))
268 .bearer_auth(self.access_token.as_ref().unwrap())
269 .query(&query)
270 .send()
271 .await
272 .map_err(|err| MangaApiError::new(format!("Failed get request: {}", err)))?;
273
274 handle_response(response).await
275 }
276
277 async fn get_user(&self, query: &GetUserMangaList) -> Result<String, MangaApiError> {
278 let response = self
279 .client
280 .get(format!("{}/{}/mangalist", USER_URL, query.user_name))
281 .bearer_auth(self.access_token.as_ref().unwrap())
282 .query(&query)
283 .send()
284 .await
285 .map_err(|err| MangaApiError::new(format!("Failed get request: {}", err)))?;
286
287 handle_response(response).await
288 }
289
290 async fn get_next_or_prev(&self, query: Option<&String>) -> Result<String, MangaApiError> {
291 if let Some(itr) = query {
292 let response = self
293 .client
294 .get(itr)
295 .bearer_auth(self.access_token.as_ref().unwrap())
296 .send()
297 .await
298 .map_err(|err| MangaApiError::new(format!("Failed get request: {}", err)))?;
299
300 handle_response(response).await
301 } else {
302 Err(MangaApiError::new("Page does not exist".to_string()))
303 }
304 }
305}
306
307#[async_trait]
311pub trait MangaApi {
312 type State: Request + Send + Sync;
313
314 async fn get_manga_list(&self, query: &GetMangaList) -> Result<MangaList, MangaApiError> {
318 let response = self.get_self().get(query).await?;
319 let result: MangaList = serde_json::from_str(response.as_str()).map_err(|err| {
320 MangaApiError::new(format!("Failed to parse MangaList result: {}", err))
321 })?;
322 Ok(result)
323 }
324
325 async fn get_manga_details(
329 &self,
330 query: &GetMangaDetails,
331 ) -> Result<MangaDetails, MangaApiError> {
332 let response = self.get_self().get_details(query).await?;
333 let result: MangaDetails = serde_json::from_str(response.as_str()).map_err(|err| {
334 MangaApiError::new(format!("Failed to parse MangaList result: {}", err))
335 })?;
336 Ok(result)
337 }
338
339 async fn get_manga_ranking(
343 &self,
344 query: &GetMangaRanking,
345 ) -> Result<MangaRanking, MangaApiError> {
346 let response = self.get_self().get_ranking(query).await?;
347 let result: MangaRanking = serde_json::from_str(response.as_str()).map_err(|err| {
348 MangaApiError::new(format!("Failed to parse MangaList result: {}", err))
349 })?;
350 Ok(result)
351 }
352
353 async fn get_user_manga_list(
359 &self,
360 query: &GetUserMangaList,
361 ) -> Result<MangaList, MangaApiError> {
362 if query.user_name == "@me".to_string() {
363 return Err(MangaApiError::new(
364 "You can only get your list via an Oauth client".to_string(),
365 ));
366 }
367 let response = self.get_self().get_user(query).await?;
368 let result: MangaList = serde_json::from_str(response.as_str()).map_err(|err| {
369 MangaApiError::new(format!("Failed to parse Anime List result: {}", err))
370 })?;
371 Ok(result)
372 }
373
374 async fn next<T>(&self, response: &T) -> Result<T, MangaApiError>
376 where
377 T: DeserializeOwned + PagingIter + Sync + Send,
378 {
379 let response = self
380 .get_self()
381 .get_next_or_prev(response.next_page())
382 .await?;
383 let result: T = serde_json::from_str(response.as_str())
384 .map_err(|err| MangaApiError::new(format!("Failed to fetch next page: {}", err)))?;
385 Ok(result)
386 }
387
388 async fn prev<T>(&self, response: &T) -> Result<T, MangaApiError>
390 where
391 T: DeserializeOwned + PagingIter + Sync + Send,
392 {
393 let response = self
394 .get_self()
395 .get_next_or_prev(response.prev_page())
396 .await?;
397 let result: T = serde_json::from_str(response.as_str())
398 .map_err(|err| MangaApiError::new(format!("Failed to fetch next page: {}", err)))?;
399 Ok(result)
400 }
401
402 fn get_self(&self) -> &Self::State;
404}
405
406#[async_trait]
407impl MangaApi for MangaApiClient<Client> {
408 type State = MangaApiClient<Client>;
409
410 fn get_self(&self) -> &Self::State {
411 self
412 }
413}
414
415#[async_trait]
416impl MangaApi for MangaApiClient<Oauth> {
417 type State = MangaApiClient<Oauth>;
418
419 fn get_self(&self) -> &Self::State {
420 self
421 }
422
423 async fn get_user_manga_list(
429 &self,
430 query: &GetUserMangaList,
431 ) -> Result<MangaList, MangaApiError> {
432 let response = self.get_self().get_user(query).await?;
433 let result: MangaList = serde_json::from_str(response.as_str()).map_err(|err| {
434 MangaApiError::new(format!("Failed to parse Anime List result: {}", err))
435 })?;
436 Ok(result)
437 }
438}
439
440impl MangaApiClient<Oauth> {
441 pub async fn update_manga_list_status(
445 &self,
446 query: &UpdateMyMangaListStatus,
447 ) -> Result<MangaListStatus, MangaApiError> {
448 let form_data = struct_to_form_data(&query).map_err(|err| {
449 MangaApiError::new(format!("Failed to turn request into form data: {}", err))
450 })?;
451 let response = self
452 .client
453 .put(format!("{}/{}/my_list_status", MANGA_URL, query.manga_id))
454 .bearer_auth(&self.access_token.as_ref().unwrap())
455 .form(&form_data)
456 .send()
457 .await
458 .map_err(|err| MangaApiError::new(format!("Failed put request: {}", err)))?;
459
460 let response = handle_response(response).await?;
461 let result: MangaListStatus = serde_json::from_str(response.as_str()).map_err(|err| {
462 MangaApiError::new(format!("Failed to parse Anime List result: {}", err))
463 })?;
464 Ok(result)
465 }
466
467 pub async fn delete_manga_list_item(
471 &self,
472 query: &DeleteMyMangaListItem,
473 ) -> Result<(), MangaApiError> {
474 let response = self
475 .client
476 .delete(format!("{}/{}/my_list_status", MANGA_URL, query.manga_id))
477 .bearer_auth(&self.access_token.as_ref().unwrap())
478 .send()
479 .await
480 .map_err(|err| MangaApiError::new(format!("Failed delete request: {}", err)))?;
481
482 match response.status() {
483 reqwest::StatusCode::OK => Ok(()),
484 reqwest::StatusCode::NOT_FOUND => Err(MangaApiError::new(
485 "Manga does not exist in user's manga list".to_string(),
486 )),
487 _ => Err(MangaApiError::new(format!(
488 "Did not recieve expected response: {}",
489 response.status()
490 ))),
491 }
492 }
493}
494
495async fn handle_response(response: reqwest::Response) -> Result<String, MangaApiError> {
496 match response.status() {
497 reqwest::StatusCode::OK => {
498 let content = response.text().await.map_err(|err| {
499 MangaApiError::new(format!("Failed to get content from response: {}", err))
500 })?;
501 Ok(content)
502 }
503 _ => Err(MangaApiError::new(format!(
504 "Did not recieve OK response: {}",
505 response.status()
506 ))),
507 }
508}