1use super::{
2 error::AnimeApiError,
3 requests::{DeleteMyAnimeListItem, GetUserAnimeList, UpdateMyAnimeListStatus},
4 responses::AnimeListStatus,
5};
6use async_trait::async_trait;
7use oauth2::{AccessToken, ClientId};
8use serde::{de::DeserializeOwned, Serialize};
9use std::marker::{PhantomData, Send, Sync};
10
11use crate::{
12 common::{struct_to_form_data, PagingIter},
13 oauth::{Authenticated, MalClientId, OauthClient},
14 ANIME_URL, USER_URL,
15};
16
17use super::{
18 requests::{
19 GetAnimeDetails, GetAnimeList, GetAnimeRanking, GetSeasonalAnime, GetSuggestedAnime,
20 },
21 responses::{AnimeDetails, AnimeList, AnimeRanking, SeasonalAnime, SuggestedAnime},
22};
23use reqwest;
24
25#[doc(hidden)]
26#[derive(Debug)]
27pub struct Client {}
28
29#[doc(hidden)]
30#[derive(Debug)]
31pub struct Oauth {}
32
33#[doc(hidden)]
34#[derive(Debug)]
35pub struct None {}
36
37#[derive(Debug, Clone)]
88pub struct AnimeApiClient<State = None> {
89 client: reqwest::Client,
90 client_id: Option<String>,
91 access_token: Option<String>,
92 state: PhantomData<State>,
93}
94
95impl From<&AccessToken> for AnimeApiClient<Oauth> {
96 fn from(value: &AccessToken) -> Self {
97 AnimeApiClient::<Oauth> {
98 client: reqwest::Client::new(),
99 client_id: None,
100 access_token: Some(value.secret().clone()),
101 state: PhantomData::<Oauth>,
102 }
103 }
104}
105
106impl From<&ClientId> for AnimeApiClient<Client> {
107 fn from(value: &ClientId) -> Self {
108 AnimeApiClient::<Client> {
109 client: reqwest::Client::new(),
110 client_id: Some(value.clone().to_string()),
111 access_token: None,
112 state: PhantomData::<Client>,
113 }
114 }
115}
116
117impl From<&MalClientId> for AnimeApiClient<Client> {
118 fn from(value: &MalClientId) -> Self {
119 AnimeApiClient::<Client> {
120 client: reqwest::Client::new(),
121 client_id: Some(value.0.to_string()),
122 access_token: None,
123 state: PhantomData::<Client>,
124 }
125 }
126}
127
128impl From<&OauthClient<Authenticated>> for AnimeApiClient<Oauth> {
129 fn from(value: &OauthClient<Authenticated>) -> Self {
130 AnimeApiClient {
131 client: reqwest::Client::new(),
132 client_id: None,
133 access_token: Some(value.get_access_token().secret().clone()),
134 state: PhantomData::<Oauth>,
135 }
136 }
137}
138
139#[async_trait]
142pub trait Request {
143 async fn get<T>(&self, query: &T) -> Result<String, AnimeApiError>
144 where
145 T: Serialize + Send + Sync;
146
147 async fn get_details(&self, query: &GetAnimeDetails) -> Result<String, AnimeApiError>;
148
149 async fn get_ranking(&self, query: &GetAnimeRanking) -> Result<String, AnimeApiError>;
150
151 async fn get_seasonal(&self, query: &GetSeasonalAnime) -> Result<String, AnimeApiError>;
152
153 async fn get_user(&self, query: &GetUserAnimeList) -> Result<String, AnimeApiError>;
154
155 async fn get_next_or_prev(&self, query: Option<&String>) -> Result<String, AnimeApiError>;
156}
157
158#[async_trait]
162pub trait AnimeApi {
163 type State: Request + Send + Sync;
164
165 async fn get_anime_list(&self, query: &GetAnimeList) -> Result<AnimeList, AnimeApiError> {
169 let response = self
170 .get_self()
171 .get(query)
172 .await
173 .map_err(|err| AnimeApiError::new(format!("Failed to get anime list: {}", err)))?;
174 let result: AnimeList = serde_json::from_str(response.as_str()).map_err(|err| {
175 AnimeApiError::new(format!("Failed to parse Anime List result: {}", err))
176 })?;
177 Ok(result)
178 }
179
180 async fn get_anime_details(
184 &self,
185 query: &GetAnimeDetails,
186 ) -> Result<AnimeDetails, AnimeApiError> {
187 let response =
188 self.get_self().get_details(query).await.map_err(|err| {
189 AnimeApiError::new(format!("Failed to get anime details: {}", err))
190 })?;
191 let result: AnimeDetails = serde_json::from_str(response.as_str()).map_err(|err| {
192 AnimeApiError::new(format!("Failed to parse Anime Details result: {}", err))
193 })?;
194 Ok(result)
195 }
196
197 async fn get_anime_ranking(
201 &self,
202 query: &GetAnimeRanking,
203 ) -> Result<AnimeRanking, AnimeApiError> {
204 let response =
205 self.get_self().get_ranking(query).await.map_err(|err| {
206 AnimeApiError::new(format!("Failed to get anime ranking: {}", err))
207 })?;
208 let result: AnimeRanking = serde_json::from_str(response.as_str()).map_err(|err| {
209 AnimeApiError::new(format!("Failed to parse Anime Ranking result: {}", err))
210 })?;
211 Ok(result)
212 }
213
214 async fn get_seasonal_anime(
218 &self,
219 query: &GetSeasonalAnime,
220 ) -> Result<SeasonalAnime, AnimeApiError> {
221 let response =
222 self.get_self().get_seasonal(query).await.map_err(|err| {
223 AnimeApiError::new(format!("Failed to get seasonal anime: {}", err))
224 })?;
225 let result: SeasonalAnime = serde_json::from_str(response.as_str()).map_err(|err| {
226 AnimeApiError::new(format!("Failed to parse Seasonal Anime result: {}", err))
227 })?;
228 Ok(result)
229 }
230
231 async fn next<T>(&self, response: &T) -> Result<T, AnimeApiError>
233 where
234 T: DeserializeOwned + PagingIter + Sync + Send,
235 {
236 let response = self
237 .get_self()
238 .get_next_or_prev(response.next_page())
239 .await
240 .map_err(|err| AnimeApiError::new(format!("Failed to fetch next page: {}", err)))?;
241 let result: T = serde_json::from_str(response.as_str())
242 .map_err(|err| AnimeApiError::new(format!("Failed to fetch next page: {}", err)))?;
243 Ok(result)
244 }
245
246 async fn prev<T>(&self, response: &T) -> Result<T, AnimeApiError>
248 where
249 T: DeserializeOwned + PagingIter + Sync + Send,
250 {
251 let response = self
252 .get_self()
253 .get_next_or_prev(response.prev_page())
254 .await
255 .map_err(|err| AnimeApiError::new(format!("Failed to fetch previous page: {}", err)))?;
256 let result: T = serde_json::from_str(response.as_str())
257 .map_err(|err| AnimeApiError::new(format!("Failed to parse page: {}", err)))?;
258 Ok(result)
259 }
260
261 fn get_self(&self) -> &Self::State;
263}
264
265#[async_trait]
266impl Request for AnimeApiClient<Client> {
267 async fn get<T>(&self, query: &T) -> Result<String, AnimeApiError>
268 where
269 T: Serialize + Send + Sync,
270 {
271 let response = self
272 .client
273 .get(ANIME_URL)
274 .header("X-MAL-CLIENT-ID", self.client_id.as_ref().unwrap())
275 .query(&query)
276 .send()
277 .await
278 .map_err(|err| AnimeApiError::new(format!("Failed get request: {}", err)))?;
279
280 handle_response(response).await
281 }
282
283 async fn get_details(&self, query: &GetAnimeDetails) -> Result<String, AnimeApiError> {
284 let response = self
285 .client
286 .get(format!("{}/{}", ANIME_URL, query.anime_id))
287 .header("X-MAL-CLIENT-ID", self.client_id.as_ref().unwrap())
288 .query(&query)
289 .send()
290 .await
291 .map_err(|err| AnimeApiError::new(format!("Failed get request: {}", err)))?;
292
293 handle_response(response).await
294 }
295
296 async fn get_ranking(&self, query: &GetAnimeRanking) -> Result<String, AnimeApiError> {
297 let response = self
298 .client
299 .get(format!("{}/ranking", ANIME_URL))
300 .header("X-MAL-CLIENT-ID", self.client_id.as_ref().unwrap())
301 .query(&query)
302 .send()
303 .await
304 .map_err(|err| AnimeApiError::new(format!("Failed get request: {}", err)))?;
305
306 handle_response(response).await
307 }
308
309 async fn get_seasonal(&self, query: &GetSeasonalAnime) -> Result<String, AnimeApiError> {
310 let response = self
311 .client
312 .get(format!(
313 "{}/season/{}/{}",
314 ANIME_URL, query.year, query.season
315 ))
316 .header("X-MAL-CLIENT-ID", self.client_id.as_ref().unwrap())
317 .query(&query)
318 .send()
319 .await
320 .map_err(|err| AnimeApiError::new(format!("Failed get request: {}", err)))?;
321
322 handle_response(response).await
323 }
324
325 async fn get_user(&self, query: &GetUserAnimeList) -> Result<String, AnimeApiError> {
326 let response = self
327 .client
328 .get(format!("{}/{}/animelist", USER_URL, query.user_name))
329 .header("X-MAL-CLIENT-ID", self.client_id.as_ref().unwrap())
330 .query(&query)
331 .send()
332 .await
333 .map_err(|err| AnimeApiError::new(format!("Failed get request: {}", err)))?;
334
335 handle_response(response).await
336 }
337
338 async fn get_next_or_prev(&self, query: Option<&String>) -> Result<String, AnimeApiError> {
339 if let Some(itr) = query {
340 let response = self
341 .client
342 .get(itr)
343 .header("X-MAL-CLIENT-ID", self.client_id.as_ref().unwrap())
344 .send()
345 .await
346 .map_err(|err| AnimeApiError::new(format!("Failed get request: {}", err)))?;
347
348 handle_response(response).await
349 } else {
350 Err(AnimeApiError::new("Page does not exist".to_string()))
351 }
352 }
353}
354
355#[async_trait]
356impl Request for AnimeApiClient<Oauth> {
357 async fn get<T>(&self, query: &T) -> Result<String, AnimeApiError>
358 where
359 T: Serialize + Send + Sync,
360 {
361 let response = self
362 .client
363 .get(ANIME_URL)
364 .bearer_auth(&self.access_token.as_ref().unwrap())
365 .query(&query)
366 .send()
367 .await
368 .map_err(|err| AnimeApiError::new(format!("Failed get request: {}", err)))?;
369
370 handle_response(response).await
371 }
372
373 async fn get_details(&self, query: &GetAnimeDetails) -> Result<String, AnimeApiError> {
374 let response = self
375 .client
376 .get(format!("{}/{}", ANIME_URL, query.anime_id))
377 .bearer_auth(&self.access_token.as_ref().unwrap())
378 .query(&query)
379 .send()
380 .await
381 .map_err(|err| AnimeApiError::new(format!("Failed get request: {}", err)))?;
382
383 handle_response(response).await
384 }
385
386 async fn get_ranking(&self, query: &GetAnimeRanking) -> Result<String, AnimeApiError> {
387 let response = self
388 .client
389 .get(format!("{}/ranking", ANIME_URL))
390 .bearer_auth(&self.access_token.as_ref().unwrap())
391 .query(&query)
392 .send()
393 .await
394 .map_err(|err| AnimeApiError::new(format!("Failed get request: {}", err)))?;
395
396 handle_response(response).await
397 }
398
399 async fn get_seasonal(&self, query: &GetSeasonalAnime) -> Result<String, AnimeApiError> {
400 let response = self
401 .client
402 .get(format!(
403 "{}/season/{}/{}",
404 ANIME_URL, query.year, query.season
405 ))
406 .bearer_auth(&self.access_token.as_ref().unwrap())
407 .query(&query)
408 .send()
409 .await
410 .map_err(|err| AnimeApiError::new(format!("Failed get request: {}", err)))?;
411
412 handle_response(response).await
413 }
414
415 async fn get_user(&self, query: &GetUserAnimeList) -> Result<String, AnimeApiError> {
416 let response = self
417 .client
418 .get(format!("{}/{}/animelist", USER_URL, query.user_name))
419 .bearer_auth(&self.access_token.as_ref().unwrap())
420 .query(&query)
421 .send()
422 .await
423 .map_err(|err| AnimeApiError::new(format!("Failed get request: {}", err)))?;
424
425 handle_response(response).await
426 }
427
428 async fn get_next_or_prev(&self, query: Option<&String>) -> Result<String, AnimeApiError> {
429 if let Some(itr) = query {
430 let response = self
431 .client
432 .get(itr)
433 .bearer_auth(&self.access_token.as_ref().unwrap())
434 .send()
435 .await
436 .map_err(|err| AnimeApiError::new(format!("Failed get request: {}", err)))?;
437
438 handle_response(response).await
439 } else {
440 Err(AnimeApiError::new("Page does not exist".to_string()))
441 }
442 }
443}
444
445#[async_trait]
446impl AnimeApi for AnimeApiClient<Client> {
447 type State = AnimeApiClient<Client>;
448
449 fn get_self(&self) -> &Self::State {
450 self
451 }
452}
453
454impl AnimeApiClient<Client> {
455 pub async fn get_user_anime_list(
461 &self,
462 query: &GetUserAnimeList,
463 ) -> Result<AnimeList, AnimeApiError> {
464 if query.user_name == "@me".to_string() {
465 return Err(AnimeApiError::new(
466 "You can only get your '@me' list via an Oauth client".to_string(),
467 ));
468 }
469 let response = self.get_self().get_user(query).await.map_err(|err| {
470 AnimeApiError::new(format!(
471 "Failed to fetch {}'s anime list: {}",
472 query.user_name, err
473 ))
474 })?;
475 let result: AnimeList = serde_json::from_str(response.as_str()).map_err(|err| {
476 AnimeApiError::new(format!("Failed to parse Anime List result: {}", err))
477 })?;
478 Ok(result)
479 }
480}
481
482#[async_trait]
483impl AnimeApi for AnimeApiClient<Oauth> {
484 type State = AnimeApiClient<Oauth>;
485
486 fn get_self(&self) -> &Self::State {
487 self
488 }
489}
490
491impl AnimeApiClient<Oauth> {
492 pub async fn get_suggested_anime(
496 &self,
497 query: &GetSuggestedAnime,
498 ) -> Result<SuggestedAnime, AnimeApiError> {
499 let response = self
500 .client
501 .get(format!("{}/suggestions", ANIME_URL))
502 .bearer_auth(&self.access_token.as_ref().unwrap())
503 .query(&query)
504 .send()
505 .await
506 .map_err(|err| {
507 AnimeApiError::new(format!("Failed to fetch suggested anime: {}", err))
508 })?;
509
510 let response = handle_response(response).await?;
511
512 let result: SuggestedAnime = serde_json::from_str(response.as_str()).map_err(|err| {
513 AnimeApiError::new(format!("Failed to parse Suggested Anime result: {}", err))
514 })?;
515 Ok(result)
516 }
517
518 pub async fn get_user_anime_list(
524 &self,
525 query: &GetUserAnimeList,
526 ) -> Result<AnimeList, AnimeApiError> {
527 let response =
528 self.get_self().get_user(query).await.map_err(|err| {
529 AnimeApiError::new(format!("Failed to get user anime list: {}", err))
530 })?;
531 let result: AnimeList = serde_json::from_str(response.as_str()).map_err(|err| {
532 AnimeApiError::new(format!("Failed to parse Anime List result: {}", err))
533 })?;
534 Ok(result)
535 }
536
537 pub async fn update_anime_list_status(
541 &self,
542 query: &UpdateMyAnimeListStatus,
543 ) -> Result<AnimeListStatus, AnimeApiError> {
544 let form_data = struct_to_form_data(&query).map_err(|err| {
545 AnimeApiError::new(format!("Failed to turn request into form data: {}", err))
546 })?;
547 let response = self
548 .client
549 .put(format!("{}/{}/my_list_status", ANIME_URL, query.anime_id))
550 .bearer_auth(&self.access_token.as_ref().unwrap())
551 .form(&form_data)
552 .send()
553 .await
554 .map_err(|err| {
555 AnimeApiError::new(format!(
556 "Failed to update user's anime list status: {}",
557 err
558 ))
559 })?;
560
561 let response = handle_response(response).await?;
562 let result: AnimeListStatus = serde_json::from_str(response.as_str()).map_err(|err| {
563 AnimeApiError::new(format!("Failed to parse Anime List result: {}", err))
564 })?;
565 Ok(result)
566 }
567
568 pub async fn delete_anime_list_item(
572 &self,
573 query: &DeleteMyAnimeListItem,
574 ) -> Result<(), AnimeApiError> {
575 let response = self
576 .client
577 .delete(format!("{}/{}/my_list_status", ANIME_URL, query.anime_id))
578 .bearer_auth(&self.access_token.as_ref().unwrap())
579 .send()
580 .await
581 .map_err(|err| {
582 AnimeApiError::new(format!("Failed to delete the anime list item: {}", err))
583 })?;
584
585 match response.status() {
586 reqwest::StatusCode::OK => Ok(()),
587 reqwest::StatusCode::NOT_FOUND => Err(AnimeApiError::new(
588 "Anime does not exist in user's anime list".to_string(),
589 )),
590 _ => Err(AnimeApiError::new(format!(
591 "Did not recieve expected response: {}",
592 response.status()
593 ))),
594 }
595 }
596}
597
598async fn handle_response(response: reqwest::Response) -> Result<String, AnimeApiError> {
599 match response.status() {
600 reqwest::StatusCode::OK => {
601 let content = response.text().await.map_err(|err| {
602 AnimeApiError::new(format!("Failed to get content from response: {}", err))
603 })?;
604 Ok(content)
605 }
606 _ => Err(AnimeApiError::new(format!(
607 "Did not recieve OK response: {}",
608 response.status()
609 ))),
610 }
611}