use std::time::Duration;
use reqwest::{Client, Method, Response};
use serde_json::Value;
use crate::config::ConnectionConfig;
use crate::error::{Error, Result};
#[derive(Clone)]
pub(crate) struct ControlClient {
config: ConnectionConfig,
client: Client,
}
impl ControlClient {
pub(crate) fn new(config: ConnectionConfig) -> Result<Self> {
let client = Client::builder()
.timeout(Duration::from_secs(config.request_timeout_secs))
.build()?;
Ok(Self { config, client })
}
pub(crate) async fn get(&self, path: &str) -> Result<Value> {
self.request(Method::GET, path, None).await
}
pub(crate) async fn post(&self, path: &str, body: Value) -> Result<Value> {
self.request(Method::POST, path, Some(body)).await
}
pub(crate) async fn delete(&self, path: &str) -> Result<Value> {
self.request(Method::DELETE, path, None).await
}
async fn request(&self, method: Method, path: &str, body: Option<Value>) -> Result<Value> {
let api_key = self.config.api_key.as_ref().ok_or(Error::MissingApiKey)?;
let mut request = self
.client
.request(method, join_url(&self.config.api_url, path))
.bearer_auth(api_key);
if let Some(body) = body {
request = request.json(&body);
}
json_response(request.send().await?).await
}
}
#[derive(Clone)]
pub(crate) struct DataPlaneClient {
pub(crate) base_url: String,
pub(crate) token: String,
client: Client,
}
impl DataPlaneClient {
pub(crate) fn new(base_url: String, token: String, config: &ConnectionConfig) -> Result<Self> {
let client = Client::builder()
.timeout(Duration::from_secs(config.request_timeout_secs))
.build()?;
Ok(Self {
base_url,
token,
client,
})
}
pub(crate) async fn get_json(&self, path: &str) -> Result<Value> {
json_response(
self.client
.get(join_url(&self.base_url, path))
.bearer_auth(&self.token)
.send()
.await?,
)
.await
}
pub(crate) async fn post_json(&self, path: &str, body: Value) -> Result<Value> {
json_response(
self.client
.post(join_url(&self.base_url, path))
.bearer_auth(&self.token)
.json(&body)
.send()
.await?,
)
.await
}
pub(crate) async fn delete_json(&self, path: &str) -> Result<Value> {
json_response(
self.client
.delete(join_url(&self.base_url, path))
.bearer_auth(&self.token)
.send()
.await?,
)
.await
}
pub(crate) async fn get_bytes(&self, path: &str) -> Result<Vec<u8>> {
let response = self
.client
.get(join_url(&self.base_url, path))
.bearer_auth(&self.token)
.send()
.await?;
if !response.status().is_success() {
return Err(Error::from_status(
response.status(),
&read_json_or_text(response).await?,
));
}
Ok(response.bytes().await?.to_vec())
}
pub(crate) async fn put_bytes(&self, path: &str, data: Vec<u8>) -> Result<Value> {
json_response(
self.client
.put(join_url(&self.base_url, path))
.bearer_auth(&self.token)
.header("content-type", "application/octet-stream")
.body(data)
.send()
.await?,
)
.await
}
}
async fn json_response(response: Response) -> Result<Value> {
let status = response.status();
let payload = read_json_or_text(response).await?;
if !status.is_success() {
return Err(Error::from_status(status, &payload));
}
Ok(payload)
}
async fn read_json_or_text(response: Response) -> Result<Value> {
let text = response.text().await?;
if text.trim().is_empty() {
return Ok(Value::Object(Default::default()));
}
Ok(serde_json::from_str(&text).unwrap_or_else(|_| serde_json::json!({ "message": text })))
}
pub(crate) fn join_url(base: &str, path: &str) -> String {
format!(
"{}{}",
base.trim_end_matches('/'),
if path.starts_with('/') {
path.to_string()
} else {
format!("/{path}")
}
)
}