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)) .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!() };
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 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 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