use crate::error::ApiError;
use anyhow::{Context, Result};
#[cfg(feature = "cache")]
use moka::future::Cache;
use reqwest::Client;
use serde::de::DeserializeOwned;
#[allow(unused_imports)]
use std::time::Duration;
#[derive(Clone, Default)]
pub struct DDApi {
client: Client,
#[cfg(feature = "cache")]
cache: Option<Cache<String, String>>,
}
impl DDApi {
pub fn new() -> Self {
DDApi {
client: Client::new(),
#[cfg(feature = "cache")]
cache: None,
}
}
pub fn new_with_client(client: Client) -> Self {
DDApi {
client,
#[cfg(feature = "cache")]
cache: None,
}
}
#[cfg(feature = "cache")]
pub fn set_cache(&mut self, capacity: u64, time_to_live: Duration) {
self.cache = Some(
Cache::builder()
.max_capacity(capacity)
.time_to_live(time_to_live)
.build(),
);
}
async fn send_request(&self, url: &str) -> Result<String> {
let response = self
.client
.get(url)
.send()
.await
.context("Failed to send request")?;
let text = response
.text()
.await
.context("Failed to read response body")?;
if text.is_empty() {
anyhow::bail!("API returned empty response");
}
#[cfg(feature = "ddnet")]
if text == "{}" {
return Err(anyhow::Error::from(ApiError::NotFound));
}
Ok(text)
}
pub async fn _generator<T>(&self, url: &str) -> Result<T>
where
T: DeserializeOwned + Send + Sync + 'static,
{
#[cfg(feature = "cache")]
{
self._generator_cached(url).await
}
#[cfg(not(feature = "cache"))]
{
self._generator_no_cache(url).await
}
}
#[cfg(feature = "cache")]
async fn _generator_cached<T>(&self, url: &str) -> Result<T>
where
T: DeserializeOwned + Send + Sync + 'static,
{
let type_name = std::any::type_name::<T>();
let cache_key = format!("{}:{}", type_name, url);
match &self.cache {
Some(cache) => {
if let Some(value) = cache.get(&cache_key).await {
self.parse_response::<T>(&value)
} else {
let response_text = self.send_request(url).await?;
cache.insert(cache_key, response_text.clone()).await;
self.parse_response::<T>(&response_text)
}
}
None => self._generator_no_cache(url).await,
}
}
pub async fn _generator_no_cache<T>(&self, url: &str) -> Result<T>
where
T: DeserializeOwned,
{
let response_text = self.send_request(url).await?;
self.parse_response::<T>(&response_text)
}
fn parse_response<T>(&self, response_text: &str) -> Result<T>
where
T: DeserializeOwned,
{
#[cfg(feature = "ddnet")]
if response_text == "{}" {
return Err(anyhow::Error::from(ApiError::NotFound));
}
#[cfg(feature = "ddstats")]
if let Ok(error_response) = serde_json::from_str::<serde_json::Value>(response_text) {
if let Some(error_msg) = error_response.get("error").and_then(|e| e.as_str()) {
return match error_msg.to_lowercase().as_str() {
"player not found" => Err(anyhow::Error::from(ApiError::NotFound)),
_ => Err(anyhow::anyhow!(error_msg.to_string())),
};
}
}
serde_json::from_str(response_text).map_err(Into::into)
}
}
#[cfg(feature = "ddnet")]
pub mod ddnet;
#[cfg(feature = "ddstats")]
pub mod ddstats;