148 lines
3.7 KiB
Rust
148 lines
3.7 KiB
Rust
use crate::api::{Category, CatsResp, Meme, MemesResp, User, UsersResp};
|
|
use log::info;
|
|
use once_cell::sync::OnceCell;
|
|
use reqwest::Url;
|
|
use serde::Deserialize;
|
|
use std::sync::Arc;
|
|
use thiserror::Error;
|
|
use tokio::sync::Mutex;
|
|
|
|
#[derive(Debug)]
|
|
pub struct JMClient {
|
|
pub http: reqwest::Client,
|
|
cache: Mutex<Cache>,
|
|
endpoint: Url,
|
|
}
|
|
|
|
macro_rules! init_cache {
|
|
($cell:expr, $init_fn:expr) => {{
|
|
let cell = &$cell;
|
|
Arc::clone(match cell.get() {
|
|
Some(x) => x,
|
|
None => {
|
|
let x = Arc::new($init_fn);
|
|
cell.get_or_init(|| x)
|
|
},
|
|
})
|
|
}};
|
|
}
|
|
|
|
impl JMClient {
|
|
pub fn new() -> Self {
|
|
Self::builder().build()
|
|
}
|
|
|
|
pub fn builder() -> JMClientBuilder {
|
|
JMClientBuilder::default()
|
|
}
|
|
|
|
/// clears the cache. all requests will be made again after this has been called.
|
|
pub async fn clear_cache(&mut self) {
|
|
self.cache.lock().await.clear();
|
|
}
|
|
|
|
async fn get_api_json<R, T, F>(
|
|
&self,
|
|
cache: &OnceCell<Arc<T>>,
|
|
endpoint: &str,
|
|
res_to_data: F,
|
|
) -> Result<Arc<T>, JMClientError>
|
|
where
|
|
for<'de> R: Deserialize<'de>,
|
|
F: FnOnce(R) -> T,
|
|
{
|
|
Ok(init_cache!(cache, {
|
|
info!("Requesting {} from server", endpoint);
|
|
let url = self.endpoint.join(endpoint)?;
|
|
let res = self.http.get(url).send().await?;
|
|
|
|
let res = serde_json::from_slice(&res.bytes().await?)?;
|
|
res_to_data(res)
|
|
}))
|
|
}
|
|
|
|
pub async fn get_cats(&self) -> Result<Arc<Vec<Category>>, JMClientError> {
|
|
self.get_api_json(
|
|
&self.cache.lock().await.cats,
|
|
"categories",
|
|
|r: CatsResp| r.categories,
|
|
)
|
|
.await
|
|
}
|
|
|
|
pub async fn get_memes(&self) -> Result<Arc<Vec<Meme>>, JMClientError> {
|
|
self.get_api_json(&self.cache.lock().await.memes, "memes", |r: MemesResp| {
|
|
r.memes
|
|
})
|
|
.await
|
|
}
|
|
|
|
pub async fn get_users(&self) -> Result<Arc<Vec<User>>, JMClientError> {
|
|
self.get_api_json(&self.cache.lock().await.users, "users", |r: UsersResp| {
|
|
r.users
|
|
})
|
|
.await
|
|
}
|
|
}
|
|
impl Default for JMClient {
|
|
fn default() -> Self {
|
|
Self::new()
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Error)]
|
|
pub enum JMClientError {
|
|
#[error("Error making http request: {0}")]
|
|
Http(#[from] reqwest::Error),
|
|
|
|
#[error("Error deserializing JensMemes response: {0}")]
|
|
Deserialize(#[from] serde_json::Error),
|
|
|
|
#[error("Failed parsing URL to make request to JensMemes: {0}")]
|
|
UrlParse(#[from] url::ParseError),
|
|
}
|
|
|
|
#[derive(Debug, Default)]
|
|
struct Cache {
|
|
users: OnceCell<Arc<Vec<User>>>,
|
|
cats: OnceCell<Arc<Vec<Category>>>,
|
|
memes: OnceCell<Arc<Vec<Meme>>>,
|
|
}
|
|
|
|
impl Cache {
|
|
fn clear(&mut self) {
|
|
self.users = OnceCell::default();
|
|
self.cats = OnceCell::default();
|
|
self.memes = OnceCell::default();
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Default)]
|
|
pub struct JMClientBuilder {
|
|
client: Option<reqwest::Client>,
|
|
endpoint: Option<Url>,
|
|
}
|
|
|
|
impl JMClientBuilder {
|
|
pub fn build(self) -> JMClient {
|
|
JMClient {
|
|
http: self.client.unwrap_or_else(reqwest::Client::new),
|
|
endpoint: self.endpoint.unwrap_or_else(|| {
|
|
// unwrapping is fine here, as the hardcoded input is known to work.
|
|
Url::parse(crate::util::consts::API_ENDPOINT).unwrap()
|
|
}),
|
|
cache: Mutex::new(Cache::default()),
|
|
}
|
|
}
|
|
|
|
pub fn client(mut self, client: reqwest::Client) -> Self {
|
|
self.client = Some(client);
|
|
self
|
|
}
|
|
|
|
pub fn endpoint(mut self, endpoint: impl Into<Url>) -> Self {
|
|
self.endpoint = Some(endpoint.into());
|
|
self
|
|
}
|
|
}
|