63 lines
2.2 KiB
Rust
63 lines
2.2 KiB
Rust
use crate::model::AppError;
|
|
|
|
pub struct IonosClient {
|
|
base_url: String,
|
|
token: String,
|
|
http: reqwest::blocking::Client,
|
|
}
|
|
|
|
fn expand_tilde(p: &str) -> String {
|
|
if let Some(rest) = p.strip_prefix("~/") {
|
|
if let Some(home) = std::env::var_os("HOME") {
|
|
return format!("{}/{}", home.to_string_lossy(), rest);
|
|
}
|
|
}
|
|
p.to_string()
|
|
}
|
|
|
|
impl IonosClient {
|
|
pub fn new(base_url: &str, token_path: &str) -> Result<Self, AppError> {
|
|
let path = expand_tilde(token_path);
|
|
let token = std::fs::read_to_string(&path)
|
|
.map_err(|e| AppError::Config(format!("token {path}: {e}")))?
|
|
.trim().to_string();
|
|
if token.is_empty() {
|
|
return Err(AppError::Config(format!("empty token at {path}")));
|
|
}
|
|
let http = reqwest::blocking::Client::builder()
|
|
.timeout(std::time::Duration::from_secs(60))
|
|
.build()
|
|
.map_err(|e| AppError::Ionos(format!("client: {e}")))?;
|
|
Ok(Self { base_url: base_url.trim_end_matches('/').to_string(), token, http })
|
|
}
|
|
|
|
/// POST with up to 3 retries on transport / 5xx, exponential backoff.
|
|
pub fn post_json<T: serde::de::DeserializeOwned>(
|
|
&self, path: &str, body: &serde_json::Value,
|
|
) -> Result<T, AppError> {
|
|
let url = format!("{}{}", self.base_url, path);
|
|
let mut last = String::new();
|
|
for attempt in 0..3 {
|
|
let r = self.http.post(&url)
|
|
.bearer_auth(&self.token)
|
|
.json(body)
|
|
.send();
|
|
match r {
|
|
Ok(resp) if resp.status().is_success() => {
|
|
return resp.json::<T>()
|
|
.map_err(|e| AppError::Ionos(format!("decode: {e}")));
|
|
}
|
|
Ok(resp) => {
|
|
last = format!("HTTP {}", resp.status());
|
|
if !resp.status().is_server_error() {
|
|
return Err(AppError::Ionos(last));
|
|
}
|
|
}
|
|
Err(e) => last = format!("transport: {e}"),
|
|
}
|
|
std::thread::sleep(std::time::Duration::from_millis(300 * (1 << attempt)));
|
|
}
|
|
Err(AppError::Ionos(format!("giving up after retries: {last}")))
|
|
}
|
|
}
|