1use crate::error::{Error, Result};
2#[cfg(feature = "cache")]
3use moka::future::Cache;
4use reqwest::header;
5use reqwest::Client;
6use serde::de::DeserializeOwned;
7#[allow(unused_imports)]
8use std::time::Duration;
9
10#[cfg(feature = "cache")]
11const DEFAULT_CACHE_TTL: Duration = Duration::from_secs(60 * 10);
12#[cfg(feature = "cache")]
13const DEFAULT_CACHE_CAPACITY: u64 = 10_000;
14
15#[derive(Clone, Default)]
16pub(crate) struct ApiCore {
17 client: Client,
18 #[cfg(feature = "cache")]
19 cache: Option<Cache<String, Vec<u8>>>,
20}
21
22impl ApiCore {
23 #[cfg(feature = "cache")]
24 fn default_cache() -> Cache<String, Vec<u8>> {
25 Cache::builder()
26 .max_capacity(DEFAULT_CACHE_CAPACITY)
27 .time_to_live(DEFAULT_CACHE_TTL)
28 .build()
29 }
30
31 fn new() -> Self {
32 let client = Client::builder()
33 .user_agent(concat!(
34 env!("CARGO_PKG_NAME"),
35 "/",
36 env!("CARGO_PKG_VERSION")
37 ))
38 .default_headers({
39 let mut h = header::HeaderMap::new();
40 h.insert(
41 header::ACCEPT,
42 header::HeaderValue::from_static("application/json"),
43 );
44 h
45 })
46 .build()
47 .unwrap_or_else(|_| Client::new());
48 Self {
49 client,
50 #[cfg(feature = "cache")]
51 cache: Some(Self::default_cache()),
52 }
53 }
54
55 fn new_with_client(client: Client) -> Self {
56 Self {
57 client,
58 #[cfg(feature = "cache")]
59 cache: Some(Self::default_cache()),
60 }
61 }
62
63 #[cfg(feature = "cache")]
64 fn set_cache(&mut self, capacity: u64, time_to_live: Duration) {
65 self.cache = Some(
66 Cache::builder()
67 .max_capacity(capacity)
68 .time_to_live(time_to_live)
69 .build(),
70 );
71 }
72
73 async fn send_request(&self, url: &str) -> Result<Vec<u8>> {
75 let response = self
76 .client
77 .get(url)
78 .timeout(Duration::from_secs(30))
80 .send()
81 .await?;
82
83 let status = response.status();
84 let body = response.bytes().await?.to_vec();
85
86 if body.is_empty() {
87 return Err(Error::EmptyBody);
88 }
89
90 if !status.is_success() {
91 let msg = String::from_utf8_lossy(&body).chars().take(2048).collect();
92 return Err(Error::HttpStatus { status, body: msg });
93 }
94
95 Ok(body)
96 }
97
98 pub async fn _generator<T>(&self, url: &str) -> Result<T>
99 where
100 T: DeserializeOwned + Send + Sync + 'static,
101 {
102 #[cfg(feature = "cache")]
103 {
104 self._generator_cached(url).await
105 }
106 #[cfg(not(feature = "cache"))]
107 {
108 self._generator_no_cache(url).await
109 }
110 }
111
112 #[cfg(feature = "cache")]
113 async fn _generator_cached<T>(&self, url: &str) -> Result<T>
114 where
115 T: DeserializeOwned + Send + Sync + 'static,
116 {
117 let type_name = std::any::type_name::<T>();
118 let cache_key = format!("{}:{}", type_name, url);
119
120 match &self.cache {
121 Some(cache) => {
122 if let Some(value) = cache.get(&cache_key).await {
123 self.parse_response::<T>(value.as_slice())
124 } else {
125 let body = self.send_request(url).await?;
126 cache.insert(cache_key, body.clone()).await;
127 self.parse_response::<T>(body.as_slice())
128 }
129 }
130 None => self._generator_no_cache(url).await,
131 }
132 }
133
134 pub async fn _generator_no_cache<T>(&self, url: &str) -> Result<T>
135 where
136 T: DeserializeOwned,
137 {
138 let body = self.send_request(url).await?;
139 self.parse_response::<T>(body.as_slice())
140 }
141
142 fn parse_response<T>(&self, body: &[u8]) -> Result<T>
143 where
144 T: DeserializeOwned,
145 {
146 #[cfg(feature = "ddnet")]
148 {
149 let trimmed = trim_ascii(body);
150 if trimmed == b"{}" {
151 return Err(Error::NotFound);
152 }
153 }
154
155 #[cfg(feature = "ddstats")]
157 {
158 #[derive(serde::Deserialize)]
159 #[serde(untagged)]
160 enum MaybeError<T> {
161 Err { error: String },
162 Ok(T),
163 }
164
165 match serde_json::from_slice::<MaybeError<T>>(body)? {
167 MaybeError::Err { error } => {
168 if error.eq_ignore_ascii_case("player not found") {
169 Err(Error::NotFound)
170 } else {
171 Err(Error::RemoteMessage(error))
172 }
173 }
174 MaybeError::Ok(v) => Ok(v),
175 }
176 }
177
178 #[cfg(not(feature = "ddstats"))]
179 {
180 Ok(serde_json::from_slice(body)?)
181 }
182 }
183}
184
185fn trim_ascii(mut s: &[u8]) -> &[u8] {
186 while let Some((&b, rest)) = s.split_first() {
187 if !b.is_ascii_whitespace() {
188 break;
189 }
190 s = rest;
191 }
192 while let Some((&b, rest)) = s.split_last() {
193 if !b.is_ascii_whitespace() {
194 break;
195 }
196 s = rest;
197 }
198 s
199}
200
201pub trait HasApiCore {
202 fn core(&self) -> &ApiCore;
203}
204
205#[derive(Clone, Default)]
206pub struct DDApi {
207 core: ApiCore,
208}
209
210impl HasApiCore for DDApi {
211 fn core(&self) -> &ApiCore {
212 &self.core
213 }
214}
215
216impl DDApi {
217 pub fn new() -> Self {
227 DDApi {
228 core: ApiCore::new(),
229 }
230 }
231
232 pub fn new_with_client(client: Client) -> Self {
254 DDApi {
255 core: ApiCore::new_with_client(client),
256 }
257 }
258
259 #[cfg(feature = "cache")]
280 pub fn set_cache(&mut self, capacity: u64, time_to_live: Duration) {
281 self.core.set_cache(capacity, time_to_live);
282 }
283
284 pub async fn _generator<T>(&self, url: &str) -> Result<T>
303 where
304 T: DeserializeOwned + Send + Sync + 'static,
305 {
306 self.core._generator(url).await
307 }
308
309 pub async fn _generator_no_cache<T>(&self, url: &str) -> Result<T>
325 where
326 T: DeserializeOwned,
327 {
328 self.core._generator_no_cache(url).await
329 }
330}
331
332#[derive(Clone, Default)]
333pub struct DDnetClient {
334 core: ApiCore,
335}
336
337impl HasApiCore for DDnetClient {
338 fn core(&self) -> &ApiCore {
339 &self.core
340 }
341}
342
343impl DDnetClient {
344 pub fn new() -> Self {
345 Self {
346 core: ApiCore::new(),
347 }
348 }
349
350 pub fn new_with_client(client: Client) -> Self {
351 Self {
352 core: ApiCore::new_with_client(client),
353 }
354 }
355
356 #[cfg(feature = "cache")]
357 pub fn set_cache(&mut self, capacity: u64, time_to_live: Duration) {
358 self.core.set_cache(capacity, time_to_live);
359 }
360}
361
362#[derive(Clone, Default)]
363pub struct DDstatsClient {
364 core: ApiCore,
365}
366
367impl HasApiCore for DDstatsClient {
368 fn core(&self) -> &ApiCore {
369 &self.core
370 }
371}
372
373impl DDstatsClient {
374 pub fn new() -> Self {
375 Self {
376 core: ApiCore::new(),
377 }
378 }
379
380 pub fn new_with_client(client: Client) -> Self {
381 Self {
382 core: ApiCore::new_with_client(client),
383 }
384 }
385
386 #[cfg(feature = "cache")]
387 pub fn set_cache(&mut self, capacity: u64, time_to_live: Duration) {
388 self.core.set_cache(capacity, time_to_live);
389 }
390}
391
392#[cfg(feature = "ddnet")]
393pub mod ddnet;
394
395#[cfg(feature = "ddstats")]
396pub mod ddstats;