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 .error_for_status()
48 .context("Server returned error status")?;
49
50 let text = response
51 .text()
52 .await
53 .context("Failed to read response body")?;
54
55 if text.is_empty() {
56 anyhow::bail!("API returned empty response");
57 }
58
59 Ok(text)
60 }
61
62 pub async fn _generator<T>(&self, uri: &str, cache: bool) -> Result<T>
63 where
64 T: DeserializeOwned + Send + Sync + 'static,
65 {
66 if cache {
67 self._generator_cached(uri).await
68 } else {
69 self._generator_no_cache(uri).await
70 }
71 }
72
73 async fn _generator_cached<T>(&self, uri: &str) -> Result<T>
74 where
75 T: DeserializeOwned + Send + Sync + 'static,
76 {
77 let type_name = std::any::type_name::<T>();
78 let cache_key = format!("{}:{}", type_name, uri);
79
80 match &self.cache {
81 Some(cache) => {
82 if let Some(value) = cache.get(&cache_key).await {
83 self.parse_response::<T>(&value)
84 } else {
85 let response_text = self.send_request(uri).await?;
86 cache.insert(cache_key, response_text.clone()).await;
87 self.parse_response::<T>(&response_text)
88 }
89 }
90 None => self._generator_no_cache(uri).await,
91 }
92 }
93
94 async fn _generator_no_cache<T>(&self, uri: &str) -> Result<T>
95 where
96 T: DeserializeOwned,
97 {
98 let response_text = self.send_request(uri).await?;
99 self.parse_response::<T>(&response_text)
100 }
101
102 fn parse_response<T>(&self, response_text: &str) -> Result<T>
103 where
104 T: DeserializeOwned,
105 {
106 if let Ok(error_response) = serde_json::from_str::<serde_json::Value>(response_text) {
107 if let Some(error_msg) = error_response.get("error").and_then(|e| e.as_str()) {
108 return match error_msg.to_lowercase().as_str() {
109 "player not found" => Err(anyhow::Error::from(ApiError::NotFound)),
110 _ => Err(anyhow!(error_msg.to_string())),
111 };
112 }
113 }
114
115 serde_json::from_str(response_text).map_err(Into::into)
116 }
117}
118
119#[cfg(feature = "ddnet")]
120pub mod ddnet;
121
122#[cfg(feature = "ddstats")]
123pub mod ddstats;