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