use crate::error::KhromaError;
use crate::models::*;
use reqwest::{Client as ReqwestClient, Response};
use url::Url;
#[derive(Debug, Clone)]
pub struct KhromaClient {
client: ReqwestClient,
base_url: Url,
token: Option<String>,
}
impl KhromaClient {
pub fn new(base_url: &str, token: Option<String>) -> Result<Self, KhromaError> {
Ok(Self {
client: ReqwestClient::new(),
base_url: Url::parse(base_url)?,
token,
})
}
async fn handle_response<T: serde::de::DeserializeOwned>(
&self,
res: Response,
) -> Result<T, KhromaError> {
let status = res.status();
if status.is_success() {
res.json::<T>().await.map_err(|e| {
KhromaError::Parse(format!("Failed to deserialize successful response: {}", e))
})
} else {
let message = match res.json::<ErrorResponse>().await {
Ok(err_res) => err_res.message,
Err(_) => format!("Failed to parse error response. Status: {}", status),
};
Err(KhromaError::Api { status, message })
}
}
async fn handle_text_response(&self, res: Response) -> Result<String, KhromaError> {
let status = res.status();
if status.is_success() {
res.text().await.map_err(KhromaError::from)
} else {
let message = match res.json::<ErrorResponse>().await {
Ok(err_res) => err_res.message,
Err(_) => format!("Failed to parse error response. Status: {}", status),
};
Err(KhromaError::Api { status, message })
}
}
fn build_request<U: AsRef<str>>(
&self,
method: reqwest::Method,
path: U,
) -> Result<reqwest::RequestBuilder, KhromaError> {
let url = self.base_url.join(path.as_ref())?;
let mut builder = self.client.request(method, url);
if let Some(token) = &self.token {
builder = builder.header("x-chroma-token", token);
}
Ok(builder)
}
pub async fn get_user_identity(&self) -> Result<GetUserIdentityResponse, KhromaError> {
let req = self.build_request(reqwest::Method::GET, "/api/v2/auth/identity")?;
let res = req.send().await?;
self.handle_response(res).await
}
pub async fn healthcheck(&self) -> Result<String, KhromaError> {
let req = self.build_request(reqwest::Method::GET, "/api/v2/healthcheck")?;
let res = req.send().await?;
self.handle_text_response(res).await
}
pub async fn heartbeat(&self) -> Result<HeartbeatResponse, KhromaError> {
let req = self.build_request(reqwest::Method::GET, "/api/v2/heartbeat")?;
let res = req.send().await?;
self.handle_response(res).await
}
pub async fn pre_flight_checks(&self) -> Result<ChecklistResponse, KhromaError> {
let req = self.build_request(reqwest::Method::GET, "/api/v2/pre-flight-checks")?;
let res = req.send().await?;
self.handle_response(res).await
}
pub async fn reset(&self) -> Result<bool, KhromaError> {
let req = self.build_request(reqwest::Method::POST, "/api/v2/reset")?;
let res = req.send().await?;
let text = self.handle_text_response(res).await?;
text.parse::<bool>().map_err(|e| KhromaError::Parse(e.to_string()))
}
pub async fn version(&self) -> Result<String, KhromaError> {
let req = self.build_request(reqwest::Method::GET, "/api/v2/version")?;
let res = req.send().await?;
self.handle_text_response(res).await
}
pub async fn create_tenant(&self, payload: &CreateTenantPayload) -> Result<CreateTenantResponse, KhromaError> {
let req = self.build_request(reqwest::Method::POST, "/api/v2/tenants")?.json(payload);
let res = req.send().await?;
self.handle_response(res).await
}
pub async fn get_tenant(&self, tenant_name: &str) -> Result<GetTenantResponse, KhromaError> {
let path = format!("/api/v2/tenants/{}", tenant_name);
let req = self.build_request(reqwest::Method::GET, &path)?;
let res = req.send().await?;
self.handle_response(res).await
}
pub async fn list_databases(&self, tenant: &str, limit: Option<i32>, offset: Option<i32>) -> Result<Vec<Database>, KhromaError> {
let path = format!("/api/v2/tenants/{}/databases", tenant);
let mut req = self.build_request(reqwest::Method::GET, &path)?;
let mut query_params = Vec::new();
if let Some(l) = limit { query_params.push(("limit", l.to_string())); }
if let Some(o) = offset { query_params.push(("offset", o.to_string())); }
if !query_params.is_empty() {
req = req.query(&query_params);
}
let res = req.send().await?;
self.handle_response(res).await
}
pub async fn create_database(&self, tenant: &str, payload: &CreateDatabasePayload) -> Result<CreateDatabaseResponse, KhromaError> {
let path = format!("/api/v2/tenants/{}/databases", tenant);
let req = self.build_request(reqwest::Method::POST, &path)?.json(payload);
let res = req.send().await?;
self.handle_response(res).await
}
pub async fn get_database(&self, tenant: &str, database: &str) -> Result<Database, KhromaError> {
let path = format!("/api/v2/tenants/{}/databases/{}", tenant, database);
let req = self.build_request(reqwest::Method::GET, &path)?;
let res = req.send().await?;
self.handle_response(res).await
}
pub async fn delete_database(&self, tenant: &str, database: &str) -> Result<DeleteDatabaseResponse, KhromaError> {
let path = format!("/api/v2/tenants/{}/databases/{}", tenant, database);
let req = self.build_request(reqwest::Method::DELETE, &path)?;
let res = req.send().await?;
self.handle_response(res).await
}
pub async fn list_collections(&self, tenant: &str, database: &str, limit: Option<i32>, offset: Option<i32>) -> Result<Vec<Collection>, KhromaError> {
let path = format!("/api/v2/tenants/{}/databases/{}/collections", tenant, database);
let mut req = self.build_request(reqwest::Method::GET, &path)?;
let mut query_params = Vec::new();
if let Some(l) = limit { query_params.push(("limit", l.to_string())); }
if let Some(o) = offset { query_params.push(("offset", o.to_string())); }
if !query_params.is_empty() {
req = req.query(&query_params);
}
let res = req.send().await?;
self.handle_response(res).await
}
pub async fn create_collection(&self, tenant: &str, database: &str, payload: &CreateCollectionPayload) -> Result<Collection, KhromaError> {
let path = format!("/api/v2/tenants/{}/databases/{}/collections", tenant, database);
let req = self.build_request(reqwest::Method::POST, &path)?.json(payload);
let res = req.send().await?;
self.handle_response(res).await
}
pub async fn get_collection(&self, tenant: &str, database: &str, collection_id: &str) -> Result<Collection, KhromaError> {
let path = format!("/api/v2/tenants/{}/databases/{}/collections/{}", tenant, database, collection_id);
let req = self.build_request(reqwest::Method::GET, &path)?;
let res = req.send().await?;
self.handle_response(res).await
}
pub async fn update_collection(&self, tenant: &str, database: &str, collection_id: &str, payload: &UpdateCollectionPayload) -> Result<UpdateCollectionResponse, KhromaError> {
let path = format!("/api/v2/tenants/{}/databases/{}/collections/{}", tenant, database, collection_id);
let req = self.build_request(reqwest::Method::PUT, &path)?.json(payload);
let res = req.send().await?;
self.handle_response(res).await
}
pub async fn delete_collection(&self, tenant: &str, database: &str, collection_id: &str) -> Result<UpdateCollectionResponse, KhromaError> {
let path = format!("/api/v2/tenants/{}/databases/{}/collections/{}", tenant, database, collection_id);
let req = self.build_request(reqwest::Method::DELETE, &path)?;
let res = req.send().await?;
self.handle_response(res).await
}
pub async fn collection_add(&self, tenant: &str, database: &str, collection_id: &str, payload: &AddCollectionRecordsPayload) -> Result<AddCollectionRecordsResponse, KhromaError> {
let path = format!("/api/v2/tenants/{}/databases/{}/collections/{}/add", tenant, database, collection_id);
let req = self.build_request(reqwest::Method::POST, &path)?.json(payload);
let res = req.send().await?;
self.handle_response(res).await
}
pub async fn collection_count(&self, tenant: &str, database: &str, collection_id: &str) -> Result<u32, KhromaError> {
let path = format!("/api/v2/tenants/{}/databases/{}/collections/{}/count", tenant, database, collection_id);
let req = self.build_request(reqwest::Method::GET, &path)?;
let res = req.send().await?;
self.handle_response(res).await
}
pub async fn collection_delete(&self, tenant: &str, database: &str, collection_id: &str, payload: &DeleteCollectionRecordsPayload) -> Result<DeleteCollectionRecordsResponse, KhromaError> {
let path = format!("/api/v2/tenants/{}/databases/{}/collections/{}/delete", tenant, database, collection_id);
let req = self.build_request(reqwest::Method::POST, &path)?.json(payload);
let res = req.send().await?;
self.handle_response(res).await
}
pub async fn fork_collection(&self, tenant: &str, database: &str, collection_id: &str, payload: &ForkCollectionPayload) -> Result<Collection, KhromaError> {
let path = format!("/api/v2/tenants/{}/databases/{}/collections/{}/fork", tenant, database, collection_id);
let req = self.build_request(reqwest::Method::POST, &path)?.json(payload);
let res = req.send().await?;
self.handle_response(res).await
}
pub async fn collection_get(&self, tenant: &str, database: &str, collection_id: &str, payload: &GetRequestPayload) -> Result<GetResponse, KhromaError> {
let path = format!("/api/v2/tenants/{}/databases/{}/collections/{}/get", tenant, database, collection_id);
let req = self.build_request(reqwest::Method::POST, &path)?.json(payload);
let res = req.send().await?;
self.handle_response(res).await
}
pub async fn collection_query(&self, tenant: &str, database: &str, collection_id: &str, limit: Option<i32>, offset: Option<i32>, payload: &QueryRequestPayload) -> Result<QueryResponse, KhromaError> {
let path = format!("/api/v2/tenants/{}/databases/{}/collections/{}/query", tenant, database, collection_id);
let mut req = self.build_request(reqwest::Method::POST, &path)?.json(payload);
let mut query_params = Vec::new();
if let Some(l) = limit { query_params.push(("limit", l.to_string())); }
if let Some(o) = offset { query_params.push(("offset", o.to_string())); }
if !query_params.is_empty() {
req = req.query(&query_params);
}
let res = req.send().await?;
self.handle_response(res).await
}
pub async fn collection_update(&self, tenant: &str, database: &str, collection_id: &str, payload: &UpdateCollectionRecordsPayload) -> Result<UpdateCollectionRecordsResponse, KhromaError> {
let path = format!("/api/v2/tenants/{}/databases/{}/collections/{}/update", tenant, database, collection_id);
let req = self.build_request(reqwest::Method::POST, &path)?.json(payload);
let res = req.send().await?;
self.handle_response(res).await
}
pub async fn collection_upsert(&self, tenant: &str, database: &str, collection_id: &str, payload: &UpsertCollectionRecordsPayload) -> Result<UpsertCollectionRecordsResponse, KhromaError> {
let path = format!("/api/v2/tenants/{}/databases/{}/collections/{}/upsert", tenant, database, collection_id);
let req = self.build_request(reqwest::Method::POST, &path)?.json(payload);
let res = req.send().await?;
self.handle_response(res).await
}
pub async fn count_collections(&self, tenant: &str, database: &str) -> Result<u32, KhromaError> {
let path = format!("/api/v2/tenants/{}/databases/{}/collections_count", tenant, database);
let req = self.build_request(reqwest::Method::GET, &path)?;
let res = req.send().await?;
self.handle_response(res).await
}
}