Skip to main content

solidb_client/client/
http_client.rs

1use super::DriverError;
2use reqwest;
3use serde_json::Value;
4use std::time::Duration;
5
6pub struct HttpClient {
7    base_url: String,
8    database: Option<String>,
9    token: Option<String>,
10    client: reqwest::Client,
11}
12
13impl HttpClient {
14    pub fn new(base_url: &str) -> Self {
15        let client = reqwest::Client::builder()
16            .timeout(Duration::from_secs(30))
17            .pool_idle_timeout(Duration::from_secs(30))
18            .pool_max_idle_per_host(16)
19            .build()
20            .unwrap();
21
22        Self {
23            base_url: base_url.to_string().trim_end_matches('/').to_string(),
24            database: None,
25            token: None,
26            client,
27        }
28    }
29
30    pub fn with_database(mut self, database: &str) -> Self {
31        self.database = Some(database.to_string());
32        self
33    }
34
35    pub fn set_database(&mut self, database: &str) {
36        self.database = Some(database.to_string());
37    }
38
39    pub async fn login(&mut self, database: &str, username: &str, password: &str) -> Result<(), DriverError> {
40        let response = self
41            .client
42            .post(&format!("{}/auth/login", self.base_url))
43            .json(&serde_json::json!({
44                "database": database,
45                "username": username,
46                "password": password
47            }))
48            .send()
49            .await
50            .map_err(|e| DriverError::ConnectionError(format!("HTTP request failed: {}", e)))?;
51
52        if response.status() == 401 {
53            return Err(DriverError::AuthError("Invalid credentials".to_string()));
54        }
55
56        let data: Value = response
57            .json()
58            .await
59            .map_err(|e| DriverError::ProtocolError(format!("Failed to parse login response: {}", e)))?;
60
61        if let Some(token) = data.get("token").and_then(|t| t.as_str()) {
62            self.token = Some(token.to_string());
63            self.database = Some(database.to_string());
64            Ok(())
65        } else {
66            Err(DriverError::AuthError("No token in response".to_string()))
67        }
68    }
69
70    pub fn set_token(&mut self, token: &str) {
71        self.token = Some(token.to_string());
72    }
73
74    fn get_headers(&self) -> reqwest::header::HeaderMap {
75        let mut headers = reqwest::header::HeaderMap::new();
76        headers.insert(reqwest::header::CONTENT_TYPE, "application/json".parse().unwrap());
77        if let Some(token) = &self.token {
78            headers.insert(
79                reqwest::header::AUTHORIZATION,
80                format!("Bearer {}", token).parse().unwrap(),
81            );
82        }
83        headers
84    }
85
86    async fn request<T>(&self, method: &str, path: &str, body: Option<&Value>) -> Result<T, DriverError>
87    where
88        T: serde::de::DeserializeOwned,
89    {
90        let url = format!("{}{}", self.base_url, path);
91        let mut request = self.client.request(method.parse().unwrap(), &url);
92        request = request.headers(self.get_headers());
93
94        if let Some(b) = body {
95            request = request.json(b);
96        }
97
98        let response = request
99            .send()
100            .await
101            .map_err(|e| DriverError::ConnectionError(format!("HTTP request failed: {}", e)))?;
102
103        let status = response.status();
104
105        if !status.is_success() {
106            let error_text = response
107                .text()
108                .await
109                .unwrap_or_else(|_| "Unknown error".to_string());
110            return Err(DriverError::ServerError(format!("HTTP {} {}: {}", status, path, error_text)));
111        }
112
113        let text = response
114            .text()
115            .await
116            .map_err(|e| DriverError::ProtocolError(format!("Failed to read response: {}", e)))?;
117
118        if text.is_empty() {
119            return Err(DriverError::ServerError(format!("Empty response for HTTP {} {}", method, path)));
120        }
121
122        serde_json::from_str(&text)
123            .map_err(|e| DriverError::ProtocolError(format!("Failed to parse response: {} - Text: {}", e, text)))
124    }
125
126    pub async fn list_databases(&self) -> Result<Vec<String>, DriverError> {
127        let response: Value = self.request("GET", "/_api/databases", None).await?;
128        Ok(response.get("databases")
129            .and_then(|d| d.as_array())
130            .map(|arr| arr.iter().filter_map(|s| s.as_str().map(|s| s.to_string())).collect())
131            .unwrap_or_default())
132    }
133
134    pub async fn create_database(&self, name: &str) -> Result<(), DriverError> {
135        self.request::<Value>("POST", "/_api/databases", Some(&serde_json::json!({"name": name}))).await?;
136        Ok(())
137    }
138
139    pub async fn list_collections(&self, database: Option<&str>) -> Result<Vec<Value>, DriverError> {
140        let db = database.or(self.database.as_deref()).ok_or_else(|| DriverError::ProtocolError("No database specified".to_string()))?;
141        let response: Value = self.request("GET", &format!("/_api/database/{}/collection", db), None).await?;
142        Ok(response.get("collections")
143            .and_then(|c| c.as_array())
144            .cloned()
145            .unwrap_or_default())
146    }
147
148    pub async fn create_collection(&self, name: &str) -> Result<(), DriverError> {
149        let db = self.database.as_deref().ok_or_else(|| DriverError::ProtocolError("No database specified".to_string()))?;
150        self.request::<Value>("POST", &format!("/_api/database/{}/collection", db), Some(&serde_json::json!({"name": name}))).await?;
151        Ok(())
152    }
153
154    pub async fn insert(&self, collection: &str, document: Value, key: Option<&str>) -> Result<Value, DriverError> {
155        let db = self.database.as_deref().ok_or_else(|| DriverError::ProtocolError("No database specified".to_string()))?;
156        let mut doc = document;
157        if let Some(k) = key {
158            if let Some(obj) = doc.as_object_mut() {
159                obj.insert("_key".to_string(), serde_json::json!(k));
160            }
161        }
162        let path = format!("/_api/database/{}/document/{}", db, collection);
163        self.client
164            .post(&format!("{}{}", self.base_url, path))
165            .headers(self.get_headers())
166            .json(&doc)
167            .send()
168            .await
169            .map_err(|e| DriverError::ConnectionError(format!("HTTP request failed: {}", e)))?
170            .json()
171            .await
172            .map_err(|e| DriverError::ProtocolError(format!("Failed to parse response: {}", e)))
173    }
174
175    pub async fn get(&self, collection: &str, key: &str) -> Result<Option<Value>, DriverError> {
176        let db = self.database.as_deref().ok_or_else(|| DriverError::ProtocolError("No database specified".to_string()))?;
177        let path = format!("/_api/database/{}/document/{}/{}", db, collection, key);
178        let response: Value = self
179            .client
180            .get(&format!("{}{}", self.base_url, path))
181            .headers(self.get_headers())
182            .send()
183            .await
184            .map_err(|e| DriverError::ConnectionError(format!("HTTP request failed: {}", e)))?
185            .json()
186            .await
187            .map_err(|e| DriverError::ProtocolError(format!("Failed to parse response: {}", e)))?;
188        Ok(Some(response))
189    }
190
191    pub async fn update(&self, collection: &str, key: &str, document: Value, merge: bool) -> Result<(), DriverError> {
192        let db = self.database.as_deref().ok_or_else(|| DriverError::ProtocolError("No database specified".to_string()))?;
193        let payload = serde_json::json!({
194            "document": document,
195            "merge": merge
196        });
197        let path = format!("/_api/database/{}/document/{}/{}", db, collection, key);
198        self.request::<Value>("PUT", &path, Some(&payload)).await?;
199        Ok(())
200    }
201
202    pub async fn delete(&self, collection: &str, key: &str) -> Result<(), DriverError> {
203        let db = self.database.as_deref().ok_or_else(|| DriverError::ProtocolError("No database specified".to_string()))?;
204        let path = format!("/_api/database/{}/collection/{}/document/{}", db, collection, key);
205        self.request::<Value>("DELETE", &path, None).await?;
206        Ok(())
207    }
208
209    pub async fn list(&self, collection: &str, limit: i32, offset: i32) -> Result<Vec<Value>, DriverError> {
210        let db = self.database.as_deref().ok_or_else(|| DriverError::ProtocolError("No database specified".to_string()))?;
211        let path = format!("/_api/database/{}/collection/{}/documents?limit={}&offset={}", db, collection, limit, offset);
212        let response: Value = self.request("GET", &path, None).await?;
213        Ok(response.get("documents")
214            .and_then(|d| d.as_array())
215            .cloned()
216            .unwrap_or_default())
217    }
218
219    pub async fn query(&self, sdbql: &str, bind_vars: Option<Value>) -> Result<Vec<Value>, DriverError> {
220        let db = self.database.as_deref().ok_or_else(|| DriverError::ProtocolError("No database specified".to_string()))?;
221        let mut payload = serde_json::json!({
222            "database": db,
223            "sdbql": sdbql
224        });
225        if let Some(bv) = bind_vars {
226            payload["bind_vars"] = bv;
227        }
228        let response: Value = self.request("POST", "/_api/query", Some(&payload)).await?;
229        Ok(response.get("result")
230            .and_then(|r| r.as_array())
231            .cloned()
232            .unwrap_or_default())
233    }
234
235    pub async fn begin_transaction(&self, isolation_level: Option<&str>) -> Result<String, DriverError> {
236        let db = self.database.as_deref().ok_or_else(|| DriverError::ProtocolError("No database specified".to_string()))?;
237        let mut payload = serde_json::json!({
238            "database": db
239        });
240        if let Some(il) = isolation_level {
241            payload["isolation_level"] = serde_json::json!(il);
242        }
243        let response: Value = self.request("POST", "/_api/transaction/begin", Some(&payload)).await?;
244        response.get("tx_id")
245            .and_then(|t| t.as_str())
246            .map(|s| s.to_string())
247            .ok_or_else(|| DriverError::ProtocolError("No tx_id in response".to_string()))
248    }
249
250    pub async fn commit_transaction(&self, tx_id: &str) -> Result<(), DriverError> {
251        self.request::<Value>("POST", "/_api/transaction/commit", Some(&serde_json::json!({"tx_id": tx_id}))).await?;
252        Ok(())
253    }
254
255    pub async fn rollback_transaction(&self, tx_id: &str) -> Result<(), DriverError> {
256        self.request::<Value>("POST", "/_api/transaction/rollback", Some(&serde_json::json!({"tx_id": tx_id}))).await?;
257        Ok(())
258    }
259
260    pub async fn cluster_status(&self) -> Result<Value, DriverError> {
261        self.request("GET", "/_api/cluster/status", None).await
262    }
263
264    pub async fn cluster_info(&self) -> Result<Value, DriverError> {
265        self.request("GET", "/_api/cluster/info", None).await
266    }
267
268    pub async fn ping(&self) -> Result<bool, DriverError> {
269        let response = self.request::<Value>("GET", "/health", None).await;
270        Ok(response.is_ok())
271    }
272}