use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct Document {
pub id: Option<String>,
pub path: String,
pub content: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub metadata: Option<HashMap<String, String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub created_at: Option<DateTime<Utc>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub updated_at: Option<DateTime<Utc>>,
}
impl Document {
pub fn new(path: impl Into<String>, content: impl Into<String>) -> Self {
Self {
id: None,
path: path.into(),
content: content.into(),
metadata: None,
created_at: None,
updated_at: None,
}
}
pub fn with_id(mut self, id: impl Into<String>) -> Self {
self.id = Some(id.into());
self
}
pub fn with_metadata(mut self, metadata: HashMap<String, String>) -> Self {
self.metadata = Some(metadata);
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SearchQuery {
pub q: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub limit: Option<usize>,
#[serde(skip_serializing_if = "Option::is_none")]
pub offset: Option<usize>,
#[serde(skip_serializing_if = "Option::is_none")]
pub filters: Option<serde_json::Value>,
}
impl SearchQuery {
pub fn new(query: impl Into<String>) -> Self {
Self {
q: query.into(),
limit: None,
offset: None,
filters: None,
}
}
pub fn with_limit(mut self, limit: usize) -> Self {
self.limit = Some(limit);
self
}
pub fn with_offset(mut self, offset: usize) -> Self {
self.offset = Some(offset);
self
}
pub fn with_filters(mut self, filters: serde_json::Value) -> Self {
self.filters = Some(filters);
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DocumentReference {
pub id: String,
pub path: String,
pub content: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub score: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub metadata: Option<HashMap<String, String>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SearchResponse {
pub query: String,
pub documents: Vec<DocumentReference>,
pub total: usize,
#[serde(skip_serializing_if = "Option::is_none")]
pub took_ms: Option<u64>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ContextRequest {
pub q: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub budget: Option<usize>,
#[serde(skip_serializing_if = "Option::is_none")]
pub max_results: Option<usize>,
#[serde(skip_serializing_if = "Option::is_none")]
pub include_metadata: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub smt_options: Option<serde_json::Value>,
}
impl ContextRequest {
pub fn new(query: impl Into<String>) -> Self {
Self {
q: query.into(),
budget: None,
max_results: None,
include_metadata: None,
smt_options: None,
}
}
pub fn with_budget(mut self, budget: usize) -> Self {
self.budget = Some(budget);
self
}
pub fn with_max_results(mut self, max_results: usize) -> Self {
self.max_results = Some(max_results);
self
}
pub fn with_metadata(mut self, include: bool) -> Self {
self.include_metadata = Some(include);
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ContextResponse {
pub query: String,
pub documents: Option<Vec<DocumentReference>>,
pub total_documents: usize,
pub total_tokens: usize,
pub coherence_score: f64,
#[serde(skip_serializing_if = "Option::is_none")]
pub smt_metrics: Option<serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub timings: Option<serde_json::Value>,
pub cache_hit: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub cache_key_fingerprint: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CompleteHealthStatus {
pub status: String,
pub version: String,
pub timestamp: i64,
pub database: DatabaseStats,
pub smt: SmtInfo,
pub features: Features,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DatabaseStats {
pub documents_indexed: String,
pub cache_entries: String,
pub fts_enabled: bool,
pub last_optimized: i64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SmtInfo {
pub enabled: bool,
pub solver: String,
pub version: String,
pub policy: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Features {
pub cache_enabled: bool,
pub fts_search: bool,
pub quantum_scoring: bool,
pub smt_optimization: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StorageInfo {
pub database_path: String,
pub size_bytes: u64,
pub document_count: usize,
#[serde(skip_serializing_if = "Option::is_none")]
pub last_vacuum: Option<DateTime<Utc>>,
}
#[derive(Debug, Clone)]
pub struct ClientConfig {
pub base_url: String,
pub auth_token: Option<String>,
pub timeout_seconds: u64,
pub max_retries: usize,
pub user_agent: String,
}
impl ClientConfig {
pub fn new(base_url: impl Into<String>) -> Self {
Self {
base_url: base_url.into(),
auth_token: None,
timeout_seconds: 30,
max_retries: 3,
user_agent: format!("contextlite-rust-client/{}", env!("CARGO_PKG_VERSION")),
}
}
pub fn with_auth_token(mut self, token: impl Into<String>) -> Self {
self.auth_token = Some(token.into());
self
}
pub fn with_timeout(mut self, seconds: u64) -> Self {
self.timeout_seconds = seconds;
self
}
pub fn with_max_retries(mut self, retries: usize) -> Self {
self.max_retries = retries;
self
}
pub fn with_user_agent(mut self, user_agent: impl Into<String>) -> Self {
self.user_agent = user_agent.into();
self
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_document_creation() {
let mut metadata = HashMap::new();
metadata.insert("lang".to_string(), "rust".to_string());
let doc = Document::new("test.rs", "fn main() {}")
.with_id("test-id")
.with_metadata(metadata);
assert_eq!(doc.path, "test.rs");
assert_eq!(doc.content, "fn main() {}");
assert_eq!(doc.id, Some("test-id".to_string()));
assert!(doc.metadata.is_some());
}
#[test]
fn test_search_query_builder() {
let query = SearchQuery::new("rust async")
.with_limit(10)
.with_offset(5);
assert_eq!(query.q, "rust async");
assert_eq!(query.limit, Some(10));
assert_eq!(query.offset, Some(5));
}
#[test]
fn test_context_request_builder() {
let request = ContextRequest::new("example query")
.with_budget(1000)
.with_max_results(5)
.with_metadata(true);
assert_eq!(request.q, "example query");
assert_eq!(request.budget, Some(1000));
assert_eq!(request.max_results, Some(5));
assert_eq!(request.include_metadata, Some(true));
}
#[test]
fn test_client_config_builder() {
let config = ClientConfig::new("http://localhost:8080")
.with_auth_token("test-token")
.with_timeout(60)
.with_max_retries(5);
assert_eq!(config.base_url, "http://localhost:8080");
assert_eq!(config.auth_token, Some("test-token".to_string()));
assert_eq!(config.timeout_seconds, 60);
assert_eq!(config.max_retries, 5);
}
}