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