use http_cache_reqwest::{Cache, HttpCache, HttpCacheOptions};
use reqwest::{Client, IntoUrl, Url};
use reqwest_middleware::{ClientBuilder, ClientWithMiddleware};
use serde::de::DeserializeOwned;
use crate::error::Error;
pub use http_cache_reqwest::{CACacheManager, CacheMode, CacheOptions};
#[derive(Clone)]
pub enum Environment {
Production,
Staging,
Custom(String),
}
impl Default for Environment {
fn default() -> Self {
Self::Production
}
}
impl TryFrom<Environment> for Url {
type Error = Error;
fn try_from(value: Environment) -> Result<Self, Self::Error> {
match value {
Environment::Production => Ok(Url::parse("https://pokeapi.co/api/v2/").unwrap()),
Environment::Staging => Ok(Url::parse("https://staging.pokeapi.co/api/v2/").unwrap()),
Environment::Custom(s) => s
.ends_with('/')
.then(|| Url::parse(&s))
.ok_or_else(|| Error::NoTrailingSlash(s.clone()))?
.map_err(|_| Error::UrlParse(s)),
}
}
}
pub struct RustemonClientBuilder {
cache: HttpCache<CACacheManager>,
environment: Environment,
}
impl Default for RustemonClientBuilder {
fn default() -> Self {
Self {
cache: HttpCache {
mode: CacheMode::Default,
manager: CACacheManager::default(),
options: HttpCacheOptions::default(),
},
environment: Environment::default(),
}
}
}
impl RustemonClientBuilder {
pub fn with_mode(&mut self, cache_mode: CacheMode) -> &mut Self {
self.cache.mode = cache_mode;
self
}
pub fn with_manager(&mut self, manager: CACacheManager) -> &mut Self {
self.cache.manager = manager;
self
}
pub fn with_options(&mut self, options: CacheOptions) -> &mut Self {
self.cache.options.cache_options = Some(options);
self
}
pub fn with_environment(&mut self, environment: Environment) -> &mut Self {
self.environment = environment;
self
}
pub fn try_build(&self) -> Result<RustemonClient, Error> {
Ok(RustemonClient {
client: ClientBuilder::new(Client::new())
.with(Cache(self.cache.clone()))
.build(),
base: Url::try_from(self.environment.clone())?,
})
}
}
pub struct RustemonClient {
client: ClientWithMiddleware,
base: Url,
}
pub(crate) enum Id<'a> {
Int(i64),
Str(&'a str),
}
impl RustemonClient {
async fn inner_get<T>(&self, url: Url) -> Result<T, Error>
where
T: DeserializeOwned,
{
Ok(self.client.get(url).send().await?.json().await?)
}
pub(crate) async fn get_by_endpoint<T>(&self, endpoint: &str) -> Result<T, Error>
where
T: DeserializeOwned,
{
let url = self
.base
.join(endpoint)
.map_err(|_| Error::UrlParse(format!("{}/{endpoint}", self.base)))?;
self.inner_get(url).await
}
pub(crate) async fn get_with_limit_and_offset<T>(
&self,
endpoint: &str,
limit: i64,
offset: i64,
) -> Result<T, Error>
where
T: DeserializeOwned,
{
let mut url = self
.base
.join(endpoint)
.map_err(|_| Error::UrlParse(format!("{}/{endpoint}", self.base)))?;
url.set_query(Some(&format!("limit={limit}&offset={offset}")));
self.inner_get(url).await
}
pub(crate) async fn get_by_endpoint_and_id<'a, T>(
&self,
endpoint: &str,
id: Id<'a>,
) -> Result<T, Error>
where
T: DeserializeOwned,
{
let inner_id = match id {
Id::Int(i) => i.to_string(),
Id::Str(s) => s.to_owned(),
};
let url = self
.base
.join(&format!("{endpoint}/{inner_id}"))
.map_err(|_| Error::UrlParse(format!("{}/{endpoint}/{inner_id}", self.base)))?;
self.inner_get(url).await
}
pub(crate) async fn get_by_url<T>(&self, url: impl IntoUrl) -> Result<T, Error>
where
T: DeserializeOwned,
{
self.inner_get(url.into_url()?).await
}
}
impl Default for RustemonClient {
fn default() -> Self {
Self {
client: ClientBuilder::new(Client::new())
.with(Cache(HttpCache {
mode: CacheMode::Default,
manager: CACacheManager::default(),
options: HttpCacheOptions::default(),
}))
.build(),
base: Url::try_from(Environment::default()).unwrap(),
}
}
}