use http_cache_reqwest::{Cache, CacheManager, 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::{CacheMode, CacheOptions};
pub use http_cache_reqwest::{CACacheManager, MokaManager};
#[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(Self::parse("https://pokeapi.co/api/v2/").unwrap()),
Environment::Staging => Ok(Self::parse("https://staging.pokeapi.co/api/v2/").unwrap()),
Environment::Custom(mut s) => {
if !s.ends_with('/') {
s.push('/');
}
Self::parse(&s).map_err(|_| Error::UrlParse(s))
}
}
}
}
pub struct RustemonClientBuilder<T: CacheManager> {
cache: HttpCache<T>,
environment: Environment,
}
impl Default for RustemonClientBuilder<CACacheManager> {
fn default() -> Self {
let manager = CACacheManager::new("./rustemon-cache".into(), false);
Self {
cache: HttpCache {
mode: CacheMode::Default,
manager,
options: HttpCacheOptions::default(),
},
environment: Environment::default(),
}
}
}
impl Default for RustemonClientBuilder<MokaManager> {
fn default() -> Self {
Self {
cache: HttpCache {
mode: CacheMode::Default,
manager: MokaManager::default(),
options: HttpCacheOptions::default(),
},
environment: Environment::default(),
}
}
}
impl<T: CacheManager> RustemonClientBuilder<T> {
pub const fn with_mode(mut self, cache_mode: CacheMode) -> Self {
self.cache.mode = cache_mode;
self
}
pub fn with_manager(mut self, manager: T) -> Self {
self.cache.manager = manager;
self
}
pub const fn with_options(mut self, options: CacheOptions) -> Self {
self.cache.options.cache_options = Some(options);
self
}
pub fn with_environment(mut self, environment: Environment) -> Self {
self.environment = environment;
self
}
pub fn try_build(self) -> Result<RustemonClient, Error> {
Ok(RustemonClient {
client: ClientBuilder::new(Client::new())
.with(Cache(self.cache))
.build(),
base: Url::try_from(self.environment)?,
})
}
}
#[derive(Debug)]
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<T>(
&self,
endpoint: &str,
id: Id<'_>,
) -> Result<T, Error>
where
T: DeserializeOwned,
{
let endpoint_id = match id {
Id::Int(i) => format!("{endpoint}/{i}"),
Id::Str(s) => format!("{endpoint}/{s}"),
};
let url = self
.base
.join(&endpoint_id)
.map_err(|_| Error::UrlParse(format!("{}/{endpoint_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 {
let manager = CACacheManager::new("./rustemon-cache".into(), false);
Self {
client: ClientBuilder::new(Client::new())
.with(Cache(HttpCache {
mode: CacheMode::Default,
manager,
options: HttpCacheOptions::default(),
}))
.build(),
base: Url::try_from(Environment::default()).unwrap(),
}
}
}