ddapi_rs/api/
mod.rs

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;