1use chrono::{DateTime, Utc};
7use serde::{Deserialize, Serialize};
8use std::collections::HashMap;
9
10#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
12pub struct Document {
13 pub id: Option<String>,
15
16 pub path: String,
18
19 pub content: String,
21
22 #[serde(skip_serializing_if = "Option::is_none")]
24 pub metadata: Option<HashMap<String, String>>,
25
26 #[serde(skip_serializing_if = "Option::is_none")]
28 pub created_at: Option<DateTime<Utc>>,
29
30 #[serde(skip_serializing_if = "Option::is_none")]
32 pub updated_at: Option<DateTime<Utc>>,
33}
34
35impl Document {
36 pub fn new(path: impl Into<String>, content: impl Into<String>) -> Self {
38 Self {
39 id: None,
40 path: path.into(),
41 content: content.into(),
42 metadata: None,
43 created_at: None,
44 updated_at: None,
45 }
46 }
47
48 pub fn with_id(mut self, id: impl Into<String>) -> Self {
50 self.id = Some(id.into());
51 self
52 }
53
54 pub fn with_metadata(mut self, metadata: HashMap<String, String>) -> Self {
56 self.metadata = Some(metadata);
57 self
58 }
59}
60
61#[derive(Debug, Clone, Serialize, Deserialize)]
63pub struct SearchQuery {
64 pub q: String,
66
67 #[serde(skip_serializing_if = "Option::is_none")]
69 pub limit: Option<usize>,
70
71 #[serde(skip_serializing_if = "Option::is_none")]
73 pub offset: Option<usize>,
74
75 #[serde(skip_serializing_if = "Option::is_none")]
77 pub filters: Option<serde_json::Value>,
78}
79
80impl SearchQuery {
81 pub fn new(query: impl Into<String>) -> Self {
83 Self {
84 q: query.into(),
85 limit: None,
86 offset: None,
87 filters: None,
88 }
89 }
90
91 pub fn with_limit(mut self, limit: usize) -> Self {
93 self.limit = Some(limit);
94 self
95 }
96
97 pub fn with_offset(mut self, offset: usize) -> Self {
99 self.offset = Some(offset);
100 self
101 }
102
103 pub fn with_filters(mut self, filters: serde_json::Value) -> Self {
105 self.filters = Some(filters);
106 self
107 }
108}
109
110#[derive(Debug, Clone, Serialize, Deserialize)]
112pub struct DocumentReference {
113 pub id: String,
115
116 pub path: String,
118
119 pub content: String,
121
122 #[serde(skip_serializing_if = "Option::is_none")]
124 pub score: Option<f64>,
125
126 #[serde(skip_serializing_if = "Option::is_none")]
128 pub metadata: Option<HashMap<String, String>>,
129}
130
131#[derive(Debug, Clone, Serialize, Deserialize)]
133pub struct SearchResponse {
134 pub query: String,
136
137 pub documents: Vec<DocumentReference>,
139
140 pub total: usize,
142
143 #[serde(skip_serializing_if = "Option::is_none")]
145 pub took_ms: Option<u64>,
146}
147
148#[derive(Debug, Clone, Serialize, Deserialize)]
150pub struct ContextRequest {
151 pub q: String,
153
154 #[serde(skip_serializing_if = "Option::is_none")]
156 pub budget: Option<usize>,
157
158 #[serde(skip_serializing_if = "Option::is_none")]
160 pub max_results: Option<usize>,
161
162 #[serde(skip_serializing_if = "Option::is_none")]
164 pub include_metadata: Option<bool>,
165
166 #[serde(skip_serializing_if = "Option::is_none")]
168 pub smt_options: Option<serde_json::Value>,
169}
170
171impl ContextRequest {
172 pub fn new(query: impl Into<String>) -> Self {
174 Self {
175 q: query.into(),
176 budget: None,
177 max_results: None,
178 include_metadata: None,
179 smt_options: None,
180 }
181 }
182
183 pub fn with_budget(mut self, budget: usize) -> Self {
185 self.budget = Some(budget);
186 self
187 }
188
189 pub fn with_max_results(mut self, max_results: usize) -> Self {
191 self.max_results = Some(max_results);
192 self
193 }
194
195 pub fn with_metadata(mut self, include: bool) -> Self {
197 self.include_metadata = Some(include);
198 self
199 }
200}
201
202#[derive(Debug, Clone, Serialize, Deserialize)]
204pub struct ContextResponse {
205 pub query: String,
207
208 pub documents: Option<Vec<DocumentReference>>,
210
211 pub total_documents: usize,
213
214 pub total_tokens: usize,
216
217 pub coherence_score: f64,
219
220 #[serde(skip_serializing_if = "Option::is_none")]
222 pub smt_metrics: Option<serde_json::Value>,
223
224 #[serde(skip_serializing_if = "Option::is_none")]
226 pub timings: Option<serde_json::Value>,
227
228 pub cache_hit: bool,
230
231 #[serde(skip_serializing_if = "Option::is_none")]
233 pub cache_key_fingerprint: Option<String>,
234}
235
236#[derive(Debug, Clone, Serialize, Deserialize)]
238pub struct CompleteHealthStatus {
239 pub status: String,
241
242 pub version: String,
244
245 pub timestamp: i64,
247
248 pub database: DatabaseStats,
250
251 pub smt: SmtInfo,
253
254 pub features: Features,
256}
257
258#[derive(Debug, Clone, Serialize, Deserialize)]
260pub struct DatabaseStats {
261 pub documents_indexed: String,
263
264 pub cache_entries: String,
266
267 pub fts_enabled: bool,
269
270 pub last_optimized: i64,
272}
273
274#[derive(Debug, Clone, Serialize, Deserialize)]
276pub struct SmtInfo {
277 pub enabled: bool,
279
280 pub solver: String,
282
283 pub version: String,
285
286 pub policy: String,
288}
289
290#[derive(Debug, Clone, Serialize, Deserialize)]
292pub struct Features {
293 pub cache_enabled: bool,
295
296 pub fts_search: bool,
298
299 pub quantum_scoring: bool,
301
302 pub smt_optimization: bool,
304}
305
306#[derive(Debug, Clone, Serialize, Deserialize)]
308pub struct StorageInfo {
309 pub database_path: String,
311
312 pub size_bytes: u64,
314
315 pub document_count: usize,
317
318 #[serde(skip_serializing_if = "Option::is_none")]
320 pub last_vacuum: Option<DateTime<Utc>>,
321}
322
323#[derive(Debug, Clone)]
325pub struct ClientConfig {
326 pub base_url: String,
328
329 pub auth_token: Option<String>,
331
332 pub timeout_seconds: u64,
334
335 pub max_retries: usize,
337
338 pub user_agent: String,
340}
341
342impl ClientConfig {
343 pub fn new(base_url: impl Into<String>) -> Self {
345 Self {
346 base_url: base_url.into(),
347 auth_token: None,
348 timeout_seconds: 30,
349 max_retries: 3,
350 user_agent: format!("contextlite-rust-client/{}", env!("CARGO_PKG_VERSION")),
351 }
352 }
353
354 pub fn with_auth_token(mut self, token: impl Into<String>) -> Self {
356 self.auth_token = Some(token.into());
357 self
358 }
359
360 pub fn with_timeout(mut self, seconds: u64) -> Self {
362 self.timeout_seconds = seconds;
363 self
364 }
365
366 pub fn with_max_retries(mut self, retries: usize) -> Self {
368 self.max_retries = retries;
369 self
370 }
371
372 pub fn with_user_agent(mut self, user_agent: impl Into<String>) -> Self {
374 self.user_agent = user_agent.into();
375 self
376 }
377}
378
379#[cfg(test)]
380mod tests {
381 use super::*;
382
383 #[test]
384 fn test_document_creation() {
385 let mut metadata = HashMap::new();
386 metadata.insert("lang".to_string(), "rust".to_string());
387
388 let doc = Document::new("test.rs", "fn main() {}")
389 .with_id("test-id")
390 .with_metadata(metadata);
391
392 assert_eq!(doc.path, "test.rs");
393 assert_eq!(doc.content, "fn main() {}");
394 assert_eq!(doc.id, Some("test-id".to_string()));
395 assert!(doc.metadata.is_some());
396 }
397
398 #[test]
399 fn test_search_query_builder() {
400 let query = SearchQuery::new("rust async")
401 .with_limit(10)
402 .with_offset(5);
403
404 assert_eq!(query.q, "rust async");
405 assert_eq!(query.limit, Some(10));
406 assert_eq!(query.offset, Some(5));
407 }
408
409 #[test]
410 fn test_context_request_builder() {
411 let request = ContextRequest::new("example query")
412 .with_budget(1000)
413 .with_max_results(5)
414 .with_metadata(true);
415
416 assert_eq!(request.q, "example query");
417 assert_eq!(request.budget, Some(1000));
418 assert_eq!(request.max_results, Some(5));
419 assert_eq!(request.include_metadata, Some(true));
420 }
421
422 #[test]
423 fn test_client_config_builder() {
424 let config = ClientConfig::new("http://localhost:8080")
425 .with_auth_token("test-token")
426 .with_timeout(60)
427 .with_max_retries(5);
428
429 assert_eq!(config.base_url, "http://localhost:8080");
430 assert_eq!(config.auth_token, Some("test-token".to_string()));
431 assert_eq!(config.timeout_seconds, 60);
432 assert_eq!(config.max_retries, 5);
433 }
434}