turbopuffer_client/
lib.rs

1use error::Error;
2use response::{
3  DeleteResponse,
4  QueryResponse,
5  ResponseVector,
6  UpsertResponse,
7};
8use serde_json::{
9  from_value,
10  Value,
11};
12
13pub mod error;
14pub mod response;
15
16const BASE_URL: &str = "https://api.turbopuffer.com/v1";
17
18#[derive(Clone)]
19pub struct Client {
20  api_key: String,
21  client: reqwest::Client,
22}
23
24impl Client {
25  /// Create a new client with the given API key.
26  ///
27  /// Example:
28  ///
29  /// ```
30  /// use turbopuffer_client::Client;
31  ///
32  /// let api_key = "secret";
33  /// let client = Client::new(api_key);
34  /// ```
35  ///
36  /// Panics: This method panics if a TLS backend cannot be initialized, or the
37  /// resolver cannot load the system configuration.
38  pub fn new(api_key: &str) -> Self {
39    Self {
40      api_key: api_key.to_string(),
41      client: reqwest::Client::new(),
42    }
43  }
44
45  /// Scope the client to a namespace. All following operations will run on
46  /// this namespace.
47  ///
48  /// Example:
49  ///
50  /// ```
51  /// use turbopuffer_client::Client;
52  ///
53  /// let ns = Client::new("secret").namespace("test");
54  /// ```
55  pub fn namespace<'a>(&'a self, namespace: &'a str) -> NamespacedClient<'a> {
56    NamespacedClient {
57      client: self,
58      namespace,
59    }
60  }
61}
62
63pub struct NamespacedClient<'a> {
64  client: &'a Client,
65  namespace: &'a str,
66}
67
68impl<'a> NamespacedClient<'a> {
69  /// Upsert a vector into a namespace. This creates the namespace if it does
70  /// not yet have any vectors.
71  ///
72  /// Example:
73  ///
74  /// ```
75  /// use turbopuffer_client::Client;
76  ///
77  /// let ns = Client::new("secret").namespace("test");
78  ///
79  /// let vectors = serde_json::json!({
80  ///   "ids": [1, 2, 3, 4],
81  ///   "vectors": [[0.1, 0.1], [0.2, 0.2], [0.3, 0.3], [0.4, 0.4]],
82  ///   "attributes": {
83  ///     "my-string": ["one", null, "three", "four"],
84  ///     "my-uint": [12, null, 84, 39],
85  ///     "my-string-array": [["a", "b"], ["b", "d"], [], ["c"]]
86  ///   }
87  /// });
88  /// ````
89  ///
90  /// ```ignore
91  /// let res = ns.upsert(&vectors).await.unwrap();
92  /// ```
93  pub async fn upsert(&self, body: &Value) -> Result<UpsertResponse, Error> {
94    let url = format!("{BASE_URL}/vectors/{}", &self.namespace);
95    let res = self
96      .client
97      .client
98      .post(url)
99      .header("Authorization", format!("Bearer {}", self.client.api_key))
100      .header("Content-Type", "application/json")
101      .json(body)
102      .send()
103      .await?;
104
105    let body = res.text().await.map_err(error::request_error)?;
106    let value = serde_json::from_str::<Value>(&body)
107      .map_err(|e| error::non_json(e, body))?;
108
109    // TODO: Remove or defer clone.
110    from_value::<UpsertResponse>(value.clone())
111      .map_err(|e| error::invalid_response(e, value))
112  }
113
114  /// Query the namespace for matching vectors.
115  ///
116  /// Example:
117  ///
118  /// ```
119  /// use turbopuffer_client::Client;
120  ///
121  /// let ns = Client::new("secret").namespace("test");
122  ///
123  /// let query = serde_json::json!({
124  ///   "vector": [0.105, 0.1],
125  ///   "distance_metric": "euclidean_squared",
126  ///   "top_k": 1,
127  ///   "include_vectors": true,
128  ///   "include_attributes": ["my-string"],
129  /// });
130  /// ```
131  ///
132  /// ```ignore
133  /// let res = ns.query(&query).await.unwrap();
134  /// ```
135  pub async fn query(&self, body: &Value) -> Result<QueryResponse, Error> {
136    let url = format!("{BASE_URL}/vectors/{}/query", &self.namespace);
137    let res = self
138      .client
139      .client
140      .post(url)
141      .header("Authorization", format!("Bearer {}", self.client.api_key))
142      .header("Content-Type", "application/json")
143      .json(body)
144      .send()
145      .await?;
146
147    let body = res.text().await.map_err(error::request_error)?;
148    let value = serde_json::from_str::<Value>(&body)
149      .map_err(|e| error::non_json(e, body))?;
150
151    // TODO: Remove or defer clone.
152    let vectors = from_value::<Vec<ResponseVector>>(value.clone())
153      .map_err(|e| error::invalid_response(e, value))?;
154    Ok(QueryResponse { vectors })
155  }
156
157  /// Deletes the namespace and all related data.
158  ///
159  /// Example:
160  ///
161  /// ```
162  /// use turbopuffer_client::Client;
163  ///
164  /// let ns = Client::new("secret").namespace("test");
165  /// ```
166  ///
167  /// ```ignore
168  /// let res = ns.delete().await.unwrap();
169  /// ```
170  pub async fn delete(&self) -> Result<DeleteResponse, Error> {
171    let url = format!("{BASE_URL}/vectors/{}", &self.namespace);
172    let res = self
173      .client
174      .client
175      .delete(url)
176      .header("Authorization", format!("Bearer {}", self.client.api_key))
177      .header("Content-Type", "application/json")
178      .send()
179      .await?;
180
181    let body = res.text().await.map_err(error::request_error)?;
182    let value = serde_json::from_str::<Value>(&body)
183      .map_err(|e| error::non_json(e, body))?;
184
185    // TODO: Remove or defer clone.
186    from_value::<DeleteResponse>(value.clone())
187      .map_err(|e| error::invalid_response(e, value))
188  }
189}