use reqwest::{Client, Method};
use serde::Serialize;
use serde_json::Value;
use tokio::time::sleep;
use crate::errors::{KeplarsError, KeplarsResult};
use crate::models::KeplarsConfig;
use crate::utils::exponential_backoff;
const API_KEY_REGEX: &str = r"^kms_[a-f0-9]+\.(live|adm)_[a-f0-9]+$";
pub struct Keplars {
pub(crate) config: KeplarsConfig,
pub(crate) http: Client,
}
impl Keplars {
pub fn new(api_key: impl Into<String>) -> KeplarsResult<Self> {
Self::with_config(KeplarsConfig {
api_key: api_key.into(),
..Default::default()
})
}
pub fn with_config(config: KeplarsConfig) -> KeplarsResult<Self> {
if config.api_key.is_empty() {
return Err(KeplarsError::Validation {
message: "API key is required".to_string(),
status_code: 0,
});
}
let re = regex::Regex::new(API_KEY_REGEX).unwrap();
if !re.is_match(&config.api_key) {
return Err(KeplarsError::Validation {
message: "Invalid API key format. Expected: kms_<id>.live_<secret> or kms_<id>.adm_<secret>".to_string(),
status_code: 0,
});
}
let http = Client::builder()
.timeout(config.timeout)
.user_agent("keplars-rust/1.1.0")
.build()
.map_err(KeplarsError::Network)?;
Ok(Self { config, http })
}
pub(crate) async fn request<T, B>(
&self,
method: Method,
path: &str,
body: Option<&B>,
retry_count: u32,
) -> KeplarsResult<T>
where
T: serde::de::DeserializeOwned,
B: Serialize,
{
let url = format!("{}{}", self.config.base_url, path);
let mut req = self
.http
.request(method.clone(), &url)
.header("Authorization", format!("Bearer {}", self.config.api_key))
.header("Content-Type", "application/json");
if let Some(b) = body {
req = req.json(b);
}
let response = match req.send().await {
Ok(r) => r,
Err(e) => {
if retry_count < self.config.max_retries {
let delay = exponential_backoff(retry_count, self.config.retry_delay);
sleep(delay).await;
return Box::pin(self.request(method, path, body, retry_count + 1)).await;
}
return Err(KeplarsError::Network(e));
}
};
let status = response.status();
if status.is_success() {
let data = response.json::<T>().await.map_err(KeplarsError::Network)?;
return Ok(data);
}
let status_u16 = status.as_u16();
let error_body: Value = response.json().await.unwrap_or(Value::Null);
let message = error_body
.get("message")
.and_then(|v| v.as_str())
.unwrap_or("Unknown error")
.to_string();
if (status_u16 >= 500) && retry_count < self.config.max_retries {
let delay = exponential_backoff(retry_count, self.config.retry_delay);
sleep(delay).await;
return Box::pin(self.request(method, path, body, retry_count + 1)).await;
}
Err(KeplarsError::from_status(status_u16, message))
}
pub(crate) async fn get<T>(&self, path: &str) -> KeplarsResult<T>
where
T: serde::de::DeserializeOwned,
{
self.request::<T, ()>(Method::GET, path, None, 0).await
}
pub(crate) async fn post<T, B>(&self, path: &str, body: &B) -> KeplarsResult<T>
where
T: serde::de::DeserializeOwned,
B: Serialize,
{
self.request(Method::POST, path, Some(body), 0).await
}
pub(crate) async fn patch<T, B>(&self, path: &str, body: &B) -> KeplarsResult<T>
where
T: serde::de::DeserializeOwned,
B: Serialize,
{
self.request(Method::PATCH, path, Some(body), 0).await
}
pub(crate) async fn delete<T>(&self, path: &str) -> KeplarsResult<T>
where
T: serde::de::DeserializeOwned,
{
self.request::<T, ()>(Method::DELETE, path, None, 0).await
}
pub(crate) async fn delete_with_body<T, B>(&self, path: &str, body: &B) -> KeplarsResult<T>
where
T: serde::de::DeserializeOwned,
B: Serialize,
{
self.request(Method::DELETE, path, Some(body), 0).await
}
}