1use std::collections::HashMap;
7
8use serde::{Deserialize, Serialize};
9
10use crate::error::Result;
11use crate::types::{WarmCacheRequest, WarmCacheResponse};
12use crate::DakeraClient;
13
14#[derive(Debug, Clone, Serialize, Deserialize)]
20pub struct OpsStats {
21 pub version: String,
22 pub total_vectors: u64,
23 pub namespace_count: u64,
24 pub uptime_seconds: u64,
25 pub timestamp: u64,
26 pub state: String,
27}
28
29#[derive(Debug, Clone, Serialize, Deserialize)]
31pub struct ClusterStatus {
32 pub cluster_id: String,
33 pub state: String,
34 pub node_count: u32,
35 pub total_vectors: u64,
36 pub namespace_count: u64,
37 pub version: String,
38 pub timestamp: u64,
39 #[serde(default, skip_serializing_if = "Option::is_none")]
41 pub redis_healthy: Option<bool>,
42}
43
44#[derive(Debug, Clone, Serialize, Deserialize)]
46pub struct NodeInfo {
47 pub node_id: String,
48 pub address: String,
49 pub role: String,
50 pub status: String,
51 pub version: String,
52 pub uptime_seconds: u64,
53 pub vector_count: u64,
54 pub memory_bytes: u64,
55 #[serde(default)]
56 pub cpu_percent: f32,
57 #[serde(default)]
58 pub memory_percent: f32,
59 pub last_heartbeat: u64,
60}
61
62#[derive(Debug, Clone, Serialize, Deserialize)]
64pub struct NodeListResponse {
65 pub nodes: Vec<NodeInfo>,
66 pub total: u32,
67}
68
69#[derive(Debug, Clone, Serialize, Deserialize)]
75pub struct IndexStats {
76 pub index_type: String,
77 pub is_built: bool,
78 pub size_bytes: u64,
79 pub indexed_vectors: u64,
80 #[serde(skip_serializing_if = "Option::is_none")]
81 pub last_rebuild: Option<u64>,
82}
83
84#[derive(Debug, Clone, Serialize, Deserialize)]
86pub struct NamespaceAdminInfo {
87 pub name: String,
88 pub vector_count: u64,
89 #[serde(skip_serializing_if = "Option::is_none")]
90 pub dimension: Option<usize>,
91 pub index_type: String,
92 pub storage_bytes: u64,
93 pub document_count: u64,
94 #[serde(skip_serializing_if = "Option::is_none")]
95 pub created_at: Option<u64>,
96 #[serde(skip_serializing_if = "Option::is_none")]
97 pub updated_at: Option<u64>,
98 pub index_stats: IndexStats,
99}
100
101#[derive(Debug, Clone, Serialize, Deserialize)]
103pub struct NamespaceListResponse {
104 pub namespaces: Vec<NamespaceAdminInfo>,
105 pub total: u64,
106 pub total_vectors: u64,
107}
108
109#[derive(Debug, Clone, Serialize, Deserialize)]
111pub struct OptimizeRequest {
112 #[serde(default)]
113 pub force: bool,
114 #[serde(skip_serializing_if = "Option::is_none")]
115 pub target_index_type: Option<String>,
116}
117
118#[derive(Debug, Clone, Serialize, Deserialize)]
120pub struct OptimizeResponse {
121 pub success: bool,
122 #[serde(skip_serializing_if = "Option::is_none")]
123 pub job_id: Option<String>,
124 pub message: String,
125}
126
127#[derive(Debug, Clone, Serialize, Deserialize)]
133pub struct IndexStatsResponse {
134 pub namespaces: HashMap<String, IndexStats>,
135 pub total_indexed_vectors: u64,
136 pub total_size_bytes: u64,
137}
138
139#[derive(Debug, Clone, Serialize, Deserialize)]
141pub struct RebuildIndexRequest {
142 #[serde(skip_serializing_if = "Option::is_none")]
143 pub namespace: Option<String>,
144 #[serde(skip_serializing_if = "Option::is_none")]
145 pub index_type: Option<String>,
146 #[serde(default)]
147 pub force: bool,
148}
149
150#[derive(Debug, Clone, Serialize, Deserialize)]
152pub struct RebuildIndexResponse {
153 pub success: bool,
154 pub job_id: String,
155 pub message: String,
156}
157
158#[derive(Debug, Clone, Serialize, Deserialize)]
164pub struct CacheStats {
165 pub enabled: bool,
166 pub cache_type: String,
167 pub entries: u64,
168 pub size_bytes: u64,
169 pub hits: u64,
170 pub misses: u64,
171 pub hit_rate: f64,
172 pub evictions: u64,
173}
174
175#[derive(Debug, Clone, Serialize, Deserialize)]
177pub struct ClearCacheRequest {
178 #[serde(skip_serializing_if = "Option::is_none")]
179 pub namespace: Option<String>,
180}
181
182#[derive(Debug, Clone, Serialize, Deserialize)]
184pub struct ClearCacheResponse {
185 pub success: bool,
186 pub entries_cleared: u64,
187 pub message: String,
188}
189
190#[derive(Debug, Clone, Serialize, Deserialize)]
196pub struct RuntimeConfig {
197 #[serde(skip_serializing_if = "Option::is_none")]
198 pub max_vectors_per_namespace: Option<u64>,
199 pub default_index_type: String,
200 pub cache_enabled: bool,
201 pub cache_max_size_bytes: u64,
202 pub rate_limit_enabled: bool,
203 pub rate_limit_rps: u32,
204 pub query_timeout_ms: u64,
205 #[serde(default = "default_true")]
207 pub autopilot_enabled: bool,
208 #[serde(default = "default_dedup_threshold")]
210 pub autopilot_dedup_threshold: f32,
211 #[serde(default = "default_dedup_interval")]
213 pub autopilot_dedup_interval_hours: u64,
214 #[serde(default = "default_consolidation_interval")]
216 pub autopilot_consolidation_interval_hours: u64,
217}
218
219fn default_true() -> bool {
220 true
221}
222fn default_dedup_threshold() -> f32 {
223 0.93
224}
225fn default_dedup_interval() -> u64 {
226 6
227}
228fn default_consolidation_interval() -> u64 {
229 12
230}
231
232#[derive(Debug, Clone, Serialize, Deserialize)]
234pub struct UpdateConfigResponse {
235 pub success: bool,
236 pub config: RuntimeConfig,
237 pub message: String,
238 #[serde(default, skip_serializing_if = "Vec::is_empty")]
239 pub warnings: Vec<String>,
240}
241
242#[derive(Debug, Clone, Serialize, Deserialize)]
248pub struct QuotaConfig {
249 #[serde(skip_serializing_if = "Option::is_none")]
250 pub max_vectors: Option<u64>,
251 #[serde(skip_serializing_if = "Option::is_none")]
252 pub max_storage_bytes: Option<u64>,
253 #[serde(skip_serializing_if = "Option::is_none")]
254 pub max_queries_per_minute: Option<u64>,
255 #[serde(skip_serializing_if = "Option::is_none")]
256 pub max_writes_per_minute: Option<u64>,
257}
258
259#[derive(Debug, Clone, Serialize, Deserialize)]
261pub struct QuotaUsage {
262 #[serde(default)]
263 pub current_vectors: u64,
264 #[serde(default)]
265 pub current_storage_bytes: u64,
266 #[serde(default)]
267 pub queries_this_minute: u64,
268 #[serde(default)]
269 pub writes_this_minute: u64,
270}
271
272#[derive(Debug, Clone, Serialize, Deserialize)]
274pub struct QuotaStatus {
275 pub namespace: String,
276 pub config: QuotaConfig,
277 pub usage: QuotaUsage,
278}
279
280#[derive(Debug, Clone, Serialize, Deserialize)]
282pub struct QuotaListResponse {
283 pub quotas: Vec<QuotaStatus>,
284 pub total: u64,
285 #[serde(skip_serializing_if = "Option::is_none")]
286 pub default_config: Option<QuotaConfig>,
287}
288
289#[derive(Debug, Clone, Serialize, Deserialize)]
295pub struct SlowQueryEntry {
296 pub id: String,
297 pub timestamp: u64,
298 pub namespace: String,
299 pub query_type: String,
300 pub duration_ms: f64,
301 #[serde(default)]
302 pub parameters: Option<serde_json::Value>,
303 #[serde(default)]
304 pub results_count: u64,
305 #[serde(default)]
306 pub vectors_scanned: u64,
307}
308
309#[derive(Debug, Clone, Serialize, Deserialize)]
311pub struct SlowQueryListResponse {
312 pub queries: Vec<SlowQueryEntry>,
313 pub total: u64,
314 pub threshold_ms: f64,
315}
316
317#[derive(Debug, Clone, Serialize, Deserialize)]
323pub struct BackupInfo {
324 pub backup_id: String,
325 pub name: String,
326 pub backup_type: String,
327 pub status: String,
328 pub namespaces: Vec<String>,
329 pub vector_count: u64,
330 pub size_bytes: u64,
331 pub created_at: u64,
332 #[serde(skip_serializing_if = "Option::is_none")]
333 pub completed_at: Option<u64>,
334 #[serde(skip_serializing_if = "Option::is_none")]
335 pub duration_seconds: Option<u64>,
336 #[serde(skip_serializing_if = "Option::is_none")]
337 pub storage_path: Option<String>,
338 #[serde(skip_serializing_if = "Option::is_none")]
339 pub error: Option<String>,
340 pub encrypted: bool,
341 #[serde(skip_serializing_if = "Option::is_none")]
342 pub compression: Option<String>,
343}
344
345#[derive(Debug, Clone, Serialize, Deserialize)]
347pub struct BackupListResponse {
348 pub backups: Vec<BackupInfo>,
349 pub total: u64,
350}
351
352#[derive(Debug, Clone, Serialize, Deserialize)]
354pub struct CreateBackupRequest {
355 pub name: String,
356 #[serde(skip_serializing_if = "Option::is_none")]
357 pub backup_type: Option<String>,
358 #[serde(skip_serializing_if = "Option::is_none")]
359 pub namespaces: Option<Vec<String>>,
360 #[serde(skip_serializing_if = "Option::is_none")]
361 pub encrypt: Option<bool>,
362 #[serde(skip_serializing_if = "Option::is_none")]
363 pub compression: Option<String>,
364}
365
366#[derive(Debug, Clone, Serialize, Deserialize)]
368pub struct CreateBackupResponse {
369 pub backup: BackupInfo,
370 #[serde(skip_serializing_if = "Option::is_none")]
371 pub estimated_completion: Option<u64>,
372}
373
374#[derive(Debug, Clone, Serialize, Deserialize)]
376pub struct RestoreBackupRequest {
377 pub backup_id: String,
378 #[serde(skip_serializing_if = "Option::is_none")]
379 pub target_namespaces: Option<Vec<String>>,
380 #[serde(skip_serializing_if = "Option::is_none")]
381 pub overwrite: Option<bool>,
382 #[serde(skip_serializing_if = "Option::is_none")]
383 pub point_in_time: Option<u64>,
384}
385
386#[derive(Debug, Clone, Serialize, Deserialize)]
388pub struct RestoreBackupResponse {
389 pub restore_id: String,
390 pub status: String,
391 pub backup_id: String,
392 pub namespaces: Vec<String>,
393 pub started_at: u64,
394 #[serde(skip_serializing_if = "Option::is_none")]
395 pub estimated_completion: Option<u64>,
396 #[serde(skip_serializing_if = "Option::is_none")]
397 pub progress_percent: Option<u8>,
398 #[serde(skip_serializing_if = "Option::is_none")]
399 pub vectors_restored: Option<u64>,
400 #[serde(skip_serializing_if = "Option::is_none")]
401 pub completed_at: Option<u64>,
402 #[serde(skip_serializing_if = "Option::is_none")]
403 pub duration_seconds: Option<u64>,
404 #[serde(skip_serializing_if = "Option::is_none")]
405 pub error: Option<String>,
406}
407
408#[derive(Debug, Clone, Serialize, Deserialize)]
414pub struct AutoPilotConfig {
415 pub enabled: bool,
416 pub dedup_threshold: f32,
417 pub dedup_interval_hours: u64,
418 pub consolidation_interval_hours: u64,
419}
420
421#[derive(Debug, Clone, Serialize, Deserialize)]
423pub struct DedupResultSnapshot {
424 pub namespaces_processed: usize,
425 pub memories_scanned: usize,
426 pub duplicates_removed: usize,
427}
428
429#[derive(Debug, Clone, Serialize, Deserialize)]
431pub struct ConsolidationResultSnapshot {
432 pub namespaces_processed: usize,
433 pub memories_scanned: usize,
434 pub clusters_merged: usize,
435 pub memories_consolidated: usize,
436}
437
438#[derive(Debug, Clone, Serialize, Deserialize)]
440pub struct AutoPilotStatusResponse {
441 pub config: AutoPilotConfig,
442 #[serde(skip_serializing_if = "Option::is_none")]
443 pub last_dedup_at: Option<u64>,
444 #[serde(skip_serializing_if = "Option::is_none")]
445 pub last_consolidation_at: Option<u64>,
446 #[serde(skip_serializing_if = "Option::is_none")]
447 pub last_dedup: Option<DedupResultSnapshot>,
448 #[serde(skip_serializing_if = "Option::is_none")]
449 pub last_consolidation: Option<ConsolidationResultSnapshot>,
450 pub total_dedup_removed: u64,
451 pub total_consolidated: u64,
452}
453
454#[derive(Debug, Clone, Serialize, Deserialize, Default)]
456pub struct AutoPilotConfigRequest {
457 #[serde(skip_serializing_if = "Option::is_none")]
458 pub enabled: Option<bool>,
459 #[serde(skip_serializing_if = "Option::is_none")]
460 pub dedup_threshold: Option<f32>,
461 #[serde(skip_serializing_if = "Option::is_none")]
462 pub dedup_interval_hours: Option<u64>,
463 #[serde(skip_serializing_if = "Option::is_none")]
464 pub consolidation_interval_hours: Option<u64>,
465}
466
467#[derive(Debug, Clone, Serialize, Deserialize)]
469pub struct AutoPilotConfigResponse {
470 pub success: bool,
471 pub config: AutoPilotConfig,
472 pub message: String,
473}
474
475#[derive(Debug, Clone, Serialize, Deserialize)]
477#[serde(rename_all = "lowercase")]
478pub enum AutoPilotTriggerAction {
479 Dedup,
480 Consolidate,
481 All,
482}
483
484#[derive(Debug, Clone, Serialize, Deserialize)]
486pub struct AutoPilotTriggerRequest {
487 pub action: AutoPilotTriggerAction,
488}
489
490#[derive(Debug, Clone, Serialize, Deserialize)]
492pub struct AutoPilotDedupResult {
493 pub namespaces_processed: usize,
494 pub memories_scanned: usize,
495 pub duplicates_removed: usize,
496}
497
498#[derive(Debug, Clone, Serialize, Deserialize)]
500pub struct AutoPilotConsolidationResult {
501 pub namespaces_processed: usize,
502 pub memories_scanned: usize,
503 pub clusters_merged: usize,
504 pub memories_consolidated: usize,
505}
506
507#[derive(Debug, Clone, Serialize, Deserialize)]
509pub struct AutoPilotTriggerResponse {
510 pub success: bool,
511 pub action: AutoPilotTriggerAction,
512 #[serde(skip_serializing_if = "Option::is_none")]
513 pub dedup: Option<AutoPilotDedupResult>,
514 #[serde(skip_serializing_if = "Option::is_none")]
515 pub consolidation: Option<AutoPilotConsolidationResult>,
516 pub message: String,
517}
518
519#[derive(Debug, Clone, Serialize, Deserialize)]
525pub struct DecayConfigResponse {
526 pub strategy: String,
528 pub half_life_hours: f64,
530 pub min_importance: f32,
532}
533
534#[derive(Debug, Clone, Serialize, Deserialize, Default)]
536pub struct DecayConfigUpdateRequest {
537 #[serde(skip_serializing_if = "Option::is_none")]
539 pub strategy: Option<String>,
540 #[serde(skip_serializing_if = "Option::is_none")]
542 pub half_life_hours: Option<f64>,
543 #[serde(skip_serializing_if = "Option::is_none")]
545 pub min_importance: Option<f32>,
546}
547
548#[derive(Debug, Clone, Serialize, Deserialize)]
550pub struct DecayConfigUpdateResponse {
551 pub success: bool,
552 pub config: DecayConfigResponse,
553 pub message: String,
554}
555
556#[derive(Debug, Clone, Serialize, Deserialize)]
558pub struct LastDecayCycleStats {
559 pub namespaces_processed: usize,
560 pub memories_processed: usize,
561 pub memories_decayed: usize,
562 pub memories_deleted: usize,
563}
564
565#[derive(Debug, Clone, Serialize, Deserialize)]
567pub struct DecayStatsResponse {
568 pub total_decayed: u64,
570 pub total_deleted: u64,
572 #[serde(skip_serializing_if = "Option::is_none")]
574 pub last_run_at: Option<u64>,
575 pub cycles_run: u64,
577 #[serde(skip_serializing_if = "Option::is_none")]
579 pub last_cycle: Option<LastDecayCycleStats>,
580}
581
582#[derive(Debug, Clone, Serialize, Deserialize)]
588pub struct TtlCleanupRequest {
589 #[serde(skip_serializing_if = "Option::is_none")]
590 pub namespace: Option<String>,
591}
592
593#[derive(Debug, Clone, Serialize, Deserialize)]
595pub struct TtlCleanupResponse {
596 pub success: bool,
597 pub vectors_removed: u64,
598 pub namespaces_cleaned: Vec<String>,
599 pub message: String,
600}
601
602#[derive(Debug, Clone, Serialize, Deserialize)]
604pub struct TtlStats {
605 pub namespace: String,
606 pub vectors_with_ttl: u64,
607 pub expiring_within_hour: u64,
608 pub expiring_within_day: u64,
609 pub expired_pending_cleanup: u64,
610}
611
612#[derive(Debug, Clone, Serialize, Deserialize)]
614pub struct TtlStatsResponse {
615 pub namespaces: Vec<TtlStats>,
616 pub total_with_ttl: u64,
617 pub total_expired: u64,
618}
619
620impl DakeraClient {
625 pub async fn ops_stats(&self) -> Result<OpsStats> {
633 let url = format!("{}/v1/ops/stats", self.base_url);
634 let response = self.client.get(&url).send().await?;
635 self.handle_response(response).await
636 }
637
638 pub async fn ops_metrics(&self) -> Result<String> {
643 let url = format!("{}/v1/ops/metrics", self.base_url);
644 let response = self.client.get(&url).send().await?;
645 self.handle_text_response(response).await
646 }
647
648 pub async fn debug_config(&self) -> Result<serde_json::Value> {
657 let url = format!("{}/debug/config", self.base_url);
658 let response = self.client.get(&url).send().await?;
659 self.handle_response(response).await
660 }
661
662 pub async fn cluster_status(&self) -> Result<ClusterStatus> {
664 let url = format!("{}/v1/admin/cluster/status", self.base_url);
665 let response = self.client.get(&url).send().await?;
666 self.handle_response(response).await
667 }
668
669 pub async fn cluster_nodes(&self) -> Result<NodeListResponse> {
671 let url = format!("{}/v1/admin/cluster/nodes", self.base_url);
672 let response = self.client.get(&url).send().await?;
673 self.handle_response(response).await
674 }
675
676 pub async fn list_namespaces_admin(&self) -> Result<NamespaceListResponse> {
682 let url = format!("{}/v1/admin/namespaces", self.base_url);
683 let response = self.client.get(&url).send().await?;
684 self.handle_response(response).await
685 }
686
687 pub async fn delete_namespace_admin(&self, namespace: &str) -> Result<serde_json::Value> {
689 let url = format!("{}/v1/admin/namespaces/{}", self.base_url, namespace);
690 let response = self.client.delete(&url).send().await?;
691 self.handle_response(response).await
692 }
693
694 pub async fn optimize_namespace(
696 &self,
697 namespace: &str,
698 request: OptimizeRequest,
699 ) -> Result<OptimizeResponse> {
700 let url = format!(
701 "{}/v1/admin/namespaces/{}/optimize",
702 self.base_url, namespace
703 );
704 let response = self.client.post(&url).json(&request).send().await?;
705 self.handle_response(response).await
706 }
707
708 pub async fn index_stats(&self) -> Result<IndexStatsResponse> {
714 let url = format!("{}/v1/admin/indexes/stats", self.base_url);
715 let response = self.client.get(&url).send().await?;
716 self.handle_response(response).await
717 }
718
719 pub async fn rebuild_indexes(
721 &self,
722 request: RebuildIndexRequest,
723 ) -> Result<RebuildIndexResponse> {
724 let url = format!("{}/v1/admin/indexes/rebuild", self.base_url);
725 let response = self.client.post(&url).json(&request).send().await?;
726 self.handle_response(response).await
727 }
728
729 pub async fn cache_stats(&self) -> Result<CacheStats> {
735 let url = format!("{}/v1/admin/cache/stats", self.base_url);
736 let response = self.client.get(&url).send().await?;
737 self.handle_response(response).await
738 }
739
740 pub async fn cache_clear(&self, namespace: Option<&str>) -> Result<ClearCacheResponse> {
742 let url = format!("{}/v1/admin/cache/clear", self.base_url);
743 let request = ClearCacheRequest {
744 namespace: namespace.map(|s| s.to_string()),
745 };
746 let response = self.client.post(&url).json(&request).send().await?;
747 self.handle_response(response).await
748 }
749
750 pub async fn cache_warm(&self, request: WarmCacheRequest) -> Result<WarmCacheResponse> {
752 let url = format!("{}/v1/admin/cache/warm", self.base_url);
753 let response = self.client.post(&url).json(&request).send().await?;
754 self.handle_response(response).await
755 }
756
757 pub async fn get_config(&self) -> Result<RuntimeConfig> {
763 let url = format!("{}/v1/admin/config", self.base_url);
764 let response = self.client.get(&url).send().await?;
765 self.handle_response(response).await
766 }
767
768 pub async fn update_config(
770 &self,
771 updates: HashMap<String, serde_json::Value>,
772 ) -> Result<UpdateConfigResponse> {
773 let url = format!("{}/v1/admin/config", self.base_url);
774 let response = self.client.put(&url).json(&updates).send().await?;
775 self.handle_response(response).await
776 }
777
778 pub async fn get_quotas(&self) -> Result<QuotaListResponse> {
784 let url = format!("{}/v1/admin/quotas", self.base_url);
785 let response = self.client.get(&url).send().await?;
786 self.handle_response(response).await
787 }
788
789 pub async fn get_quota(&self, namespace: &str) -> Result<QuotaStatus> {
791 let url = format!("{}/v1/admin/quotas/{}", self.base_url, namespace);
792 let response = self.client.get(&url).send().await?;
793 self.handle_response(response).await
794 }
795
796 pub async fn set_quota(
798 &self,
799 namespace: &str,
800 config: QuotaConfig,
801 ) -> Result<serde_json::Value> {
802 let url = format!("{}/v1/admin/quotas/{}", self.base_url, namespace);
803 let request = serde_json::json!({ "config": config });
804 let response = self.client.put(&url).json(&request).send().await?;
805 self.handle_response(response).await
806 }
807
808 pub async fn delete_quota(&self, namespace: &str) -> Result<serde_json::Value> {
810 let url = format!("{}/v1/admin/quotas/{}", self.base_url, namespace);
811 let response = self.client.delete(&url).send().await?;
812 self.handle_response(response).await
813 }
814
815 pub async fn update_quotas(&self, config: Option<QuotaConfig>) -> Result<serde_json::Value> {
817 let url = format!("{}/v1/admin/quotas/default", self.base_url);
818 let request = serde_json::json!({ "config": config });
819 let response = self.client.put(&url).json(&request).send().await?;
820 self.handle_response(response).await
821 }
822
823 pub async fn slow_queries(
829 &self,
830 limit: Option<usize>,
831 namespace: Option<&str>,
832 query_type: Option<&str>,
833 ) -> Result<SlowQueryListResponse> {
834 let mut url = format!("{}/v1/admin/slow-queries", self.base_url);
835 let mut params = Vec::new();
836 if let Some(l) = limit {
837 params.push(format!("limit={}", l));
838 }
839 if let Some(ns) = namespace {
840 params.push(format!("namespace={}", ns));
841 }
842 if let Some(qt) = query_type {
843 params.push(format!("query_type={}", qt));
844 }
845 if !params.is_empty() {
846 url.push('?');
847 url.push_str(¶ms.join("&"));
848 }
849 let response = self.client.get(&url).send().await?;
850 self.handle_response(response).await
851 }
852
853 pub async fn slow_query_summary(&self) -> Result<serde_json::Value> {
855 let url = format!("{}/v1/admin/slow-queries/summary", self.base_url);
856 let response = self.client.get(&url).send().await?;
857 self.handle_response(response).await
858 }
859
860 pub async fn clear_slow_queries(&self) -> Result<serde_json::Value> {
862 let url = format!("{}/v1/admin/slow-queries", self.base_url);
863 let response = self.client.delete(&url).send().await?;
864 self.handle_response(response).await
865 }
866
867 pub async fn create_backup(
873 &self,
874 request: CreateBackupRequest,
875 ) -> Result<CreateBackupResponse> {
876 let url = format!("{}/v1/admin/backups", self.base_url);
877 let response = self.client.post(&url).json(&request).send().await?;
878 self.handle_response(response).await
879 }
880
881 pub async fn list_backups(&self) -> Result<BackupListResponse> {
883 let url = format!("{}/v1/admin/backups", self.base_url);
884 let response = self.client.get(&url).send().await?;
885 self.handle_response(response).await
886 }
887
888 pub async fn get_backup(&self, backup_id: &str) -> Result<BackupInfo> {
890 let url = format!("{}/v1/admin/backups/{}", self.base_url, backup_id);
891 let response = self.client.get(&url).send().await?;
892 self.handle_response(response).await
893 }
894
895 pub async fn restore_backup(
897 &self,
898 request: RestoreBackupRequest,
899 ) -> Result<RestoreBackupResponse> {
900 let url = format!("{}/v1/admin/backups/restore", self.base_url);
901 let response = self.client.post(&url).json(&request).send().await?;
902 self.handle_response(response).await
903 }
904
905 pub async fn delete_backup(&self, backup_id: &str) -> Result<serde_json::Value> {
907 let url = format!("{}/v1/admin/backups/{}", self.base_url, backup_id);
908 let response = self.client.delete(&url).send().await?;
909 self.handle_response(response).await
910 }
911
912 pub async fn configure_ttl(
918 &self,
919 namespace: &str,
920 ttl_seconds: u64,
921 strategy: Option<&str>,
922 ) -> Result<serde_json::Value> {
923 let url = format!("{}/v1/admin/namespaces/{}/ttl", self.base_url, namespace);
924 let mut body = serde_json::json!({ "ttl_seconds": ttl_seconds });
925 if let Some(s) = strategy {
926 body["strategy"] = serde_json::Value::String(s.to_string());
927 }
928 let response = self.client.post(&url).json(&body).send().await?;
929 self.handle_response(response).await
930 }
931
932 pub async fn ttl_cleanup(&self, namespace: Option<&str>) -> Result<TtlCleanupResponse> {
934 let url = format!("{}/v1/admin/ttl/cleanup", self.base_url);
935 let request = TtlCleanupRequest {
936 namespace: namespace.map(|s| s.to_string()),
937 };
938 let response = self.client.post(&url).json(&request).send().await?;
939 self.handle_response(response).await
940 }
941
942 pub async fn ttl_stats(&self) -> Result<TtlStatsResponse> {
944 let url = format!("{}/v1/admin/ttl/stats", self.base_url);
945 let response = self.client.get(&url).send().await?;
946 self.handle_response(response).await
947 }
948
949 pub async fn autopilot_status(&self) -> Result<AutoPilotStatusResponse> {
955 let url = format!("{}/v1/admin/autopilot/status", self.base_url);
956 let response = self.client.get(&url).send().await?;
957 self.handle_response(response).await
958 }
959
960 pub async fn autopilot_update_config(
964 &self,
965 request: AutoPilotConfigRequest,
966 ) -> Result<AutoPilotConfigResponse> {
967 let url = format!("{}/v1/admin/autopilot/config", self.base_url);
968 let response = self.client.put(&url).json(&request).send().await?;
969 self.handle_response(response).await
970 }
971
972 pub async fn autopilot_trigger(
977 &self,
978 action: AutoPilotTriggerAction,
979 ) -> Result<AutoPilotTriggerResponse> {
980 let url = format!("{}/v1/admin/autopilot/trigger", self.base_url);
981 let request = AutoPilotTriggerRequest { action };
982 let response = self.client.post(&url).json(&request).send().await?;
983 self.handle_response(response).await
984 }
985
986 pub async fn decay_config(&self) -> Result<DecayConfigResponse> {
995 let url = format!("{}/v1/admin/decay/config", self.base_url);
996 let response = self.client.get(&url).send().await?;
997 self.handle_response(response).await
998 }
999
1000 pub async fn decay_update_config(
1006 &self,
1007 request: DecayConfigUpdateRequest,
1008 ) -> Result<DecayConfigUpdateResponse> {
1009 let url = format!("{}/v1/admin/decay/config", self.base_url);
1010 let response = self.client.put(&url).json(&request).send().await?;
1011 self.handle_response(response).await
1012 }
1013
1014 pub async fn decay_stats(&self) -> Result<DecayStatsResponse> {
1019 let url = format!("{}/v1/admin/decay/stats", self.base_url);
1020 let response = self.client.get(&url).send().await?;
1021 self.handle_response(response).await
1022 }
1023
1024 pub async fn get_kpis(&self) -> Result<KpiSnapshot> {
1034 let url = format!("{}/v1/kpis", self.base_url);
1035 let response = self.client.get(&url).send().await?;
1036 self.handle_response(response).await
1037 }
1038
1039 pub async fn admin_fulltext_reindex(
1051 &self,
1052 namespace: Option<&str>,
1053 ) -> Result<FulltextReindexResponse> {
1054 let url = format!("{}/v1/admin/fulltext/reindex", self.base_url);
1055 let body = serde_json::json!({ "namespace": namespace });
1056 let response = self.client.post(&url).json(&body).send().await?;
1057 self.handle_response(response).await
1058 }
1059
1060 pub async fn admin_cluster_replication(&self) -> Result<crate::types::ReplicationStatus> {
1066 let url = format!("{}/v1/admin/cluster/replication", self.base_url);
1067 let response = self.client.get(&url).send().await?;
1068 self.handle_response(response).await
1069 }
1070
1071 pub async fn admin_list_shards(&self) -> Result<crate::types::ShardListResponse> {
1073 let url = format!("{}/v1/admin/cluster/shards", self.base_url);
1074 let response = self.client.get(&url).send().await?;
1075 self.handle_response(response).await
1076 }
1077
1078 pub async fn admin_rebalance_shards(
1080 &self,
1081 request: crate::types::ShardRebalanceRequest,
1082 ) -> Result<crate::types::ShardRebalanceResponse> {
1083 let url = format!("{}/v1/admin/cluster/shards/rebalance", self.base_url);
1084 let response = self.client.post(&url).json(&request).send().await?;
1085 self.handle_response(response).await
1086 }
1087
1088 pub async fn admin_maintenance_status(&self) -> Result<crate::types::MaintenanceStatus> {
1090 let url = format!("{}/v1/admin/cluster/maintenance", self.base_url);
1091 let response = self.client.get(&url).send().await?;
1092 self.handle_response(response).await
1093 }
1094
1095 pub async fn admin_enable_maintenance(
1097 &self,
1098 request: crate::types::EnableMaintenanceRequest,
1099 ) -> Result<crate::types::MaintenanceStatus> {
1100 let url = format!("{}/v1/admin/cluster/maintenance/enable", self.base_url);
1101 let response = self.client.post(&url).json(&request).send().await?;
1102 self.handle_response(response).await
1103 }
1104
1105 pub async fn admin_disable_maintenance(
1107 &self,
1108 request: crate::types::DisableMaintenanceRequest,
1109 ) -> Result<crate::types::MaintenanceStatus> {
1110 let url = format!("{}/v1/admin/cluster/maintenance/disable", self.base_url);
1111 let response = self.client.post(&url).json(&request).send().await?;
1112 self.handle_response(response).await
1113 }
1114
1115 pub async fn admin_list_quotas(&self) -> Result<crate::types::QuotaListResponse> {
1121 let url = format!("{}/v1/admin/quotas", self.base_url);
1122 let response = self.client.get(&url).send().await?;
1123 self.handle_response(response).await
1124 }
1125
1126 pub async fn admin_get_default_quota(&self) -> Result<crate::types::DefaultQuotaResponse> {
1128 let url = format!("{}/v1/admin/quotas/default", self.base_url);
1129 let response = self.client.get(&url).send().await?;
1130 self.handle_response(response).await
1131 }
1132
1133 pub async fn admin_set_default_quota(
1135 &self,
1136 request: crate::types::SetDefaultQuotaRequest,
1137 ) -> Result<crate::types::SetQuotaResponse> {
1138 let url = format!("{}/v1/admin/quotas/default", self.base_url);
1139 let response = self.client.put(&url).json(&request).send().await?;
1140 self.handle_response(response).await
1141 }
1142
1143 pub async fn admin_get_quota(&self, namespace: &str) -> Result<crate::types::QuotaStatus> {
1145 let url = format!("{}/v1/admin/quotas/{}", self.base_url, namespace);
1146 let response = self.client.get(&url).send().await?;
1147 self.handle_response(response).await
1148 }
1149
1150 pub async fn admin_set_quota(
1152 &self,
1153 namespace: &str,
1154 request: crate::types::SetQuotaRequest,
1155 ) -> Result<crate::types::SetQuotaResponse> {
1156 let url = format!("{}/v1/admin/quotas/{}", self.base_url, namespace);
1157 let response = self.client.put(&url).json(&request).send().await?;
1158 self.handle_response(response).await
1159 }
1160
1161 pub async fn admin_delete_quota(&self, namespace: &str) -> Result<serde_json::Value> {
1163 let url = format!("{}/v1/admin/quotas/{}", self.base_url, namespace);
1164 let response = self.client.delete(&url).send().await?;
1165 self.handle_response(response).await
1166 }
1167
1168 pub async fn admin_check_quota(
1170 &self,
1171 namespace: &str,
1172 request: crate::types::QuotaCheckRequest,
1173 ) -> Result<crate::types::QuotaCheckResult> {
1174 let url = format!("{}/v1/admin/quotas/{}/check", self.base_url, namespace);
1175 let response = self.client.post(&url).json(&request).send().await?;
1176 self.handle_response(response).await
1177 }
1178
1179 pub async fn admin_list_slow_queries(
1185 &self,
1186 namespace: Option<&str>,
1187 query_type: Option<&str>,
1188 limit: Option<u32>,
1189 ) -> Result<Vec<serde_json::Value>> {
1190 let mut url = format!("{}/v1/admin/slow-queries", self.base_url);
1191 let mut params = Vec::new();
1192 if let Some(ns) = namespace {
1193 params.push(format!("namespace={}", ns));
1194 }
1195 if let Some(qt) = query_type {
1196 params.push(format!("query_type={}", qt));
1197 }
1198 if let Some(l) = limit {
1199 params.push(format!("limit={}", l));
1200 }
1201 if !params.is_empty() {
1202 url.push('?');
1203 url.push_str(¶ms.join("&"));
1204 }
1205 let response = self.client.get(&url).send().await?;
1206 self.handle_response(response).await
1207 }
1208
1209 pub async fn admin_slow_query_summary(&self) -> Result<serde_json::Value> {
1211 let url = format!("{}/v1/admin/slow-queries/summary", self.base_url);
1212 let response = self.client.get(&url).send().await?;
1213 self.handle_response(response).await
1214 }
1215
1216 pub async fn admin_clear_slow_queries(
1218 &self,
1219 namespace: Option<&str>,
1220 ) -> Result<serde_json::Value> {
1221 let mut url = format!("{}/v1/admin/slow-queries", self.base_url);
1222 if let Some(ns) = namespace {
1223 url.push_str(&format!("?namespace={}", ns));
1224 }
1225 let response = self.client.delete(&url).send().await?;
1226 self.handle_response(response).await
1227 }
1228
1229 pub async fn admin_update_slow_query_config(
1231 &self,
1232 config: serde_json::Value,
1233 ) -> Result<serde_json::Value> {
1234 let url = format!("{}/v1/admin/slow-queries/config", self.base_url);
1235 let response = self.client.patch(&url).json(&config).send().await?;
1236 self.handle_response(response).await
1237 }
1238
1239 pub async fn admin_list_backups(&self) -> Result<crate::types::BackupListResponse> {
1245 let url = format!("{}/v1/admin/backups", self.base_url);
1246 let response = self.client.get(&url).send().await?;
1247 self.handle_response(response).await
1248 }
1249
1250 pub async fn admin_create_backup(
1252 &self,
1253 request: crate::types::CreateBackupRequest,
1254 ) -> Result<crate::types::CreateBackupResponse> {
1255 let url = format!("{}/v1/admin/backups", self.base_url);
1256 let response = self.client.post(&url).json(&request).send().await?;
1257 self.handle_response(response).await
1258 }
1259
1260 pub async fn admin_get_backup(&self, backup_id: &str) -> Result<crate::types::AdminBackupInfo> {
1262 let url = format!("{}/v1/admin/backups/{}", self.base_url, backup_id);
1263 let response = self.client.get(&url).send().await?;
1264 self.handle_response(response).await
1265 }
1266
1267 pub async fn admin_delete_backup(&self, backup_id: &str) -> Result<serde_json::Value> {
1269 let url = format!("{}/v1/admin/backups/{}", self.base_url, backup_id);
1270 let response = self.client.delete(&url).send().await?;
1271 self.handle_response(response).await
1272 }
1273
1274 pub async fn admin_get_backup_schedule(&self) -> Result<crate::types::BackupSchedule> {
1276 let url = format!("{}/v1/admin/backups/schedule", self.base_url);
1277 let response = self.client.get(&url).send().await?;
1278 self.handle_response(response).await
1279 }
1280
1281 pub async fn admin_update_backup_schedule(
1283 &self,
1284 request: crate::types::UpdateBackupScheduleRequest,
1285 ) -> Result<crate::types::BackupSchedule> {
1286 let url = format!("{}/v1/admin/backups/schedule", self.base_url);
1287 let response = self.client.post(&url).json(&request).send().await?;
1288 self.handle_response(response).await
1289 }
1290
1291 pub async fn admin_restore_backup(
1293 &self,
1294 request: crate::types::RestoreBackupRequest,
1295 ) -> Result<crate::types::RestoreBackupResponse> {
1296 let url = format!("{}/v1/admin/backups/restore", self.base_url);
1297 let response = self.client.post(&url).json(&request).send().await?;
1298 self.handle_response(response).await
1299 }
1300
1301 pub async fn admin_get_restore_status(
1303 &self,
1304 restore_id: &str,
1305 ) -> Result<crate::types::RestoreBackupResponse> {
1306 let url = format!("{}/v1/admin/backups/restore/{}", self.base_url, restore_id);
1307 let response = self.client.get(&url).send().await?;
1308 self.handle_response(response).await
1309 }
1310
1311 pub async fn ops_diagnostics(&self) -> Result<serde_json::Value> {
1317 let url = format!("{}/ops/diagnostics", self.base_url);
1318 let response = self.client.get(&url).send().await?;
1319 self.handle_response(response).await
1320 }
1321
1322 pub async fn ops_list_jobs(&self) -> Result<Vec<crate::types::JobInfo>> {
1324 let url = format!("{}/ops/jobs", self.base_url);
1325 let response = self.client.get(&url).send().await?;
1326 self.handle_response(response).await
1327 }
1328
1329 pub async fn ops_get_job(&self, job_id: &str) -> Result<crate::types::JobInfo> {
1331 let url = format!("{}/ops/jobs/{}", self.base_url, job_id);
1332 let response = self.client.get(&url).send().await?;
1333 self.handle_response(response).await
1334 }
1335
1336 pub async fn ops_compact(
1338 &self,
1339 request: crate::types::CompactionRequest,
1340 ) -> Result<crate::types::CompactionResponse> {
1341 let url = format!("{}/ops/compact", self.base_url);
1342 let response = self.client.post(&url).json(&request).send().await?;
1343 self.handle_response(response).await
1344 }
1345
1346 pub async fn ops_shutdown(&self) -> Result<serde_json::Value> {
1348 let url = format!("{}/ops/shutdown", self.base_url);
1349 let response = self.client.post(&url).send().await?;
1350 self.handle_response(response).await
1351 }
1352
1353 pub async fn download_backup(&self, backup_id: &str) -> Result<Vec<u8>> {
1359 let url = format!("{}/v1/admin/backups/{}/download", self.base_url, backup_id);
1360 let response = self.client.get(&url).send().await?;
1361 if !response.status().is_success() {
1362 let status = response.status();
1363 let body = response.text().await.unwrap_or_default();
1364 return Err(crate::error::ClientError::Server {
1365 status: status.as_u16(),
1366 message: body,
1367 code: None,
1368 });
1369 }
1370 Ok(response.bytes().await?.to_vec())
1371 }
1372
1373 pub async fn upload_backup(&self, data: Vec<u8>) -> Result<crate::types::CreateBackupResponse> {
1375 let url = format!("{}/v1/admin/backups/upload", self.base_url);
1376 let response = self
1377 .client
1378 .post(&url)
1379 .header("Content-Type", "application/gzip")
1380 .body(data)
1381 .send()
1382 .await?;
1383 self.handle_response(response).await
1384 }
1385
1386 pub async fn storage_tier_overview(&self) -> Result<crate::types::StorageTierOverview> {
1392 let url = format!("{}/v1/admin/storage/tiers", self.base_url);
1393 let response = self.client.get(&url).send().await?;
1394 self.handle_response(response).await
1395 }
1396
1397 pub async fn background_activity(&self) -> Result<serde_json::Value> {
1403 let url = format!("{}/v1/admin/background-activity", self.base_url);
1404 let response = self.client.get(&url).send().await?;
1405 self.handle_response(response).await
1406 }
1407
1408 pub async fn memory_type_stats(&self) -> Result<crate::types::MemoryTypeStatsResponse> {
1414 let url = format!("{}/v1/admin/memory-type-stats", self.base_url);
1415 let response = self.client.get(&url).send().await?;
1416 self.handle_response(response).await
1417 }
1418
1419 pub async fn migrate_namespace_dimensions(
1425 &self,
1426 request: crate::types::MigrateNamespaceDimensionsRequest,
1427 ) -> Result<crate::types::MigrateDimensionsResponse> {
1428 let url = format!("{}/v1/admin/namespaces/migrate-dimensions", self.base_url);
1429 let response = self.client.post(&url).json(&request).send().await?;
1430 self.handle_response(response).await
1431 }
1432
1433 pub async fn drain_reembed(
1448 &self,
1449 request: crate::types::DrainReembedRequest,
1450 ) -> Result<crate::types::DrainReembedResponse> {
1451 let url = format!("{}/v1/admin/reembed/drain", self.base_url);
1452 let response = self.client.post(&url).json(&request).send().await?;
1453 self.handle_response(response).await
1454 }
1455
1456 pub async fn admin_reembed_static_count(&self) -> Result<crate::types::StaticCountResponse> {
1465 let url = format!("{}/v1/admin/reembed/static-count", self.base_url);
1466 let response = self.client.get(&url).send().await?;
1467 self.handle_response(response).await
1468 }
1469}
1470
1471#[derive(Debug, Clone, Serialize, Deserialize)]
1482pub struct KpiSnapshot {
1483 pub recall_latency_p50_ms: f64,
1485 pub recall_latency_p99_ms: f64,
1487 pub store_latency_p50_ms: f64,
1489 pub api_error_rate_5xx_pct: f64,
1491 pub active_agents_count: u64,
1493 pub session_count_week: u64,
1495 pub cross_agent_network_node_count: u64,
1497 pub memory_retention_7d_pct: f64,
1499}
1500
1501#[derive(Debug, Clone, Serialize, Deserialize)]
1507pub struct FulltextReindexNamespaceResult {
1508 pub namespace: String,
1510 pub vectors_scanned: usize,
1512 pub newly_indexed: usize,
1514 pub already_indexed: usize,
1516 pub parse_failures: usize,
1518}
1519
1520#[derive(Debug, Clone, Serialize, Deserialize)]
1524pub struct FulltextReindexResponse {
1525 pub namespaces_processed: usize,
1527 pub total_indexed: usize,
1529 pub total_skipped: usize,
1531 pub details: Vec<FulltextReindexNamespaceResult>,
1533}
1534
1535#[cfg(test)]
1540mod tests {
1541 use super::*;
1542
1543 #[test]
1548 fn test_ops_stats_deserializes() {
1549 let json = r#"{
1550 "version": "0.11.104",
1551 "total_vectors": 100000,
1552 "namespace_count": 5,
1553 "uptime_seconds": 86400,
1554 "timestamp": 1785000000,
1555 "state": "healthy"
1556 }"#;
1557 let stats: OpsStats = serde_json::from_str(json).unwrap();
1558 assert_eq!(stats.version, "0.11.104");
1559 assert_eq!(stats.total_vectors, 100000);
1560 assert_eq!(stats.state, "healthy");
1561 }
1562
1563 #[test]
1568 fn test_cluster_status_deserializes_without_redis_healthy() {
1569 let json = r#"{
1570 "cluster_id": "cl-1",
1571 "state": "active",
1572 "node_count": 3,
1573 "total_vectors": 500000,
1574 "namespace_count": 10,
1575 "version": "0.11.104",
1576 "timestamp": 1785000000
1577 }"#;
1578 let cs: ClusterStatus = serde_json::from_str(json).unwrap();
1579 assert_eq!(cs.cluster_id, "cl-1");
1580 assert_eq!(cs.node_count, 3);
1581 assert!(cs.redis_healthy.is_none());
1582 }
1583
1584 #[test]
1585 fn test_cluster_status_deserializes_with_redis_healthy() {
1586 let json = r#"{
1587 "cluster_id": "cl-2",
1588 "state": "active",
1589 "node_count": 1,
1590 "total_vectors": 0,
1591 "namespace_count": 0,
1592 "version": "0.11.104",
1593 "timestamp": 1785000000,
1594 "redis_healthy": true
1595 }"#;
1596 let cs: ClusterStatus = serde_json::from_str(json).unwrap();
1597 assert_eq!(cs.redis_healthy, Some(true));
1598 }
1599
1600 #[test]
1605 fn test_node_info_deserializes_with_defaults() {
1606 let json = r#"{
1607 "node_id": "n1",
1608 "address": "10.0.0.1:8080",
1609 "role": "primary",
1610 "status": "online",
1611 "version": "0.11.104",
1612 "uptime_seconds": 3600,
1613 "vector_count": 10000,
1614 "memory_bytes": 1073741824,
1615 "last_heartbeat": 1785000000
1616 }"#;
1617 let node: NodeInfo = serde_json::from_str(json).unwrap();
1618 assert_eq!(node.node_id, "n1");
1619 assert!((node.cpu_percent - 0.0).abs() < 1e-6);
1620 assert!((node.memory_percent - 0.0).abs() < 1e-6);
1621 }
1622
1623 #[test]
1628 fn test_node_list_response_deserializes_empty() {
1629 let json = r#"{"nodes": [], "total": 0}"#;
1630 let resp: NodeListResponse = serde_json::from_str(json).unwrap();
1631 assert_eq!(resp.total, 0);
1632 assert!(resp.nodes.is_empty());
1633 }
1634
1635 #[test]
1640 fn test_index_stats_optional_last_rebuild_absent() {
1641 let json = r#"{
1642 "index_type": "hnsw",
1643 "is_built": true,
1644 "size_bytes": 2048,
1645 "indexed_vectors": 100
1646 }"#;
1647 let stats: IndexStats = serde_json::from_str(json).unwrap();
1648 assert!(stats.last_rebuild.is_none());
1649 assert!(stats.is_built);
1650 }
1651
1652 #[test]
1653 fn test_index_stats_with_last_rebuild() {
1654 let json = r#"{
1655 "index_type": "flat",
1656 "is_built": false,
1657 "size_bytes": 0,
1658 "indexed_vectors": 0,
1659 "last_rebuild": 1785000000
1660 }"#;
1661 let stats: IndexStats = serde_json::from_str(json).unwrap();
1662 assert_eq!(stats.last_rebuild, Some(1785000000));
1663 }
1664
1665 #[test]
1670 fn test_optimize_request_default_force_false() {
1671 let req = OptimizeRequest {
1672 force: false,
1673 target_index_type: None,
1674 };
1675 let json = serde_json::to_string(&req).unwrap();
1676 assert!(json.contains("\"force\":false"));
1677 assert!(!json.contains("target_index_type"));
1678 }
1679
1680 #[test]
1681 fn test_optimize_request_with_target_index_type() {
1682 let req = OptimizeRequest {
1683 force: true,
1684 target_index_type: Some("hnsw".to_string()),
1685 };
1686 let json = serde_json::to_string(&req).unwrap();
1687 assert!(json.contains("\"force\":true"));
1688 assert!(json.contains("\"target_index_type\":\"hnsw\""));
1689 }
1690
1691 #[test]
1696 fn test_optimize_response_deserializes_with_job_id() {
1697 let json = r#"{"success": true, "job_id": "job-abc", "message": "started"}"#;
1698 let resp: OptimizeResponse = serde_json::from_str(json).unwrap();
1699 assert!(resp.success);
1700 assert_eq!(resp.job_id.as_deref(), Some("job-abc"));
1701 }
1702
1703 #[test]
1704 fn test_optimize_response_deserializes_without_job_id() {
1705 let json = r#"{"success": true, "message": "done"}"#;
1706 let resp: OptimizeResponse = serde_json::from_str(json).unwrap();
1707 assert!(resp.job_id.is_none());
1708 }
1709
1710 #[test]
1715 fn test_rebuild_index_request_all_optional_omitted() {
1716 let req = RebuildIndexRequest {
1717 namespace: None,
1718 index_type: None,
1719 force: false,
1720 };
1721 let json = serde_json::to_string(&req).unwrap();
1722 assert!(!json.contains("namespace"));
1723 assert!(!json.contains("index_type"));
1724 assert!(json.contains("\"force\":false"));
1725 }
1726
1727 #[test]
1728 fn test_rebuild_index_request_with_namespace() {
1729 let req = RebuildIndexRequest {
1730 namespace: Some("prod".to_string()),
1731 index_type: Some("hnsw".to_string()),
1732 force: true,
1733 };
1734 let json = serde_json::to_string(&req).unwrap();
1735 assert!(json.contains("\"namespace\":\"prod\""));
1736 assert!(json.contains("\"index_type\":\"hnsw\""));
1737 assert!(json.contains("\"force\":true"));
1738 }
1739
1740 #[test]
1745 fn test_cache_stats_deserializes() {
1746 let json = r#"{
1747 "enabled": true,
1748 "cache_type": "redis",
1749 "entries": 1000,
1750 "size_bytes": 524288,
1751 "hits": 8000,
1752 "misses": 2000,
1753 "hit_rate": 0.8,
1754 "evictions": 50
1755 }"#;
1756 let stats: CacheStats = serde_json::from_str(json).unwrap();
1757 assert!(stats.enabled);
1758 assert_eq!(stats.cache_type, "redis");
1759 assert!((stats.hit_rate - 0.8).abs() < 1e-9);
1760 }
1761
1762 #[test]
1767 fn test_clear_cache_request_without_namespace_omits_field() {
1768 let req = ClearCacheRequest { namespace: None };
1769 let json = serde_json::to_string(&req).unwrap();
1770 assert!(!json.contains("namespace"));
1771 }
1772
1773 #[test]
1774 fn test_clear_cache_request_with_namespace() {
1775 let req = ClearCacheRequest {
1776 namespace: Some("prod-ns".to_string()),
1777 };
1778 let json = serde_json::to_string(&req).unwrap();
1779 assert!(json.contains("\"namespace\":\"prod-ns\""));
1780 }
1781
1782 #[test]
1787 fn test_clear_cache_response_deserializes() {
1788 let json = r#"{"success": true, "entries_cleared": 42, "message": "ok"}"#;
1789 let resp: ClearCacheResponse = serde_json::from_str(json).unwrap();
1790 assert!(resp.success);
1791 assert_eq!(resp.entries_cleared, 42);
1792 }
1793
1794 #[test]
1799 fn test_runtime_config_autopilot_defaults() {
1800 let json = r#"{
1801 "default_index_type": "hnsw",
1802 "cache_enabled": true,
1803 "cache_max_size_bytes": 1073741824,
1804 "rate_limit_enabled": false,
1805 "rate_limit_rps": 1000,
1806 "query_timeout_ms": 5000
1807 }"#;
1808 let cfg: RuntimeConfig = serde_json::from_str(json).unwrap();
1809 assert!(cfg.autopilot_enabled);
1811 assert!((cfg.autopilot_dedup_threshold - 0.93).abs() < 1e-4);
1813 assert_eq!(cfg.autopilot_dedup_interval_hours, 6);
1815 assert_eq!(cfg.autopilot_consolidation_interval_hours, 12);
1817 }
1818
1819 #[test]
1824 fn test_update_config_response_warnings_default_empty() {
1825 let json = r#"{
1826 "success": true,
1827 "config": {
1828 "default_index_type": "hnsw",
1829 "cache_enabled": false,
1830 "cache_max_size_bytes": 0,
1831 "rate_limit_enabled": false,
1832 "rate_limit_rps": 0,
1833 "query_timeout_ms": 5000
1834 },
1835 "message": "updated"
1836 }"#;
1837 let resp: UpdateConfigResponse = serde_json::from_str(json).unwrap();
1838 assert!(resp.success);
1839 assert!(resp.warnings.is_empty());
1840 }
1841
1842 #[test]
1847 fn test_quota_config_all_none_serializes_empty() {
1848 let cfg = QuotaConfig {
1849 max_vectors: None,
1850 max_storage_bytes: None,
1851 max_queries_per_minute: None,
1852 max_writes_per_minute: None,
1853 };
1854 let json = serde_json::to_string(&cfg).unwrap();
1855 assert_eq!(json, "{}");
1856 }
1857
1858 #[test]
1859 fn test_quota_config_with_max_vectors() {
1860 let cfg = QuotaConfig {
1861 max_vectors: Some(1_000_000),
1862 max_storage_bytes: None,
1863 max_queries_per_minute: None,
1864 max_writes_per_minute: None,
1865 };
1866 let json = serde_json::to_string(&cfg).unwrap();
1867 assert!(json.contains("\"max_vectors\":1000000"));
1868 assert!(!json.contains("max_storage"));
1869 }
1870
1871 #[test]
1876 fn test_quota_usage_all_fields_default_zero() {
1877 let json = r#"{}"#;
1878 let usage: QuotaUsage = serde_json::from_str(json).unwrap();
1879 assert_eq!(usage.current_vectors, 0);
1880 assert_eq!(usage.current_storage_bytes, 0);
1881 assert_eq!(usage.queries_this_minute, 0);
1882 assert_eq!(usage.writes_this_minute, 0);
1883 }
1884
1885 #[test]
1890 fn test_quota_list_response_optional_default_config() {
1891 let json = r#"{"quotas": [], "total": 0}"#;
1892 let resp: QuotaListResponse = serde_json::from_str(json).unwrap();
1893 assert!(resp.default_config.is_none());
1894 assert_eq!(resp.total, 0);
1895 }
1896
1897 #[test]
1902 fn test_slow_query_list_response_deserializes() {
1903 let json = r#"{"queries": [], "total": 0, "threshold_ms": 100.0}"#;
1904 let resp: SlowQueryListResponse = serde_json::from_str(json).unwrap();
1905 assert!((resp.threshold_ms - 100.0).abs() < 1e-9);
1906 assert!(resp.queries.is_empty());
1907 }
1908
1909 #[test]
1914 fn test_backup_info_deserializes_minimal() {
1915 let json = r#"{
1916 "backup_id": "bkp-1",
1917 "name": "daily",
1918 "backup_type": "full",
1919 "status": "completed",
1920 "namespaces": ["ns-1"],
1921 "vector_count": 5000,
1922 "size_bytes": 1048576,
1923 "created_at": 1785000000,
1924 "encrypted": true
1925 }"#;
1926 let bkp: BackupInfo = serde_json::from_str(json).unwrap();
1927 assert_eq!(bkp.backup_id, "bkp-1");
1928 assert!(bkp.encrypted);
1929 assert!(bkp.completed_at.is_none());
1930 assert!(bkp.error.is_none());
1931 assert!(bkp.compression.is_none());
1932 }
1933
1934 #[test]
1939 fn test_create_backup_request_minimal_omits_optional() {
1940 let req = CreateBackupRequest {
1941 name: "snapshot".to_string(),
1942 backup_type: None,
1943 namespaces: None,
1944 encrypt: None,
1945 compression: None,
1946 };
1947 let json = serde_json::to_string(&req).unwrap();
1948 assert!(json.contains("\"name\":\"snapshot\""));
1949 assert!(!json.contains("backup_type"));
1950 assert!(!json.contains("namespaces"));
1951 assert!(!json.contains("encrypt"));
1952 assert!(!json.contains("compression"));
1953 }
1954
1955 #[test]
1956 fn test_create_backup_request_with_namespaces() {
1957 let req = CreateBackupRequest {
1958 name: "ns-backup".to_string(),
1959 backup_type: Some("incremental".to_string()),
1960 namespaces: Some(vec!["ns-a".to_string(), "ns-b".to_string()]),
1961 encrypt: Some(true),
1962 compression: None,
1963 };
1964 let json = serde_json::to_string(&req).unwrap();
1965 assert!(json.contains("\"backup_type\":\"incremental\""));
1966 assert!(json.contains("\"ns-a\""));
1967 assert!(json.contains("\"encrypt\":true"));
1968 }
1969
1970 #[test]
1975 fn test_namespace_list_response_deserializes() {
1976 let json = r#"{"namespaces": [], "total": 0, "total_vectors": 0}"#;
1977 let resp: NamespaceListResponse = serde_json::from_str(json).unwrap();
1978 assert_eq!(resp.total, 0);
1979 assert_eq!(resp.total_vectors, 0);
1980 }
1981
1982 #[test]
1987 fn test_index_stats_response_deserializes_with_hashmap() {
1988 let json = r#"{
1989 "namespaces": {
1990 "default": {
1991 "index_type": "hnsw",
1992 "is_built": true,
1993 "size_bytes": 4096,
1994 "indexed_vectors": 200
1995 }
1996 },
1997 "total_indexed_vectors": 200,
1998 "total_size_bytes": 4096
1999 }"#;
2000 let resp: IndexStatsResponse = serde_json::from_str(json).unwrap();
2001 assert_eq!(resp.total_indexed_vectors, 200);
2002 assert!(resp.namespaces.contains_key("default"));
2003 }
2004}