1use anyhow::{anyhow, Context, Result};
2use moka::future::Cache;
3use reqwest::Client;
4use serde::de::DeserializeOwned;
5use crate::error::ApiError;
6
7#[derive(Clone, Default)]
8pub struct DDApi {
9 client: Client,
10 cache: Option<Cache<String, String>>,
11}
12
13impl DDApi {
14 pub fn new() -> Self {
15 DDApi {
16 client: Client::new(),
17 cache: None,
18 }
19 }
20
21 pub fn new_with_client(client: Client) -> Self {
22 DDApi {
23 client,
24 cache: None,
25 }
26 }
27
28 #[cfg(feature = "cache")]
29 pub fn set_cache(&mut self, capacity: u64, time_to_live: u64) {
30 use std::time::Duration;
31
32 self.cache = Some(
33 Cache::builder()
34 .max_capacity(capacity)
35 .time_to_live(Duration::from_secs(time_to_live))
36 .build(),
37 );
38 }
39
40 async fn send_request(&self, uri: &str) -> Result<String> {
41 let response = self
42 .client
43 .get(uri)
44 .send()
45 .await
46 .context("Failed to send request")?;
47
48 let text = response
49 .text()
50 .await
51 .context("Failed to read response body")?;
52
53 if text.is_empty() {
54 anyhow::bail!("API returned empty response");
55 }
56
57 Ok(text)
58 }
59
60 pub async fn _generator<T>(&self, uri: &str, cache: bool) -> Result<T>
61 where
62 T: DeserializeOwned + Send + Sync + 'static,
63 {
64 if cache {
65 self._generator_cached(uri).await
66 } else {
67 self._generator_no_cache(uri).await
68 }
69 }
70
71 async fn _generator_cached<T>(&self, uri: &str) -> Result<T>
72 where
73 T: DeserializeOwned + Send + Sync + 'static,
74 {
75 let type_name = std::any::type_name::<T>();
76 let cache_key = format!("{}:{}", type_name, uri);
77
78 match &self.cache {
79 Some(cache) => {
80 if let Some(value) = cache.get(&cache_key).await {
81 self.parse_response::<T>(&value)
82 } else {
83 let response_text = self.send_request(uri).await?;
84 cache.insert(cache_key, response_text.clone()).await;
85 self.parse_response::<T>(&response_text)
86 }
87 }
88 None => self._generator_no_cache(uri).await,
89 }
90 }
91
92 async fn _generator_no_cache<T>(&self, uri: &str) -> Result<T>
93 where
94 T: DeserializeOwned,
95 {
96 let response_text = self.send_request(uri).await?;
97 self.parse_response::<T>(&response_text)
98 }
99
100 fn parse_response<T>(&self, response_text: &str) -> Result<T>
101 where
102 T: DeserializeOwned,
103 {
104 if let Ok(error_response) = serde_json::from_str::<serde_json::Value>(response_text) {
105 if let Some(error_msg) = error_response.get("error").and_then(|e| e.as_str()) {
106 return match error_msg.to_lowercase().as_str() {
107 "player not found" => Err(anyhow::Error::from(ApiError::NotFound)),
108 _ => Err(anyhow!(error_msg.to_string())),
109 };
110 }
111 }
112
113 serde_json::from_str(response_text).map_err(Into::into)
114 }
115}
116
117#[cfg(feature = "ddnet")]
118pub mod ddnet;
119
120#[cfg(feature = "ddstats")]
121pub mod ddstats;