use crate::vault::VaultError;
use reqwest::{
header::{HeaderMap, HeaderName, HeaderValue},
Client, Method,
};
use serde_json::{json, Value};
use std::str::FromStr;
use std::time::Duration;
pub struct VaultClient {
pub addr: String,
pub token: String,
client: Client,
custom_headers: HeaderMap,
}
impl VaultClient {
pub fn new(addr: &str, token: &str) -> Result<Self, VaultError> {
let client = Client::builder()
.timeout(Duration::from_secs(30))
.build()
.map_err(|e| VaultError::Network(format!("Failed to create HTTP client: {}", e)))?;
Ok(Self {
addr: addr.to_string(),
token: token.to_string(),
client,
custom_headers: HeaderMap::new(),
})
}
pub fn add_header(&mut self, name: &str, value: &str) -> &mut Self {
if let (Ok(header_name), Ok(header_value)) =
(HeaderName::from_str(name), HeaderValue::from_str(value))
{
self.custom_headers.insert(header_name, header_value);
}
self
}
#[cfg(any(test, feature = "full-api"))]
pub async fn get(&self, path: &str) -> Result<Value, VaultError> {
self.request(Method::GET, path, None).await
}
pub async fn post_with_body(&self, path: &str, body: Value) -> Result<Value, VaultError> {
self.request(Method::POST, path, Some(body)).await
}
pub async fn put_with_body(&self, path: &str, body: Value) -> Result<Value, VaultError> {
self.request(Method::PUT, path, Some(body)).await
}
#[cfg(any(test, feature = "full-api"))]
pub async fn delete(&self, path: &str) -> Result<Value, VaultError> {
self.request(Method::DELETE, path, None).await
}
async fn request(
&self,
method: Method,
path: &str,
body: Option<Value>,
) -> Result<Value, VaultError> {
let url = format!("{}{}", self.addr, path);
let mut request = self.client.request(method, &url);
request = request.header("X-Vault-Token", &self.token);
for (name, value) in self.custom_headers.iter() {
request = request.header(name, value);
}
if let Some(json_body) = body {
request = request.json(&json_body);
}
let response = request
.send()
.await
.map_err(|e| VaultError::Network(format!("Request failed: {}", e)))?;
let status = response.status();
if !status.is_success() {
let error_text = response
.text()
.await
.unwrap_or_else(|_| "Unknown error".to_string());
return Err(VaultError::HttpStatus(status.as_u16(), error_text));
}
if status == reqwest::StatusCode::NO_CONTENT {
return Ok(json!({}));
}
response
.json::<Value>()
.await
.map_err(|e| VaultError::ParseError(format!("Failed to parse response: {}", e)))
}
}