use {crate::{content_types::{APPLICATION_JSON,
APPLICATION_PROTOBUF},
dual_format::DEBUG_FORMAT_HEADER,
error::{JetError,
Result}},
bytes::Bytes,
prost::Message,
reqwest::{Client,
Response},
serde::{Serialize,
de::DeserializeOwned},
std::time::Duration,
tracing::debug};
#[derive(Clone)]
pub struct JetClient {
base_url: String,
client: Client,
debug_key: Option<String>,
}
impl JetClient {
pub fn new(base_url: &str) -> Result<Self> {
let client = Client::builder().timeout(Duration::from_secs(30)).gzip(true).build()?;
Ok(Self {
base_url: base_url.trim_end_matches('/').to_string(),
client,
debug_key: None,
})
}
pub fn builder() -> JetClientBuilder {
JetClientBuilder::new()
}
pub async fn get<T>(&self, path: &str) -> Result<T>
where
T: Message + Default, {
let url = format!("{}{}", self.base_url, path);
debug!("GET {} (protobuf)", url);
let response = self
.client
.get(&url)
.header("Accept", APPLICATION_PROTOBUF)
.send()
.await?;
self.decode_protobuf_response(response).await
}
pub async fn post<Req, Res>(&self, path: &str, body: &Req) -> Result<Res>
where
Req: Message,
Res: Message + Default, {
let url = format!("{}{}", self.base_url, path);
debug!("POST {} (protobuf)", url);
let encoded = body.encode_to_vec();
let response = self
.client
.post(&url)
.header("Content-Type", APPLICATION_PROTOBUF)
.header("Accept", APPLICATION_PROTOBUF)
.body(encoded)
.send()
.await?;
self.decode_protobuf_response(response).await
}
pub async fn put<Req, Res>(&self, path: &str, body: &Req) -> Result<Res>
where
Req: Message,
Res: Message + Default, {
let url = format!("{}{}", self.base_url, path);
debug!("PUT {} (protobuf)", url);
let encoded = body.encode_to_vec();
let response = self
.client
.put(&url)
.header("Content-Type", APPLICATION_PROTOBUF)
.header("Accept", APPLICATION_PROTOBUF)
.body(encoded)
.send()
.await?;
self.decode_protobuf_response(response).await
}
pub async fn delete<T>(&self, path: &str) -> Result<T>
where
T: Message + Default, {
let url = format!("{}{}", self.base_url, path);
debug!("DELETE {} (protobuf)", url);
let response = self
.client
.delete(&url)
.header("Accept", APPLICATION_PROTOBUF)
.send()
.await?;
self.decode_protobuf_response(response).await
}
pub async fn post_raw(&self, path: &str, body: Bytes) -> Result<Bytes> {
let url = format!("{}{}", self.base_url, path);
debug!("POST (raw) {}", url);
let response = self
.client
.post(&url)
.header("Content-Type", APPLICATION_PROTOBUF)
.body(body)
.send()
.await?;
let bytes = response.bytes().await?;
Ok(bytes)
}
pub async fn get_json<T>(&self, path: &str) -> Result<T>
where
T: DeserializeOwned, {
let url = format!("{}{}", self.base_url, path);
debug!("GET {} (json)", url);
let mut request = self.client.get(&url).header("Accept", APPLICATION_JSON);
if let Some(key) = &self.debug_key {
request = request.header(DEBUG_FORMAT_HEADER, key.as_str());
}
let response = request.send().await?;
self.decode_json_response(response).await
}
pub async fn post_json<Req, Res>(&self, path: &str, body: &Req) -> Result<Res>
where
Req: Serialize,
Res: DeserializeOwned, {
let url = format!("{}{}", self.base_url, path);
debug!("POST {} (json)", url);
let json_body = serde_json::to_vec(body)?;
let mut request = self
.client
.post(&url)
.header("Content-Type", APPLICATION_JSON)
.header("Accept", APPLICATION_JSON)
.body(json_body);
if let Some(key) = &self.debug_key {
request = request.header(DEBUG_FORMAT_HEADER, key.as_str());
}
let response = request.send().await?;
self.decode_json_response(response).await
}
pub async fn put_json<Req, Res>(&self, path: &str, body: &Req) -> Result<Res>
where
Req: Serialize,
Res: DeserializeOwned, {
let url = format!("{}{}", self.base_url, path);
debug!("PUT {} (json)", url);
let json_body = serde_json::to_vec(body)?;
let mut request = self
.client
.put(&url)
.header("Content-Type", APPLICATION_JSON)
.header("Accept", APPLICATION_JSON)
.body(json_body);
if let Some(key) = &self.debug_key {
request = request.header(DEBUG_FORMAT_HEADER, key.as_str());
}
let response = request.send().await?;
self.decode_json_response(response).await
}
pub async fn delete_json<T>(&self, path: &str) -> Result<T>
where
T: DeserializeOwned, {
let url = format!("{}{}", self.base_url, path);
debug!("DELETE {} (json)", url);
let mut request = self.client.delete(&url).header("Accept", APPLICATION_JSON);
if let Some(key) = &self.debug_key {
request = request.header(DEBUG_FORMAT_HEADER, key.as_str());
}
let response = request.send().await?;
self.decode_json_response(response).await
}
pub async fn get_json_raw(&self, path: &str) -> Result<String> {
let url = format!("{}{}", self.base_url, path);
debug!("GET {} (json raw)", url);
let mut request = self.client.get(&url).header("Accept", APPLICATION_JSON);
if let Some(key) = &self.debug_key {
request = request.header(DEBUG_FORMAT_HEADER, key.as_str());
}
let response = request.send().await?;
let status = response.status();
if !status.is_success() {
let error_text = response.text().await.unwrap_or_default();
return Err(JetError::Internal(format!("HTTP {}: {}", status, error_text)));
}
let text = response.text().await?;
Ok(text)
}
async fn decode_protobuf_response<T>(&self, response: Response) -> Result<T>
where
T: Message + Default, {
let status = response.status();
if !status.is_success() {
let error_text = response.text().await.unwrap_or_default();
return Err(JetError::Internal(format!("HTTP {}: {}", status, error_text)));
}
let bytes = response.bytes().await?;
let decoded = T::decode(bytes)?;
Ok(decoded)
}
async fn decode_json_response<T>(&self, response: Response) -> Result<T>
where
T: DeserializeOwned, {
let status = response.status();
if !status.is_success() {
let error_text = response.text().await.unwrap_or_default();
return Err(JetError::Internal(format!("HTTP {}: {}", status, error_text)));
}
let bytes = response.bytes().await?;
let decoded = serde_json::from_slice(&bytes)?;
Ok(decoded)
}
}
pub struct JetClientBuilder {
base_url: Option<String>,
timeout: Duration,
gzip: bool,
debug_key: Option<String>,
}
impl JetClientBuilder {
fn new() -> Self {
Self {
base_url: None,
timeout: Duration::from_secs(30),
gzip: true,
debug_key: None,
}
}
pub fn base_url(mut self, url: &str) -> Self {
self.base_url = Some(url.trim_end_matches('/').to_string());
self
}
pub fn timeout(mut self, timeout: Duration) -> Self {
self.timeout = timeout;
self
}
pub fn gzip(mut self, enabled: bool) -> Self {
self.gzip = enabled;
self
}
pub fn debug_key(mut self, key: &str) -> Self {
self.debug_key = Some(key.to_string());
self
}
pub fn build(self) -> Result<JetClient> {
let base_url = self
.base_url
.ok_or_else(|| JetError::Internal("Base URL is required".to_string()))?;
let client = Client::builder().timeout(self.timeout).gzip(self.gzip).build()?;
Ok(JetClient {
base_url,
client,
debug_key: self.debug_key,
})
}
}