contextlite_client/
types.rs

1//! Core types for the ContextLite Rust client.
2//!
3//! This module defines all data structures used for communication with the ContextLite server,
4//! following the same patterns as the Go, Python, and Node.js clients for consistency.
5
6use chrono::{DateTime, Utc};
7use serde::{Deserialize, Serialize};
8use std::collections::HashMap;
9
10/// A document in the ContextLite system
11#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
12pub struct Document {
13    /// Unique document identifier (auto-generated if not provided)
14    pub id: Option<String>,
15    
16    /// File path or logical path of the document
17    pub path: String,
18    
19    /// Content of the document
20    pub content: String,
21    
22    /// Optional metadata as key-value pairs
23    #[serde(skip_serializing_if = "Option::is_none")]
24    pub metadata: Option<HashMap<String, String>>,
25    
26    /// Creation timestamp
27    #[serde(skip_serializing_if = "Option::is_none")]
28    pub created_at: Option<DateTime<Utc>>,
29    
30    /// Last update timestamp
31    #[serde(skip_serializing_if = "Option::is_none")]
32    pub updated_at: Option<DateTime<Utc>>,
33}
34
35impl Document {
36    /// Create a new document with required fields
37    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    /// Set the document ID
49    pub fn with_id(mut self, id: impl Into<String>) -> Self {
50        self.id = Some(id.into());
51        self
52    }
53    
54    /// Set metadata for the document
55    pub fn with_metadata(mut self, metadata: HashMap<String, String>) -> Self {
56        self.metadata = Some(metadata);
57        self
58    }
59}
60
61/// Search query parameters
62#[derive(Debug, Clone, Serialize, Deserialize)]
63pub struct SearchQuery {
64    /// The search query string
65    pub q: String,
66    
67    /// Maximum number of results to return
68    #[serde(skip_serializing_if = "Option::is_none")]
69    pub limit: Option<usize>,
70    
71    /// Number of results to skip (for pagination)
72    #[serde(skip_serializing_if = "Option::is_none")]
73    pub offset: Option<usize>,
74    
75    /// Additional filters as arbitrary JSON
76    #[serde(skip_serializing_if = "Option::is_none")]
77    pub filters: Option<serde_json::Value>,
78}
79
80impl SearchQuery {
81    /// Create a new search query
82    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    /// Set the result limit
92    pub fn with_limit(mut self, limit: usize) -> Self {
93        self.limit = Some(limit);
94        self
95    }
96    
97    /// Set the result offset for pagination
98    pub fn with_offset(mut self, offset: usize) -> Self {
99        self.offset = Some(offset);
100        self
101    }
102    
103    /// Add filters to the query
104    pub fn with_filters(mut self, filters: serde_json::Value) -> Self {
105        self.filters = Some(filters);
106        self
107    }
108}
109
110/// Reference to a document in search results or context
111#[derive(Debug, Clone, Serialize, Deserialize)]
112pub struct DocumentReference {
113    /// Document ID
114    pub id: String,
115    
116    /// Document path
117    pub path: String,
118    
119    /// Document content (may be truncated)
120    pub content: String,
121    
122    /// Relevance score for this document
123    #[serde(skip_serializing_if = "Option::is_none")]
124    pub score: Option<f64>,
125    
126    /// Additional metadata
127    #[serde(skip_serializing_if = "Option::is_none")]
128    pub metadata: Option<HashMap<String, String>>,
129}
130
131/// Response from document search
132#[derive(Debug, Clone, Serialize, Deserialize)]
133pub struct SearchResponse {
134    /// Search query that was executed
135    pub query: String,
136    
137    /// Documents matching the search
138    pub documents: Vec<DocumentReference>,
139    
140    /// Total number of results found
141    pub total: usize,
142    
143    /// Search execution time in milliseconds
144    #[serde(skip_serializing_if = "Option::is_none")]
145    pub took_ms: Option<u64>,
146}
147
148/// Request for context assembly
149#[derive(Debug, Clone, Serialize, Deserialize)]
150pub struct ContextRequest {
151    /// Query for context assembly
152    pub q: String,
153    
154    /// Token budget for the assembled context
155    #[serde(skip_serializing_if = "Option::is_none")]
156    pub budget: Option<usize>,
157    
158    /// Maximum number of documents to include
159    #[serde(skip_serializing_if = "Option::is_none")]
160    pub max_results: Option<usize>,
161    
162    /// Whether to include document metadata
163    #[serde(skip_serializing_if = "Option::is_none")]
164    pub include_metadata: Option<bool>,
165    
166    /// SMT optimization parameters
167    #[serde(skip_serializing_if = "Option::is_none")]
168    pub smt_options: Option<serde_json::Value>,
169}
170
171impl ContextRequest {
172    /// Create a new context request
173    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    /// Set the token budget
184    pub fn with_budget(mut self, budget: usize) -> Self {
185        self.budget = Some(budget);
186        self
187    }
188    
189    /// Set the maximum number of results
190    pub fn with_max_results(mut self, max_results: usize) -> Self {
191        self.max_results = Some(max_results);
192        self
193    }
194    
195    /// Include document metadata in the response
196    pub fn with_metadata(mut self, include: bool) -> Self {
197        self.include_metadata = Some(include);
198        self
199    }
200}
201
202/// Assembled context response
203#[derive(Debug, Clone, Serialize, Deserialize)]
204pub struct ContextResponse {
205    /// The search query used
206    pub query: String,
207    
208    /// Documents that were included in the context
209    pub documents: Option<Vec<DocumentReference>>,
210    
211    /// Total number of documents included
212    pub total_documents: usize,
213    
214    /// Total token count
215    pub total_tokens: usize,
216    
217    /// Coherence score of the assembled context
218    pub coherence_score: f64,
219    
220    /// SMT solver metrics
221    #[serde(skip_serializing_if = "Option::is_none")]
222    pub smt_metrics: Option<serde_json::Value>,
223    
224    /// Timing information
225    #[serde(skip_serializing_if = "Option::is_none")]
226    pub timings: Option<serde_json::Value>,
227    
228    /// Whether this was a cache hit
229    pub cache_hit: bool,
230    
231    /// Cache key fingerprint
232    #[serde(skip_serializing_if = "Option::is_none")]
233    pub cache_key_fingerprint: Option<String>,
234}
235
236/// System health status
237#[derive(Debug, Clone, Serialize, Deserialize)]
238pub struct CompleteHealthStatus {
239    /// Overall system status
240    pub status: String,
241    
242    /// System version
243    pub version: String,
244    
245    /// Timestamp of the status check
246    pub timestamp: i64,
247    
248    /// Database information
249    pub database: DatabaseStats,
250    
251    /// SMT solver information
252    pub smt: SmtInfo,
253    
254    /// Available features
255    pub features: Features,
256}
257
258/// Database statistics and status
259#[derive(Debug, Clone, Serialize, Deserialize)]
260pub struct DatabaseStats {
261    /// Number of documents currently indexed
262    pub documents_indexed: String,
263    
264    /// Number of active cache entries
265    pub cache_entries: String,
266    
267    /// Whether FTS (Full Text Search) is enabled
268    pub fts_enabled: bool,
269    
270    /// Last optimization timestamp
271    pub last_optimized: i64,
272}
273
274/// SMT solver information
275#[derive(Debug, Clone, Serialize, Deserialize)]
276pub struct SmtInfo {
277    /// Whether SMT optimization is enabled
278    pub enabled: bool,
279    
280    /// SMT solver name
281    pub solver: String,
282    
283    /// SMT solver version
284    pub version: String,
285    
286    /// SMT policy description
287    pub policy: String,
288}
289
290/// Available system features
291#[derive(Debug, Clone, Serialize, Deserialize)]
292pub struct Features {
293    /// Whether caching is enabled
294    pub cache_enabled: bool,
295    
296    /// Whether FTS search is available
297    pub fts_search: bool,
298    
299    /// Whether quantum scoring is enabled
300    pub quantum_scoring: bool,
301    
302    /// Whether SMT optimization is available
303    pub smt_optimization: bool,
304}
305
306/// Storage information
307#[derive(Debug, Clone, Serialize, Deserialize)]
308pub struct StorageInfo {
309    /// Database file path
310    pub database_path: String,
311    
312    /// Database size in bytes
313    pub size_bytes: u64,
314    
315    /// Number of documents
316    pub document_count: usize,
317    
318    /// Last vacuum timestamp
319    #[serde(skip_serializing_if = "Option::is_none")]
320    pub last_vacuum: Option<DateTime<Utc>>,
321}
322
323/// Client configuration
324#[derive(Debug, Clone)]
325pub struct ClientConfig {
326    /// Base URL of the ContextLite server
327    pub base_url: String,
328    
329    /// Authentication token
330    pub auth_token: Option<String>,
331    
332    /// Request timeout in seconds
333    pub timeout_seconds: u64,
334    
335    /// Maximum number of retries for failed requests
336    pub max_retries: usize,
337    
338    /// User agent string
339    pub user_agent: String,
340}
341
342impl ClientConfig {
343    /// Create a new client configuration
344    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    /// Set the authentication token
355    pub fn with_auth_token(mut self, token: impl Into<String>) -> Self {
356        self.auth_token = Some(token.into());
357        self
358    }
359    
360    /// Set the request timeout
361    pub fn with_timeout(mut self, seconds: u64) -> Self {
362        self.timeout_seconds = seconds;
363        self
364    }
365    
366    /// Set the maximum number of retries
367    pub fn with_max_retries(mut self, retries: usize) -> Self {
368        self.max_retries = retries;
369        self
370    }
371    
372    /// Set a custom user agent
373    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}