tetrio_api/http/
cached_client.rs

1
2use std::fmt::Display;
3use std::marker::PhantomData;
4use std::str::FromStr;
5use std::sync::Arc;
6use std::time::Duration;
7use async_lock::Mutex;
8use bytes::{Buf, Bytes};
9use futures::FutureExt;
10use futures_core::future::BoxFuture;
11use http::{HeaderValue, Request};
12use serde::de::DeserializeOwned;
13use serde::Serialize;
14use url::Url;
15use super::caches::cache::CacheHandler;
16use super::error::{Error, ErrorTrait};
17use super::clients::http_client::HttpClient;
18use super::parameters::leaderboard_query::LeaderboardType;
19use super::parameters::personal_user_records::{PersonalLeaderboard, PersonalRecordsQuery};
20use super::parameters::value_bound_query::ValueBoundQuery;
21use crate::models::general::achivement_info::AchievementInfoPacket;
22use crate::models::general::activity::ActivityPacket;
23use crate::models::general::stats::StatsPacket;
24use crate::models::labs::league_ranks::LeagueRanksPacket;
25use crate::models::labs::leagueflow::LeagueFlowPacket;
26use crate::models::labs::scoreflow::ScoreFlowPacket;
27use crate::models::news::latest::LatestNewsPacket;
28use crate::models::news::NewsPacket;
29use crate::models::packet::{Packet, SuccessPacket};
30use crate::models::users::summaries::{AchievementsSummaryPacket, AllSummariesPacket, BlitzSummaryPacket, LeagueSummaryPacket, SprintSummaryPacket, ZenSummaryPacket, ZenithExSummaryPacket, ZenithSummaryPacket};
31use crate::models::users::user_history_leaderboard::HistoricalLeaderboardPacket;
32use crate::models::users::user_info::UserInfoPacket;
33use crate::models::users::user_leaderboard::LeaderboardPacket;
34use crate::models::users::user_records::{PersonalBlitzRecordPacket, PersonalLeagueRecordPacket, PersonalSprintRecordPacket, PersonalZenithExRecordPacket, PersonalZenithRecordPacket};
35use crate::models::users::user_search::UserSearchPacket;
36use futures::future::Either;
37use tower::Service;
38use tower_util::ServiceExt;
39
40pub struct CachedClient<HttpClientImpl: HttpClient, Cache: CacheHandler<HttpClientImpl::HttpError>> {
41    req_service: Mutex<Box<dyn Send + Sync + Service< Request<Vec<u8>>, Response = Bytes, Error = HttpClientImpl::HttpError, Future = BoxFuture<'static, Result<Bytes, HttpClientImpl::HttpError>>>>>,
42    cache_handler: Cache,
43    _phantom: PhantomData<HttpClientImpl>,
44}
45
46impl<HttpClientImpl: HttpClient + Default, Cache: CacheHandler<HttpClientImpl::HttpError> + Default> Default for CachedClient<HttpClientImpl, Cache> {
47    fn default() -> Self {
48        return Self::new(HttpClientImpl::default(), Cache::default())
49    }
50}
51
52
53impl<HttpClientImpl: HttpClient, Cache: CacheHandler<HttpClientImpl::HttpError>> CachedClient<HttpClientImpl, Cache> {
54    pub fn new(client: HttpClientImpl, cache_handler: Cache) -> Self {
55            let client = Arc::new(client);
56            let svc = tower::ServiceBuilder::new()
57                .rate_limit(1, Duration::new(1, 0)) // 1 requests every 1 seconds
58                .service(tower::service_fn(move |request| {
59                    let clone_client = client.clone();
60                    async move {
61                        clone_client.execute(request).await
62                    }.boxed()
63                }));
64
65            CachedClient {
66                req_service: Mutex::new(Box::new(svc)),
67                cache_handler,
68                _phantom: PhantomData::default(),
69            }
70    }
71
72}
73
74
75
76
77pub const TETRIO_API_URL: &str = "https://ch.tetr.io/api/";
78
79impl<HttpClientImpl: HttpClient + Send + Sync, Cache: CacheHandler<HttpClientImpl::HttpError>> ErrorTrait for CachedClient<HttpClientImpl, Cache> {
80    type Error = Error<HttpClientImpl::HttpError, Cache::CachingError>;
81}
82
83impl<HttpClientImpl: HttpClient + Send + Sync, Cache: CacheHandler<HttpClientImpl::HttpError>> CachedClient<HttpClientImpl, Cache> {
84
85   
86    pub(crate) async fn parse_body<T: DeserializeOwned>(body: Bytes) -> Result<T, <Self as ErrorTrait>::Error> {
87        let jd = &mut serde_json::Deserializer::from_reader(body.clone().reader());
88
89        let result: Result<T, _> = serde_path_to_error::deserialize(jd);
90
91        match result {
92            Ok(v) => Ok(v),
93            Err(err) => {
94                Err(Error::ParsingError(err))
95            }
96        }
97    }
98
99    pub fn make_url<T: Display>(href: &str, query_params: &[[T; 2]]) -> String {
100        let mut url = match Url::from_str(&format!("{TETRIO_API_URL}{href}")) {
101            Ok(v) => v,
102            Err(_) => unreachable!() // TETRIO_API_URL HAS TO BE VALID for ANYTHING to work.
103        };
104    
105    
106        {
107            let mut query = url.query_pairs_mut();
108    
109            for pair in query_params {
110                query.append_pair(&pair[0].to_string(), &pair[1].to_string());
111            };
112    
113            query.finish();
114        }
115    
116    
117    
118        return dbg!(url.to_string().replacen(TETRIO_API_URL, "", 1));
119    }
120
121    pub async fn make_request<T: DeserializeOwned + Serialize>(&self, url: &str, session_id: &Option<&str>) -> Result<T, <Self as ErrorTrait>::Error>  {
122        let req = Request::builder()
123                        .method(http::Method::GET)
124                        .uri(url);
125
126        let req = if let Some(session_id) = session_id {
127            req.header("X-SESSION-ID", HeaderValue::from_str(session_id).map_err(Error::InvalidHeaderValue)?)
128        } else {
129            req
130        };
131        
132
133        let mut service = self.req_service.lock().await;
134
135        let response = service.ready_and().await.map_err(Error::HttpError)?.call(req.body(vec![]).map_err(Error::RequestParsingError)?).await.map_err(Error::HttpError)?;
136
137        
138
139        Self::parse_body::<T>(
140            response
141        ).await
142    }
143
144    pub async fn cache_value_if_success<T: DeserializeOwned + Serialize + Send + Sync>(&self, cache_key: String, r: Packet<T>) -> Result<bool, <Self as ErrorTrait>::Error> {
145        let Packet { success, cache, data, .. } = r;
146        match (cache, success, data) {
147            (Some(cache), true, Some(data)) => {
148                self.cache_handler.cache_value(&cache_key, SuccessPacket { success: true, cache, data }).await?;
149
150                Ok(true)
151            }
152            _ => { Ok(false) }
153        }
154    }
155
156    pub(crate) fn get_url(route: impl Display) -> String {
157        format!("{TETRIO_API_URL}{route}")
158    }
159
160    pub(crate) fn get_cache_key(url: &str, session_id: &Option<&str>) -> String {
161        format!("{url}&X_SESSION_ID={session_id:?}")
162    }
163
164    pub async fn make_tetrio_api_request<T: DeserializeOwned + Serialize + Send + Sync + Clone>(&self, route: impl Display, session_id: Option<&str>) -> Result<Packet<T>, <Self as ErrorTrait>::Error> {
165        let url = Self::get_url(route);
166        let cache_key = Self::get_cache_key(&url, &session_id);
167        dbg!(&url);
168        let response = self.cache_handler.try_get_cache(&cache_key).await?;
169        response.map_or_else(|| Either::Left(async {
170            let packet = self.make_request::<Packet<T>>(&url, &session_id).await;
171            dbg!(packet.is_ok());
172            // ignore error because we don't care if it's not cached
173            let _ = match &packet {
174                Ok(value) => {self.cache_value_if_success(cache_key, value.clone()).await},
175                _ => { return packet; }
176            };
177
178            return packet;
179        }), |value| Either::Right(async move { Ok(value) })).await
180    }
181
182    pub async fn cache_tetrio_api_result_if_not_present<T: DeserializeOwned + Serialize + Clone + Send + Sync>(&self, route: impl Display, session_id: Option<&str>, packet: &str) -> Result<Packet<T>, <Self as ErrorTrait>::Error> {
183        let url = Self::get_url(route);
184        let cache_key = Self::get_cache_key(&url, &session_id);
185        let response = self.cache_handler.try_get_cache(&cache_key).await?;
186        response.map_or_else(|| Either::Left(async {
187            let deserializer = &mut serde_json::Deserializer::from_str(packet);
188
189            let result: Result<Packet<T>, _> = serde_path_to_error::deserialize(deserializer).map_err(
190                Error::ConversionError
191            );
192
193
194            // ignore error because we don't care if it's not cached
195            let _ = match &result {
196                Ok(value) => {self.cache_value_if_success(cache_key, value.clone()).await},
197                _ => { return result }
198            };
199
200            return result;
201        }), |value| Either::Right(async move { Ok(value) })).await
202    }
203
204    pub async fn get_from_cache<T: DeserializeOwned + Serialize + Clone + Send + Sync>(&self, route: impl Display, session_id: Option<&str>) -> Result<Option<Packet<T>>, <Self as ErrorTrait>::Error> {
205        let url = Self::get_url(route);
206        let cache_key = Self::get_cache_key(&url, &session_id);
207    
208        self.cache_handler.try_get_cache(&cache_key).await
209    }
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229    pub async fn fetch_general_stats(&self) -> Result<StatsPacket, <Self as ErrorTrait>::Error> {
230        return self.make_tetrio_api_request("general/stats", None).await;
231    }
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247    pub async fn fetch_general_activity(&self) -> Result<ActivityPacket, <Self as ErrorTrait>::Error> {
248        return self.make_tetrio_api_request("general/activity", None).await;
249
250    }
251    
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284    pub async fn fetch_user_info(&self, user: &str) -> Result<UserInfoPacket, <Self as ErrorTrait>::Error> {
285        self.make_tetrio_api_request(format!("users/{user}"), None).await
286    }
287    
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322    pub async fn fetch_user_summaries(&self, user: &str) -> Result<AllSummariesPacket, <Self as ErrorTrait>::Error> {
323        self.make_tetrio_api_request(format!("users/{user}/summaries"), None).await
324
325    }
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361    pub async fn fetch_user_zen_summaries(&self, user: &str) -> Result<ZenSummaryPacket, <Self as ErrorTrait>::Error> {
362        self.make_tetrio_api_request(format!("users/{user}/summaries/zen"), None).await
363
364    }
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400    pub async fn fetch_user_league_summaries(&self, user: &str) -> Result<LeagueSummaryPacket, <Self as ErrorTrait>::Error> {
401        self.make_tetrio_api_request(format!("users/{user}/summaries/league"), None).await
402
403    }
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439    pub async fn fetch_user_40l_summaries(&self, user: &str) -> Result<SprintSummaryPacket, <Self as ErrorTrait>::Error> {
440        self.make_tetrio_api_request(format!("users/{user}/summaries/40l"), None).await
441
442    }
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479    pub async fn fetch_user_blitz_summaries(&self, user: &str) -> Result<BlitzSummaryPacket, <Self as ErrorTrait>::Error> {
480        self.make_tetrio_api_request(format!("users/{user}/summaries/blitz"), None).await
481
482    }
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518    pub async fn fetch_user_zenith_summaries(&self, user: &str) -> Result<ZenithSummaryPacket, <Self as ErrorTrait>::Error> {
519        self.make_tetrio_api_request(format!("users/{user}/summaries/zenith"), None).await
520
521    }
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557    pub async fn fetch_user_zenithex_summaries(&self, user: &str) -> Result<ZenithExSummaryPacket, <Self as ErrorTrait>::Error> {
558        self.make_tetrio_api_request(format!("users/{user}/summaries/zenithex"), None).await
559
560    }
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596    pub async fn fetch_user_achievements_summaries(&self, user: &str) -> Result<AchievementsSummaryPacket, <Self as ErrorTrait>::Error> {
597        self.make_tetrio_api_request(format!("users/{user}/summaries/achievements"), None).await
598    }
599    
600    
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629    pub async fn search_discord_user(&self, query: &str) -> Result<UserSearchPacket, <Self as ErrorTrait>::Error> {
630
631
632        self.make_tetrio_api_request(format!("users/search/discord:{query}"), None).await
633    }
634
635    pub async fn fetch_leaderboard(&self,
636                                   leaderboard_type: LeaderboardType,
637                                   query: ValueBoundQuery,
638                                   session_id: Option<&str>) -> Result<LeaderboardPacket, <Self as ErrorTrait>::Error> {
639        let url = format!("users/by/{}", leaderboard_type.to_string());
640        self.make_tetrio_api_request(Self::make_url(&url, &query.as_query_params()), session_id).await
641    }
642
643    pub async fn fetch_historical_leaderboard(&self,
644                                   leaderboard_type: LeaderboardType,
645                                   season: String,
646                                   query: ValueBoundQuery,
647                                   session_id: Option<&str>) -> Result<HistoricalLeaderboardPacket, <Self as ErrorTrait>::Error> {
648        let url = format!("users/history/{}/{}", leaderboard_type.to_string(), season);
649        self.make_tetrio_api_request(Self::make_url(&url, &query.as_query_params()), session_id).await
650    }
651
652    pub async fn fetch_user_personal_40l_records(&self,
653                                             user: &str,
654                                             leaderboard: PersonalLeaderboard,
655                                             query: PersonalRecordsQuery) -> Result<PersonalSprintRecordPacket, <Self as ErrorTrait>::Error> {
656        let url = format!("users/{}/records/{}/{}", user, "40l", leaderboard.to_string());
657
658        self.make_tetrio_api_request(Self::make_url(&url, &query.as_query_params()), None).await
659    }
660
661    pub async fn fetch_user_personal_blitz_records(&self,
662                                             user: &str,
663                                             leaderboard: PersonalLeaderboard,
664                                             query: PersonalRecordsQuery) -> Result<PersonalBlitzRecordPacket, <Self as ErrorTrait>::Error> {
665        let url = format!("users/{}/records/{}/{}", user, "blitz", leaderboard.to_string());
666
667        self.make_tetrio_api_request(Self::make_url(&url, &query.as_query_params()), None).await
668    }
669
670    pub async fn fetch_user_personal_league_records(&self,
671                                             user: &str,
672                                             leaderboard: PersonalLeaderboard,
673                                             query: PersonalRecordsQuery) -> Result<PersonalLeagueRecordPacket, <Self as ErrorTrait>::Error> {
674        let url = format!("users/{}/records/{}/{}", user, "league", leaderboard.to_string());
675
676        self.make_tetrio_api_request(Self::make_url(&url, &query.as_query_params()), None).await
677    }
678
679    pub async fn fetch_user_personal_zenith_records(&self,
680                                             user: &str,
681                                             leaderboard: PersonalLeaderboard,
682                                             query: PersonalRecordsQuery) -> Result<PersonalZenithRecordPacket, <Self as ErrorTrait>::Error> {
683        let url = format!("users/{}/records/{}/{}", user, "zenith", leaderboard.to_string());
684
685        self.make_tetrio_api_request(Self::make_url(&url, &query.as_query_params()), None).await
686    }
687
688    pub async fn fetch_user_personal_zenithex_records(&self,
689                                             user: &str,
690                                             leaderboard: PersonalLeaderboard,
691                                             query: PersonalRecordsQuery) -> Result<PersonalZenithExRecordPacket, <Self as ErrorTrait>::Error> {
692        let url = format!("users/{}/records/{}/{}", user, "zenithex", leaderboard.to_string());
693
694        self.make_tetrio_api_request(Self::make_url(&url, &query.as_query_params()), None).await
695    }
696    
697    pub async fn fetch_news(&self, limit: Option<i64>) -> Result<NewsPacket, <Self as ErrorTrait>::Error> {
698        let url = "news/";
699        let limit = limit.map(|l| vec![["limit".to_lowercase(), l.to_string()]]).unwrap_or(vec![]);
700        self.make_tetrio_api_request(Self::make_url(&url, &limit), None).await
701
702    }
703    
704    pub async fn fetch_latest_news(
705        &self,
706        stream: &str,
707        limit: Option<i64>,
708    ) -> Result<LatestNewsPacket, <Self as ErrorTrait>::Error> {
709        let url = format!("news/{}", stream);
710        let limit = limit.map(|l| vec![["limit".to_lowercase(), l.to_string()]]).unwrap_or(vec![]);
711        self.make_tetrio_api_request(Self::make_url(&url, &limit), None).await
712    }
713    
714    pub async fn fetch_scoreflow(&self, user: &str, game_mode: &str) -> Result<ScoreFlowPacket, <Self as ErrorTrait>::Error> {
715        let url = format!("labs/scoreflow/{user}/{game_mode}");
716        self.make_tetrio_api_request(url, None).await
717    }
718
719    pub async fn fetch_leagueflow(&self, user: &str) -> Result<LeagueFlowPacket, <Self as ErrorTrait>::Error> {
720        let url = format!("labs/leagueflow/{user}");
721        self.make_tetrio_api_request(url, None).await
722    }
723
724    pub async fn fetch_leagueranks(&self) -> Result<LeagueRanksPacket, <Self as ErrorTrait>::Error> {
725        let url = format!("labs/league_ranks");
726        self.make_tetrio_api_request(url, None).await
727    }
728
729    pub async fn fetch_achievement_info(&self, achievement: &str) -> Result<AchievementInfoPacket, <Self as ErrorTrait>::Error> {
730        let url = format!("achievements/{achievement}");
731        self.make_tetrio_api_request(url, None).await
732    }
733}
734