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 if !(response_text.starts_with("{") && response_text.ends_with("}")) {
115 return Err(anyhow::Error::from(ApiError::InvalidJsonFormat));
116 }
117
118 #[cfg(feature = "ddnet")]
120 if response_text == "{}" {
121 return Err(anyhow::Error::from(ApiError::NotFound));
122 }
123
124 #[cfg(feature = "ddstats")]
126 if let Ok(error_response) = serde_json::from_str::<serde_json::Value>(response_text) {
127 if let Some(error_msg) = error_response.get("error").and_then(|e| e.as_str()) {
128 return match error_msg.to_lowercase().as_str() {
129 "player not found" => Err(anyhow::Error::from(ApiError::NotFound)),
130 _ => Err(anyhow!(error_msg.to_string())),
131 };
132 }
133 }
134
135 serde_json::from_str(response_text).map_err(Into::into)
136 }
137}
138
139#[cfg(feature = "ddnet")]
140pub mod ddnet;
141
142#[cfg(feature = "ddstats")]
143pub mod ddstats;