use std::{error::Error, sync::Arc};
use reqwest::Client;
use serde_json::{json, Value};
use super::store::{send, ChromaDistance, Store};
use crate::embedding::Embedder;
pub struct StoreBuilder {
client: Option<Client>,
url: String,
tenant: String,
database: String,
api_key: Option<String>,
collection_name: Option<String>,
embedder: Option<Arc<dyn Embedder>>,
distance: ChromaDistance,
namespace_key: String,
collection_metadata: Option<Value>,
}
impl Default for StoreBuilder {
fn default() -> Self {
Self::new()
}
}
impl StoreBuilder {
pub fn new() -> Self {
StoreBuilder {
client: None,
url: "http://localhost:8000".to_string(),
tenant: "default_tenant".to_string(),
database: "default_database".to_string(),
api_key: None,
collection_name: None,
embedder: None,
distance: ChromaDistance::default(),
namespace_key: "namespace".to_string(),
collection_metadata: None,
}
}
pub fn url(mut self, url: &str) -> Self {
self.url = url.trim_end_matches('/').to_string();
self
}
pub fn tenant(mut self, tenant: &str) -> Self {
self.tenant = tenant.to_string();
self
}
pub fn database(mut self, database: &str) -> Self {
self.database = database.to_string();
self
}
pub fn api_key(mut self, api_key: &str) -> Self {
self.api_key = Some(api_key.to_string());
self
}
pub fn client(mut self, client: Client) -> Self {
self.client = Some(client);
self
}
pub fn collection_name(mut self, collection_name: &str) -> Self {
self.collection_name = Some(collection_name.to_string());
self
}
pub fn embedder<E: Embedder + 'static>(mut self, embedder: E) -> Self {
self.embedder = Some(Arc::new(embedder));
self
}
pub fn distance(mut self, distance: ChromaDistance) -> Self {
self.distance = distance;
self
}
pub fn namespace_key(mut self, key: &str) -> Self {
self.namespace_key = key.to_string();
self
}
pub fn collection_metadata(mut self, metadata: Value) -> Self {
self.collection_metadata = Some(metadata);
self
}
pub async fn build(self) -> Result<Store, Box<dyn Error>> {
let collection_name = self
.collection_name
.ok_or("'collection_name' is required")?;
let embedder = self.embedder.ok_or("'embedder' is required")?;
let client = self.client.unwrap_or_default();
let collections_url = format!(
"{}/api/v2/tenants/{}/databases/{}/collections",
self.url, self.tenant, self.database
);
let mut body = json!({
"name": collection_name,
"get_or_create": true,
"configuration": { "hnsw": { "space": self.distance.as_str() } },
});
if let Some(metadata) = self.collection_metadata {
body["metadata"] = metadata;
}
let collection = send(client.post(&collections_url).json(&body), &self.api_key).await?;
let id = collection["id"]
.as_str()
.ok_or("Chroma didn't return a collection id")?;
Ok(Store {
client,
collection_url: format!("{collections_url}/{id}"),
api_key: self.api_key,
embedder,
distance: self.distance,
namespace_key: self.namespace_key,
})
}
}