1use super::client::{Result, RpcClient, RpcClientError};
17use super::types::VectorizerValue;
18
19fn need_str(v: &VectorizerValue, cmd: &str, key: &str) -> Result<String> {
24 v.map_get(key)
25 .and_then(|x| x.as_str().map(str::to_owned))
26 .ok_or_else(|| RpcClientError::Server(format!("{cmd}: missing string field '{key}'")))
27}
28
29fn need_int(v: &VectorizerValue, cmd: &str, key: &str) -> Result<i64> {
31 v.map_get(key)
32 .and_then(|x| x.as_int())
33 .ok_or_else(|| RpcClientError::Server(format!("{cmd}: missing int field '{key}'")))
34}
35
36fn need_bool(v: &VectorizerValue, cmd: &str, key: &str) -> Result<bool> {
38 v.map_get(key)
39 .and_then(|x| x.as_bool())
40 .ok_or_else(|| RpcClientError::Server(format!("{cmd}: missing bool field '{key}'")))
41}
42
43fn decode_string_array(v: VectorizerValue, cmd: &str) -> Result<Vec<String>> {
45 let arr = v
46 .as_array()
47 .ok_or_else(|| RpcClientError::Server(format!("{cmd}: expected Array")))?;
48 Ok(arr
49 .iter()
50 .filter_map(|x| x.as_str().map(str::to_owned))
51 .collect())
52}
53
54#[derive(Debug, Clone)]
58pub struct CollectionInfo {
59 pub name: String,
61 pub vector_count: i64,
63 pub document_count: i64,
65 pub dimension: i64,
67 pub metric: String,
69 pub created_at: String,
71 pub updated_at: String,
73}
74
75#[derive(Debug, Clone)]
77pub struct SearchHit {
78 pub id: String,
80 pub score: f64,
82 pub payload: Option<String>,
85}
86
87#[derive(Debug, Clone)]
89pub struct CreateCollectionResult {
90 pub name: String,
91 pub dimension: i64,
92 pub metric: String,
93 pub success: bool,
94}
95
96#[derive(Debug, Clone)]
98pub struct CleanupEmptyResult {
99 pub removed: i64,
100 pub dry_run: bool,
101}
102
103#[derive(Debug, Clone)]
105pub struct VectorWriteResult {
106 pub id: String,
107 pub success: bool,
108}
109
110#[derive(Debug, Clone)]
112pub struct BatchItemResult {
113 pub index: i64,
114 pub id: Option<String>,
115 pub status: String,
116 pub error: Option<String>,
117}
118
119#[derive(Debug, Clone)]
121pub struct BatchInsertResult {
122 pub inserted: i64,
123 pub failed: i64,
124 pub results: Vec<BatchItemResult>,
125}
126
127#[derive(Debug, Clone)]
129pub struct BatchUpdateResult {
130 pub updated: i64,
131 pub failed: i64,
132 pub results: Vec<BatchItemResult>,
133}
134
135#[derive(Debug, Clone)]
137pub struct BatchDeleteResult {
138 pub deleted: i64,
139 pub failed: i64,
140 pub results: Vec<BatchItemResult>,
141}
142
143#[derive(Debug, Clone)]
145pub struct BatchSearchResult {
146 pub index: i64,
147 pub status: String,
148 pub results: Vec<SearchHit>,
149 pub error: Option<String>,
150}
151
152#[derive(Debug, Clone)]
154pub struct MoveRpcResult {
155 pub src: String,
156 pub dst: String,
157 pub moved: i64,
158 pub failed: i64,
159}
160
161#[derive(Debug, Clone)]
163pub struct CopyRpcResult {
164 pub src: String,
165 pub dst: String,
166 pub copied: i64,
167 pub failed: i64,
168}
169
170#[derive(Debug, Clone)]
172pub struct DeleteByFilterRpcResult {
173 pub scanned: i64,
174 pub matched: i64,
175 pub deleted: i64,
176}
177
178#[derive(Debug, Clone)]
180pub struct BulkUpdateMetadataRpcResult {
181 pub scanned: i64,
182 pub matched: i64,
183 pub updated: i64,
184}
185
186#[derive(Debug, Clone)]
188pub struct SetExpiryResult {
189 pub id: String,
190 pub expires_at: i64,
191 pub success: bool,
192}
193
194#[derive(Debug, Clone)]
196pub struct EmbedResult {
197 pub embedding: Vec<f64>,
198 pub model: String,
199 pub dimension: i64,
200}
201
202#[derive(Debug, Clone)]
204pub struct VectorListResult {
205 pub items: Vec<VectorizerValue>,
206 pub total: i64,
207 pub page: i64,
208 pub limit: i64,
209}
210
211#[derive(Debug, Clone)]
216pub struct SearchExplainResult {
217 pub hits: Vec<SearchHit>,
218 pub collection: String,
219 pub k: i64,
220 pub trace: SearchTrace,
221}
222
223#[derive(Debug, Clone)]
225pub struct SearchTrace {
226 pub visited_nodes: i64,
227 pub ef_search: i64,
228 pub hnsw_search_ms: f64,
229 pub total_ms: f64,
230}
231
232#[derive(Debug, Clone)]
234pub struct DiscoverResult {
235 pub answer_prompt: String,
236 pub sections: i64,
237 pub bullets: i64,
238 pub chunks: i64,
239}
240
241#[derive(Debug, Clone)]
243pub struct ScoredCollection {
244 pub name: String,
245 pub score: f64,
246 pub vector_count: i64,
247}
248
249#[derive(Debug, Clone)]
251pub struct ExpandQueriesResult {
252 pub original_query: String,
253 pub expanded_queries: Vec<String>,
254 pub count: i64,
255}
256
257#[derive(Debug, Clone)]
259pub struct DiscoveryChunk {
260 pub collection: String,
261 pub score: f64,
262 pub content_preview: String,
263}
264
265#[derive(Debug, Clone)]
267pub struct CompressBullet {
268 pub text: String,
269 pub source_id: String,
270 pub score: f64,
271}
272
273#[derive(Debug, Clone)]
275pub struct AnswerPlanResult {
276 pub sections: Vec<AnswerPlanSection>,
277 pub total_bullets: i64,
278}
279
280#[derive(Debug, Clone)]
282pub struct AnswerPlanSection {
283 pub title: String,
284 pub bullets_count: i64,
285}
286
287#[derive(Debug, Clone)]
289pub struct RenderPromptResult {
290 pub prompt: String,
291 pub length: i64,
292 pub estimated_tokens: i64,
293}
294
295#[derive(Debug, Clone)]
297pub struct GraphDiscoveryStatus {
298 pub total_nodes: i64,
299 pub nodes_with_edges: i64,
300 pub total_edges: i64,
301 pub progress_percentage: f64,
302}
303
304#[derive(Debug, Clone)]
306pub struct DiscoverEdgesResult {
307 pub success: bool,
308 pub total_nodes: i64,
309 pub nodes_processed: i64,
310 pub nodes_with_edges: i64,
311 pub total_edges_created: i64,
312}
313
314#[derive(Debug, Clone)]
316pub struct DiscoverEdgesForNodeResult {
317 pub success: bool,
318 pub node_id: String,
319 pub edges_created: i64,
320}
321
322#[derive(Debug, Clone)]
324pub struct AdminStats {
325 pub collections_count: i64,
326 pub total_vectors: i64,
327 pub version: String,
328}
329
330#[derive(Debug, Clone)]
332pub struct AdminStatus {
333 pub ready: bool,
334 pub collections_count: i64,
335 pub version: String,
336}
337
338#[derive(Debug, Clone)]
340pub struct SlowQueryConfigResult {
341 pub threshold_ms: i64,
342 pub capacity: i64,
343 pub status: String,
344}
345
346#[derive(Debug, Clone)]
348pub struct AuthMeResult {
349 pub username: String,
350 pub authenticated: bool,
351}
352
353#[derive(Debug, Clone)]
355pub struct RefreshTokenResult {
356 pub access_token: String,
357 pub token_type: String,
358}
359
360#[derive(Debug, Clone)]
362pub struct ValidatePasswordResult {
363 pub valid: bool,
364 pub errors: Vec<String>,
365}
366
367#[derive(Debug, Clone)]
369pub struct ApiKeyCreated {
370 pub api_key: String,
371 pub id: String,
372 pub name: String,
373}
374
375#[derive(Debug, Clone)]
377pub struct RotatedApiKey {
378 pub old_key_id: String,
379 pub new_key_id: String,
380 pub new_token: String,
381 pub grace_until: Option<String>,
382}
383
384#[derive(Debug, Clone)]
386pub struct ReplicationConfigureResult {
387 pub success: bool,
388 pub role: String,
389 pub message: String,
390}
391
392#[derive(Debug, Clone)]
394pub struct RebalanceStatus {
395 pub status: Option<String>,
397 pub message: Option<String>,
398}
399
400fn decode_batch_items(arr: &[VectorizerValue]) -> Vec<BatchItemResult> {
403 arr.iter()
404 .map(|entry| {
405 let index = entry.map_get("index").and_then(|v| v.as_int()).unwrap_or(0);
406 let id = entry
407 .map_get("id")
408 .and_then(|v| v.as_str())
409 .map(str::to_owned);
410 let status = entry
411 .map_get("status")
412 .and_then(|v| v.as_str())
413 .unwrap_or("unknown")
414 .to_owned();
415 let error = entry
416 .map_get("error")
417 .and_then(|v| v.as_str())
418 .map(str::to_owned);
419 BatchItemResult {
420 index,
421 id,
422 status,
423 error,
424 }
425 })
426 .collect()
427}
428
429fn decode_search_hits(arr: &[VectorizerValue]) -> Vec<SearchHit> {
430 arr.iter()
431 .filter_map(|entry| {
432 let id = entry.map_get("id")?.as_str().map(str::to_owned)?;
433 let score = entry
434 .map_get("score")
435 .and_then(|v| v.as_float())
436 .unwrap_or(0.0);
437 let payload = entry
438 .map_get("payload")
439 .and_then(|v| v.as_str())
440 .map(str::to_owned);
441 Some(SearchHit { id, score, payload })
442 })
443 .collect()
444}
445
446impl RpcClient {
451 pub async fn list_collections(&self) -> Result<Vec<String>> {
454 let v = self.call("collections.list", vec![]).await?;
455 decode_string_array(v, "collections.list")
456 }
457
458 pub async fn get_collection_info(&self, name: &str) -> Result<CollectionInfo> {
460 let v = self
461 .call(
462 "collections.get_info",
463 vec![VectorizerValue::Str(name.to_owned())],
464 )
465 .await?;
466 Ok(CollectionInfo {
467 name: need_str(&v, "collections.get_info", "name")?,
468 vector_count: need_int(&v, "collections.get_info", "vector_count")?,
469 document_count: need_int(&v, "collections.get_info", "document_count")?,
470 dimension: need_int(&v, "collections.get_info", "dimension")?,
471 metric: need_str(&v, "collections.get_info", "metric")?,
472 created_at: need_str(&v, "collections.get_info", "created_at")?,
473 updated_at: need_str(&v, "collections.get_info", "updated_at")?,
474 })
475 }
476
477 pub async fn create_collection(
482 &self,
483 name: &str,
484 config: VectorizerValue,
485 ) -> Result<CreateCollectionResult> {
486 let v = self
487 .call(
488 "collections.create",
489 vec![VectorizerValue::Str(name.to_owned()), config],
490 )
491 .await?;
492 Ok(CreateCollectionResult {
493 name: need_str(&v, "collections.create", "name")?,
494 dimension: need_int(&v, "collections.create", "dimension")?,
495 metric: need_str(&v, "collections.create", "metric")?,
496 success: need_bool(&v, "collections.create", "success")?,
497 })
498 }
499
500 pub async fn delete_collection(&self, name: &str) -> Result<bool> {
502 let v = self
503 .call(
504 "collections.delete",
505 vec![VectorizerValue::Str(name.to_owned())],
506 )
507 .await?;
508 need_bool(&v, "collections.delete", "success")
509 }
510
511 pub async fn list_empty_collections(&self) -> Result<Vec<String>> {
513 let v = self.call("collections.list_empty", vec![]).await?;
514 decode_string_array(v, "collections.list_empty")
515 }
516
517 pub async fn cleanup_empty_collections(&self, dry_run: bool) -> Result<CleanupEmptyResult> {
522 let config = VectorizerValue::Map(vec![(
523 VectorizerValue::Str("dry_run".into()),
524 VectorizerValue::Bool(dry_run),
525 )]);
526 let v = self.call("collections.cleanup_empty", vec![config]).await?;
527 Ok(CleanupEmptyResult {
528 removed: need_int(&v, "collections.cleanup_empty", "removed")?,
529 dry_run: need_bool(&v, "collections.cleanup_empty", "dry_run")?,
530 })
531 }
532
533 pub async fn force_save_collection(&self, name: &str) -> Result<bool> {
535 let v = self
536 .call(
537 "collections.force_save",
538 vec![VectorizerValue::Str(name.to_owned())],
539 )
540 .await?;
541 need_bool(&v, "collections.force_save", "success")
542 }
543}
544
545impl RpcClient {
550 pub async fn get_vector(&self, collection: &str, vector_id: &str) -> Result<VectorizerValue> {
554 self.call(
555 "vectors.get",
556 vec![
557 VectorizerValue::Str(collection.to_owned()),
558 VectorizerValue::Str(vector_id.to_owned()),
559 ],
560 )
561 .await
562 }
563
564 pub async fn insert_vector(
569 &self,
570 collection: &str,
571 id: Option<&str>,
572 data: Vec<f32>,
573 payload: Option<VectorizerValue>,
574 ) -> Result<VectorWriteResult> {
575 let id_val = id
576 .map(|s| VectorizerValue::Str(s.to_owned()))
577 .unwrap_or(VectorizerValue::Null);
578 let data_val = VectorizerValue::Array(
579 data.into_iter()
580 .map(|f| VectorizerValue::Float(f as f64))
581 .collect(),
582 );
583 let mut args = vec![
584 VectorizerValue::Str(collection.to_owned()),
585 id_val,
586 data_val,
587 ];
588 if let Some(p) = payload {
589 args.push(p);
590 }
591 let v = self.call("vectors.insert", args).await?;
592 Ok(VectorWriteResult {
593 id: need_str(&v, "vectors.insert", "id")?,
594 success: need_bool(&v, "vectors.insert", "success")?,
595 })
596 }
597
598 pub async fn insert_text_vector(
602 &self,
603 collection: &str,
604 id: Option<&str>,
605 text: &str,
606 payload: Option<VectorizerValue>,
607 ) -> Result<VectorWriteResult> {
608 let id_val = id
609 .map(|s| VectorizerValue::Str(s.to_owned()))
610 .unwrap_or(VectorizerValue::Null);
611 let mut args = vec![
612 VectorizerValue::Str(collection.to_owned()),
613 id_val,
614 VectorizerValue::Str(text.to_owned()),
615 ];
616 if let Some(p) = payload {
617 args.push(p);
618 }
619 let v = self.call("vectors.insert_text", args).await?;
620 Ok(VectorWriteResult {
621 id: need_str(&v, "vectors.insert_text", "id")?,
622 success: need_bool(&v, "vectors.insert_text", "success")?,
623 })
624 }
625
626 pub async fn update_vector(
628 &self,
629 collection: &str,
630 id: &str,
631 data: Vec<f32>,
632 payload: Option<VectorizerValue>,
633 ) -> Result<VectorWriteResult> {
634 let data_val = VectorizerValue::Array(
635 data.into_iter()
636 .map(|f| VectorizerValue::Float(f as f64))
637 .collect(),
638 );
639 let mut args = vec![
640 VectorizerValue::Str(collection.to_owned()),
641 VectorizerValue::Str(id.to_owned()),
642 data_val,
643 ];
644 if let Some(p) = payload {
645 args.push(p);
646 }
647 let v = self.call("vectors.update", args).await?;
648 Ok(VectorWriteResult {
649 id: need_str(&v, "vectors.update", "id")?,
650 success: need_bool(&v, "vectors.update", "success")?,
651 })
652 }
653
654 pub async fn delete_vector_rpc(&self, collection: &str, id: &str) -> Result<bool> {
656 let v = self
657 .call(
658 "vectors.delete",
659 vec![
660 VectorizerValue::Str(collection.to_owned()),
661 VectorizerValue::Str(id.to_owned()),
662 ],
663 )
664 .await?;
665 need_bool(&v, "vectors.delete", "success")
666 }
667
668 pub async fn list_vectors(
672 &self,
673 collection: &str,
674 page: i64,
675 limit: i64,
676 ) -> Result<VectorListResult> {
677 let v = self
678 .call(
679 "vectors.list",
680 vec![
681 VectorizerValue::Str(collection.to_owned()),
682 VectorizerValue::Int(page),
683 VectorizerValue::Int(limit),
684 ],
685 )
686 .await?;
687 let items = v
688 .map_get("items")
689 .and_then(|x| x.as_array())
690 .map(|a| a.to_vec())
691 .unwrap_or_default();
692 Ok(VectorListResult {
693 items,
694 total: need_int(&v, "vectors.list", "total")?,
695 page: need_int(&v, "vectors.list", "page")?,
696 limit: need_int(&v, "vectors.list", "limit")?,
697 })
698 }
699
700 pub async fn embed_text(&self, text: &str, model: Option<&str>) -> Result<EmbedResult> {
702 let mut args = vec![VectorizerValue::Str(text.to_owned())];
703 if let Some(m) = model {
704 args.push(VectorizerValue::Str(m.to_owned()));
705 }
706 let v = self.call("vectors.embed", args).await?;
707 let embedding = v
708 .map_get("embedding")
709 .and_then(|x| x.as_array())
710 .map(|arr| arr.iter().filter_map(|x| x.as_float()).collect())
711 .unwrap_or_default();
712 Ok(EmbedResult {
713 embedding,
714 model: v
715 .map_get("model")
716 .and_then(|x| x.as_str())
717 .unwrap_or("bm25")
718 .to_owned(),
719 dimension: v.map_get("dimension").and_then(|x| x.as_int()).unwrap_or(0),
720 })
721 }
722
723 pub async fn batch_insert_vectors(
729 &self,
730 collection: &str,
731 items: Vec<VectorizerValue>,
732 ) -> Result<BatchInsertResult> {
733 let v = self
734 .call(
735 "vectors.batch_insert",
736 vec![
737 VectorizerValue::Str(collection.to_owned()),
738 VectorizerValue::Array(items),
739 ],
740 )
741 .await?;
742 let results = v
743 .map_get("results")
744 .and_then(|x| x.as_array())
745 .map(|a| decode_batch_items(a))
746 .unwrap_or_default();
747 Ok(BatchInsertResult {
748 inserted: v.map_get("inserted").and_then(|x| x.as_int()).unwrap_or(0),
749 failed: v.map_get("failed").and_then(|x| x.as_int()).unwrap_or(0),
750 results,
751 })
752 }
753
754 pub async fn batch_insert_texts(
759 &self,
760 collection: &str,
761 items: Vec<VectorizerValue>,
762 ) -> Result<BatchInsertResult> {
763 let v = self
764 .call(
765 "vectors.batch_insert_texts",
766 vec![
767 VectorizerValue::Str(collection.to_owned()),
768 VectorizerValue::Array(items),
769 ],
770 )
771 .await?;
772 let results = v
773 .map_get("results")
774 .and_then(|x| x.as_array())
775 .map(|a| decode_batch_items(a))
776 .unwrap_or_default();
777 Ok(BatchInsertResult {
778 inserted: v.map_get("inserted").and_then(|x| x.as_int()).unwrap_or(0),
779 failed: v.map_get("failed").and_then(|x| x.as_int()).unwrap_or(0),
780 results,
781 })
782 }
783
784 pub async fn batch_search(
789 &self,
790 requests: Vec<VectorizerValue>,
791 ) -> Result<Vec<BatchSearchResult>> {
792 let v = self
793 .call(
794 "vectors.batch_search",
795 vec![VectorizerValue::Array(requests)],
796 )
797 .await?;
798 let arr = v
799 .as_array()
800 .ok_or_else(|| RpcClientError::Server("vectors.batch_search: expected Array".into()))?;
801 Ok(arr
802 .iter()
803 .map(|entry| {
804 let index = entry.map_get("index").and_then(|x| x.as_int()).unwrap_or(0);
805 let status = entry
806 .map_get("status")
807 .and_then(|x| x.as_str())
808 .unwrap_or("unknown")
809 .to_owned();
810 let error = entry
811 .map_get("error")
812 .and_then(|x| x.as_str())
813 .map(str::to_owned);
814 let results = entry
815 .map_get("results")
816 .and_then(|x| x.as_array())
817 .map(|a| decode_search_hits(a))
818 .unwrap_or_default();
819 BatchSearchResult {
820 index,
821 status,
822 results,
823 error,
824 }
825 })
826 .collect())
827 }
828
829 pub async fn batch_update_vectors(
834 &self,
835 collection: &str,
836 updates: Vec<VectorizerValue>,
837 ) -> Result<BatchUpdateResult> {
838 let v = self
839 .call(
840 "vectors.batch_update",
841 vec![
842 VectorizerValue::Str(collection.to_owned()),
843 VectorizerValue::Array(updates),
844 ],
845 )
846 .await?;
847 let results = v
848 .map_get("results")
849 .and_then(|x| x.as_array())
850 .map(|a| decode_batch_items(a))
851 .unwrap_or_default();
852 Ok(BatchUpdateResult {
853 updated: v.map_get("updated").and_then(|x| x.as_int()).unwrap_or(0),
854 failed: v.map_get("failed").and_then(|x| x.as_int()).unwrap_or(0),
855 results,
856 })
857 }
858
859 pub async fn batch_delete_vectors(
861 &self,
862 collection: &str,
863 ids: Vec<String>,
864 ) -> Result<BatchDeleteResult> {
865 let ids_val = VectorizerValue::Array(ids.into_iter().map(VectorizerValue::Str).collect());
866 let v = self
867 .call(
868 "vectors.batch_delete",
869 vec![VectorizerValue::Str(collection.to_owned()), ids_val],
870 )
871 .await?;
872 let results = v
873 .map_get("results")
874 .and_then(|x| x.as_array())
875 .map(|a| decode_batch_items(a))
876 .unwrap_or_default();
877 Ok(BatchDeleteResult {
878 deleted: v.map_get("deleted").and_then(|x| x.as_int()).unwrap_or(0),
879 failed: v.map_get("failed").and_then(|x| x.as_int()).unwrap_or(0),
880 results,
881 })
882 }
883
884 pub async fn move_vectors_rpc(
889 &self,
890 src: &str,
891 dst: &str,
892 ids: Vec<String>,
893 ) -> Result<MoveRpcResult> {
894 let ids_val = VectorizerValue::Array(ids.into_iter().map(VectorizerValue::Str).collect());
895 let v = self
896 .call(
897 "vectors.move",
898 vec![
899 VectorizerValue::Str(src.to_owned()),
900 VectorizerValue::Str(dst.to_owned()),
901 ids_val,
902 ],
903 )
904 .await?;
905 Ok(MoveRpcResult {
906 src: need_str(&v, "vectors.move", "src")?,
907 dst: need_str(&v, "vectors.move", "dst")?,
908 moved: v.map_get("moved").and_then(|x| x.as_int()).unwrap_or(0),
909 failed: v.map_get("failed").and_then(|x| x.as_int()).unwrap_or(0),
910 })
911 }
912
913 pub async fn copy_vectors_rpc(
915 &self,
916 src: &str,
917 dst: &str,
918 ids: Vec<String>,
919 ) -> Result<CopyRpcResult> {
920 let ids_val = VectorizerValue::Array(ids.into_iter().map(VectorizerValue::Str).collect());
921 let v = self
922 .call(
923 "vectors.copy",
924 vec![
925 VectorizerValue::Str(src.to_owned()),
926 VectorizerValue::Str(dst.to_owned()),
927 ids_val,
928 ],
929 )
930 .await?;
931 Ok(CopyRpcResult {
932 src: need_str(&v, "vectors.copy", "src")?,
933 dst: need_str(&v, "vectors.copy", "dst")?,
934 copied: v.map_get("copied").and_then(|x| x.as_int()).unwrap_or(0),
935 failed: v.map_get("failed").and_then(|x| x.as_int()).unwrap_or(0),
936 })
937 }
938
939 pub async fn delete_by_filter_rpc(
944 &self,
945 collection: &str,
946 filter: VectorizerValue,
947 ) -> Result<DeleteByFilterRpcResult> {
948 let v = self
949 .call(
950 "vectors.delete_by_filter",
951 vec![VectorizerValue::Str(collection.to_owned()), filter],
952 )
953 .await?;
954 Ok(DeleteByFilterRpcResult {
955 scanned: v.map_get("scanned").and_then(|x| x.as_int()).unwrap_or(0),
956 matched: v.map_get("matched").and_then(|x| x.as_int()).unwrap_or(0),
957 deleted: v.map_get("deleted").and_then(|x| x.as_int()).unwrap_or(0),
958 })
959 }
960
961 pub async fn bulk_update_metadata_rpc(
967 &self,
968 collection: &str,
969 filter: VectorizerValue,
970 patch: VectorizerValue,
971 ) -> Result<BulkUpdateMetadataRpcResult> {
972 let v = self
973 .call(
974 "vectors.bulk_update_metadata",
975 vec![VectorizerValue::Str(collection.to_owned()), filter, patch],
976 )
977 .await?;
978 Ok(BulkUpdateMetadataRpcResult {
979 scanned: v.map_get("scanned").and_then(|x| x.as_int()).unwrap_or(0),
980 matched: v.map_get("matched").and_then(|x| x.as_int()).unwrap_or(0),
981 updated: v.map_get("updated").and_then(|x| x.as_int()).unwrap_or(0),
982 })
983 }
984
985 pub async fn set_vector_expiry(
989 &self,
990 collection: &str,
991 id: &str,
992 expires_at: &str,
993 ) -> Result<SetExpiryResult> {
994 let v = self
995 .call(
996 "vectors.set_expiry",
997 vec![
998 VectorizerValue::Str(collection.to_owned()),
999 VectorizerValue::Str(id.to_owned()),
1000 VectorizerValue::Str(expires_at.to_owned()),
1001 ],
1002 )
1003 .await?;
1004 Ok(SetExpiryResult {
1005 id: need_str(&v, "vectors.set_expiry", "id")?,
1006 expires_at: need_int(&v, "vectors.set_expiry", "expires_at")?,
1007 success: need_bool(&v, "vectors.set_expiry", "success")?,
1008 })
1009 }
1010}
1011
1012impl RpcClient {
1017 pub async fn search_basic(
1020 &self,
1021 collection: &str,
1022 query: &str,
1023 limit: usize,
1024 ) -> Result<Vec<SearchHit>> {
1025 let args = vec![
1026 VectorizerValue::Str(collection.to_owned()),
1027 VectorizerValue::Str(query.to_owned()),
1028 VectorizerValue::Int(limit as i64),
1029 ];
1030 let v = self.call("search.basic", args).await?;
1031 let arr = v
1032 .as_array()
1033 .ok_or_else(|| RpcClientError::Server("search.basic: expected Array".into()))?;
1034 Ok(decode_search_hits(arr))
1035 }
1036
1037 pub async fn search_intelligent(&self, request: VectorizerValue) -> Result<VectorizerValue> {
1043 self.call("search.intelligent", vec![request]).await
1044 }
1045
1046 pub async fn search_by_text(
1048 &self,
1049 collection: &str,
1050 query: &str,
1051 limit: usize,
1052 ) -> Result<Vec<SearchHit>> {
1053 let v = self
1054 .call(
1055 "search.by_text",
1056 vec![
1057 VectorizerValue::Str(collection.to_owned()),
1058 VectorizerValue::Str(query.to_owned()),
1059 VectorizerValue::Int(limit as i64),
1060 ],
1061 )
1062 .await?;
1063 let arr = v
1064 .map_get("results")
1065 .and_then(|x| x.as_array())
1066 .ok_or_else(|| {
1067 RpcClientError::Server("search.by_text: missing results array".into())
1068 })?;
1069 Ok(decode_search_hits(arr))
1070 }
1071
1072 pub async fn search_by_file(
1078 &self,
1079 collection: &str,
1080 request: VectorizerValue,
1081 ) -> Result<Vec<SearchHit>> {
1082 let v = self
1083 .call(
1084 "search.by_file",
1085 vec![VectorizerValue::Str(collection.to_owned()), request],
1086 )
1087 .await?;
1088 let arr = v
1089 .map_get("results")
1090 .and_then(|x| x.as_array())
1091 .ok_or_else(|| {
1092 RpcClientError::Server("search.by_file: missing results array".into())
1093 })?;
1094 Ok(decode_search_hits(arr))
1095 }
1096
1097 pub async fn search_hybrid(
1103 &self,
1104 collection: &str,
1105 request: VectorizerValue,
1106 ) -> Result<Vec<SearchHit>> {
1107 let v = self
1108 .call(
1109 "search.hybrid",
1110 vec![VectorizerValue::Str(collection.to_owned()), request],
1111 )
1112 .await?;
1113 let arr = v
1114 .map_get("results")
1115 .and_then(|x| x.as_array())
1116 .ok_or_else(|| RpcClientError::Server("search.hybrid: missing results array".into()))?;
1117 Ok(decode_search_hits(arr))
1118 }
1119
1120 pub async fn search_semantic(&self, request: VectorizerValue) -> Result<VectorizerValue> {
1126 self.call("search.semantic", vec![request]).await
1127 }
1128
1129 pub async fn search_contextual(&self, request: VectorizerValue) -> Result<VectorizerValue> {
1135 self.call("search.contextual", vec![request]).await
1136 }
1137
1138 pub async fn search_multi_collection(
1144 &self,
1145 request: VectorizerValue,
1146 ) -> Result<VectorizerValue> {
1147 self.call("search.multi_collection", vec![request]).await
1148 }
1149
1150 pub async fn search_explain(
1155 &self,
1156 collection: &str,
1157 request: VectorizerValue,
1158 ) -> Result<SearchExplainResult> {
1159 let v = self
1160 .call(
1161 "search.explain",
1162 vec![VectorizerValue::Str(collection.to_owned()), request],
1163 )
1164 .await?;
1165 let hits = v
1166 .map_get("hits")
1167 .and_then(|x| x.as_array())
1168 .map(|a| decode_search_hits(a))
1169 .unwrap_or_default();
1170 let trace_val = v.map_get("trace").cloned().unwrap_or(VectorizerValue::Null);
1171 let trace = SearchTrace {
1172 visited_nodes: trace_val
1173 .map_get("visited_nodes")
1174 .and_then(|x| x.as_int())
1175 .unwrap_or(0),
1176 ef_search: trace_val
1177 .map_get("ef_search")
1178 .and_then(|x| x.as_int())
1179 .unwrap_or(0),
1180 hnsw_search_ms: trace_val
1181 .map_get("hnsw_search_ms")
1182 .and_then(|x| x.as_float())
1183 .unwrap_or(0.0),
1184 total_ms: trace_val
1185 .map_get("total_ms")
1186 .and_then(|x| x.as_float())
1187 .unwrap_or(0.0),
1188 };
1189 Ok(SearchExplainResult {
1190 hits,
1191 collection: v
1192 .map_get("collection")
1193 .and_then(|x| x.as_str())
1194 .unwrap_or("")
1195 .to_owned(),
1196 k: v.map_get("k").and_then(|x| x.as_int()).unwrap_or(0),
1197 trace,
1198 })
1199 }
1200}
1201
1202impl RpcClient {
1207 pub async fn discover(&self, request: VectorizerValue) -> Result<DiscoverResult> {
1214 let v = self.call("discovery.discover", vec![request]).await?;
1215 Ok(DiscoverResult {
1216 answer_prompt: need_str(&v, "discovery.discover", "answer_prompt")?,
1217 sections: v.map_get("sections").and_then(|x| x.as_int()).unwrap_or(0),
1218 bullets: v.map_get("bullets").and_then(|x| x.as_int()).unwrap_or(0),
1219 chunks: v.map_get("chunks").and_then(|x| x.as_int()).unwrap_or(0),
1220 })
1221 }
1222
1223 pub async fn filter_collections(&self, request: VectorizerValue) -> Result<Vec<String>> {
1229 let v = self
1230 .call("discovery.filter_collections", vec![request])
1231 .await?;
1232 let arr = v
1233 .map_get("filtered_collections")
1234 .and_then(|x| x.as_array())
1235 .ok_or_else(|| {
1236 RpcClientError::Server(
1237 "discovery.filter_collections: missing filtered_collections".into(),
1238 )
1239 })?;
1240 Ok(arr
1241 .iter()
1242 .filter_map(|entry| entry.map_get("name")?.as_str().map(str::to_owned))
1243 .collect())
1244 }
1245
1246 pub async fn score_collections(
1250 &self,
1251 request: VectorizerValue,
1252 ) -> Result<Vec<ScoredCollection>> {
1253 let v = self
1254 .call("discovery.score_collections", vec![request])
1255 .await?;
1256 let arr = v
1257 .map_get("scored_collections")
1258 .and_then(|x| x.as_array())
1259 .ok_or_else(|| {
1260 RpcClientError::Server(
1261 "discovery.score_collections: missing scored_collections".into(),
1262 )
1263 })?;
1264 Ok(arr
1265 .iter()
1266 .map(|entry| ScoredCollection {
1267 name: entry
1268 .map_get("name")
1269 .and_then(|x| x.as_str())
1270 .unwrap_or("")
1271 .to_owned(),
1272 score: entry
1273 .map_get("score")
1274 .and_then(|x| x.as_float())
1275 .unwrap_or(0.0),
1276 vector_count: entry
1277 .map_get("vector_count")
1278 .and_then(|x| x.as_int())
1279 .unwrap_or(0),
1280 })
1281 .collect())
1282 }
1283
1284 pub async fn expand_queries(&self, request: VectorizerValue) -> Result<ExpandQueriesResult> {
1290 let v = self.call("discovery.expand_queries", vec![request]).await?;
1291 let expanded = v
1292 .map_get("expanded_queries")
1293 .and_then(|x| x.as_array())
1294 .map(|arr| {
1295 arr.iter()
1296 .filter_map(|x| x.as_str().map(str::to_owned))
1297 .collect()
1298 })
1299 .unwrap_or_default();
1300 Ok(ExpandQueriesResult {
1301 original_query: need_str(&v, "discovery.expand_queries", "original_query")?,
1302 count: v.map_get("count").and_then(|x| x.as_int()).unwrap_or(0),
1303 expanded_queries: expanded,
1304 })
1305 }
1306
1307 pub async fn broad_discovery(&self, request: VectorizerValue) -> Result<Vec<DiscoveryChunk>> {
1313 let v = self
1314 .call("discovery.broad_discovery", vec![request])
1315 .await?;
1316 let arr = v
1317 .map_get("chunks")
1318 .and_then(|x| x.as_array())
1319 .ok_or_else(|| {
1320 RpcClientError::Server("discovery.broad_discovery: missing chunks".into())
1321 })?;
1322 Ok(arr
1323 .iter()
1324 .map(|entry| DiscoveryChunk {
1325 collection: entry
1326 .map_get("collection")
1327 .and_then(|x| x.as_str())
1328 .unwrap_or("")
1329 .to_owned(),
1330 score: entry
1331 .map_get("score")
1332 .and_then(|x| x.as_float())
1333 .unwrap_or(0.0),
1334 content_preview: entry
1335 .map_get("content_preview")
1336 .and_then(|x| x.as_str())
1337 .unwrap_or("")
1338 .to_owned(),
1339 })
1340 .collect())
1341 }
1342
1343 pub async fn semantic_focus(&self, request: VectorizerValue) -> Result<Vec<DiscoveryChunk>> {
1348 let v = self.call("discovery.semantic_focus", vec![request]).await?;
1349 let arr = v
1350 .map_get("chunks")
1351 .and_then(|x| x.as_array())
1352 .ok_or_else(|| {
1353 RpcClientError::Server("discovery.semantic_focus: missing chunks".into())
1354 })?;
1355 Ok(arr
1356 .iter()
1357 .map(|entry| DiscoveryChunk {
1358 collection: entry
1359 .map_get("collection")
1360 .and_then(|x| x.as_str())
1361 .unwrap_or("")
1362 .to_owned(),
1363 score: entry
1364 .map_get("score")
1365 .and_then(|x| x.as_float())
1366 .unwrap_or(0.0),
1367 content_preview: entry
1368 .map_get("content_preview")
1369 .and_then(|x| x.as_str())
1370 .unwrap_or("")
1371 .to_owned(),
1372 })
1373 .collect())
1374 }
1375
1376 pub async fn promote_readme(&self, request: VectorizerValue) -> Result<VectorizerValue> {
1383 self.call("discovery.promote_readme", vec![request]).await
1384 }
1385
1386 pub async fn compress_evidence(&self, request: VectorizerValue) -> Result<Vec<CompressBullet>> {
1391 let v = self
1392 .call("discovery.compress_evidence", vec![request])
1393 .await?;
1394 let arr = v
1395 .map_get("bullets")
1396 .and_then(|x| x.as_array())
1397 .ok_or_else(|| {
1398 RpcClientError::Server("discovery.compress_evidence: missing bullets".into())
1399 })?;
1400 Ok(arr
1401 .iter()
1402 .map(|entry| CompressBullet {
1403 text: entry
1404 .map_get("text")
1405 .and_then(|x| x.as_str())
1406 .unwrap_or("")
1407 .to_owned(),
1408 source_id: entry
1409 .map_get("source_id")
1410 .and_then(|x| x.as_str())
1411 .unwrap_or("")
1412 .to_owned(),
1413 score: entry
1414 .map_get("score")
1415 .and_then(|x| x.as_float())
1416 .unwrap_or(0.0),
1417 })
1418 .collect())
1419 }
1420
1421 pub async fn build_answer_plan(&self, request: VectorizerValue) -> Result<AnswerPlanResult> {
1427 let v = self
1428 .call("discovery.build_answer_plan", vec![request])
1429 .await?;
1430 let sections = v
1431 .map_get("sections")
1432 .and_then(|x| x.as_array())
1433 .map(|arr| {
1434 arr.iter()
1435 .map(|entry| AnswerPlanSection {
1436 title: entry
1437 .map_get("title")
1438 .and_then(|x| x.as_str())
1439 .unwrap_or("")
1440 .to_owned(),
1441 bullets_count: entry
1442 .map_get("bullets_count")
1443 .and_then(|x| x.as_int())
1444 .unwrap_or(0),
1445 })
1446 .collect()
1447 })
1448 .unwrap_or_default();
1449 Ok(AnswerPlanResult {
1450 sections,
1451 total_bullets: v
1452 .map_get("total_bullets")
1453 .and_then(|x| x.as_int())
1454 .unwrap_or(0),
1455 })
1456 }
1457
1458 pub async fn render_llm_prompt(&self, request: VectorizerValue) -> Result<RenderPromptResult> {
1464 let v = self
1465 .call("discovery.render_llm_prompt", vec![request])
1466 .await?;
1467 Ok(RenderPromptResult {
1468 prompt: need_str(&v, "discovery.render_llm_prompt", "prompt")?,
1469 length: v.map_get("length").and_then(|x| x.as_int()).unwrap_or(0),
1470 estimated_tokens: v
1471 .map_get("estimated_tokens")
1472 .and_then(|x| x.as_int())
1473 .unwrap_or(0),
1474 })
1475 }
1476}
1477
1478impl RpcClient {
1483 pub async fn file_content(&self, request: VectorizerValue) -> Result<VectorizerValue> {
1488 self.call("file.content", vec![request]).await
1489 }
1490
1491 pub async fn file_list(&self, request: VectorizerValue) -> Result<VectorizerValue> {
1497 self.call("file.list", vec![request]).await
1498 }
1499
1500 pub async fn file_summary(&self, request: VectorizerValue) -> Result<VectorizerValue> {
1506 self.call("file.summary", vec![request]).await
1507 }
1508
1509 pub async fn file_chunks(&self, request: VectorizerValue) -> Result<VectorizerValue> {
1515 self.call("file.chunks", vec![request]).await
1516 }
1517
1518 pub async fn file_outline(&self, request: VectorizerValue) -> Result<VectorizerValue> {
1524 self.call("file.outline", vec![request]).await
1525 }
1526
1527 pub async fn file_related(&self, request: VectorizerValue) -> Result<VectorizerValue> {
1533 self.call("file.related", vec![request]).await
1534 }
1535
1536 pub async fn file_search_by_type(&self, request: VectorizerValue) -> Result<VectorizerValue> {
1542 self.call("file.search_by_type", vec![request]).await
1543 }
1544}
1545
1546impl RpcClient {
1551 pub async fn graph_list_nodes(&self, collection: &str) -> Result<VectorizerValue> {
1553 self.call(
1554 "graph.list_nodes",
1555 vec![VectorizerValue::Str(collection.to_owned())],
1556 )
1557 .await
1558 }
1559
1560 pub async fn graph_neighbors(
1562 &self,
1563 collection: &str,
1564 node_id: &str,
1565 ) -> Result<VectorizerValue> {
1566 self.call(
1567 "graph.neighbors",
1568 vec![
1569 VectorizerValue::Str(collection.to_owned()),
1570 VectorizerValue::Str(node_id.to_owned()),
1571 ],
1572 )
1573 .await
1574 }
1575
1576 pub async fn graph_find_related(
1578 &self,
1579 collection: &str,
1580 node_id: &str,
1581 max_hops: i64,
1582 ) -> Result<VectorizerValue> {
1583 self.call(
1584 "graph.find_related",
1585 vec![
1586 VectorizerValue::Str(collection.to_owned()),
1587 VectorizerValue::Str(node_id.to_owned()),
1588 VectorizerValue::Int(max_hops),
1589 ],
1590 )
1591 .await
1592 }
1593
1594 pub async fn graph_find_path(
1596 &self,
1597 collection: &str,
1598 from: &str,
1599 to: &str,
1600 ) -> Result<VectorizerValue> {
1601 self.call(
1602 "graph.find_path",
1603 vec![
1604 VectorizerValue::Str(collection.to_owned()),
1605 VectorizerValue::Str(from.to_owned()),
1606 VectorizerValue::Str(to.to_owned()),
1607 ],
1608 )
1609 .await
1610 }
1611
1612 pub async fn graph_create_edge(
1618 &self,
1619 collection: &str,
1620 edge: VectorizerValue,
1621 ) -> Result<VectorizerValue> {
1622 self.call(
1623 "graph.create_edge",
1624 vec![VectorizerValue::Str(collection.to_owned()), edge],
1625 )
1626 .await
1627 }
1628
1629 pub async fn graph_delete_edge(
1631 &self,
1632 collection: &str,
1633 edge_id: &str,
1634 ) -> Result<VectorizerValue> {
1635 self.call(
1636 "graph.delete_edge",
1637 vec![
1638 VectorizerValue::Str(collection.to_owned()),
1639 VectorizerValue::Str(edge_id.to_owned()),
1640 ],
1641 )
1642 .await
1643 }
1644
1645 pub async fn graph_list_edges(&self, collection: &str) -> Result<VectorizerValue> {
1647 self.call(
1648 "graph.list_edges",
1649 vec![VectorizerValue::Str(collection.to_owned())],
1650 )
1651 .await
1652 }
1653
1654 pub async fn graph_discover_edges(
1660 &self,
1661 collection: &str,
1662 request: VectorizerValue,
1663 ) -> Result<DiscoverEdgesResult> {
1664 let v = self
1665 .call(
1666 "graph.discover_edges",
1667 vec![VectorizerValue::Str(collection.to_owned()), request],
1668 )
1669 .await?;
1670 Ok(DiscoverEdgesResult {
1671 success: v
1672 .map_get("success")
1673 .and_then(|x| x.as_bool())
1674 .unwrap_or(false),
1675 total_nodes: v
1676 .map_get("total_nodes")
1677 .and_then(|x| x.as_int())
1678 .unwrap_or(0),
1679 nodes_processed: v
1680 .map_get("nodes_processed")
1681 .and_then(|x| x.as_int())
1682 .unwrap_or(0),
1683 nodes_with_edges: v
1684 .map_get("nodes_with_edges")
1685 .and_then(|x| x.as_int())
1686 .unwrap_or(0),
1687 total_edges_created: v
1688 .map_get("total_edges_created")
1689 .and_then(|x| x.as_int())
1690 .unwrap_or(0),
1691 })
1692 }
1693
1694 pub async fn graph_discover_edges_for_node(
1699 &self,
1700 collection: &str,
1701 node_id: &str,
1702 request: VectorizerValue,
1703 ) -> Result<DiscoverEdgesForNodeResult> {
1704 let v = self
1705 .call(
1706 "graph.discover_edges_for_node",
1707 vec![
1708 VectorizerValue::Str(collection.to_owned()),
1709 VectorizerValue::Str(node_id.to_owned()),
1710 request,
1711 ],
1712 )
1713 .await?;
1714 Ok(DiscoverEdgesForNodeResult {
1715 success: v
1716 .map_get("success")
1717 .and_then(|x| x.as_bool())
1718 .unwrap_or(false),
1719 node_id: v
1720 .map_get("node_id")
1721 .and_then(|x| x.as_str())
1722 .unwrap_or(node_id)
1723 .to_owned(),
1724 edges_created: v
1725 .map_get("edges_created")
1726 .and_then(|x| x.as_int())
1727 .unwrap_or(0),
1728 })
1729 }
1730
1731 pub async fn graph_discovery_status(&self, collection: &str) -> Result<GraphDiscoveryStatus> {
1733 let v = self
1734 .call(
1735 "graph.discovery_status",
1736 vec![VectorizerValue::Str(collection.to_owned())],
1737 )
1738 .await?;
1739 Ok(GraphDiscoveryStatus {
1740 total_nodes: v
1741 .map_get("total_nodes")
1742 .and_then(|x| x.as_int())
1743 .unwrap_or(0),
1744 nodes_with_edges: v
1745 .map_get("nodes_with_edges")
1746 .and_then(|x| x.as_int())
1747 .unwrap_or(0),
1748 total_edges: v
1749 .map_get("total_edges")
1750 .and_then(|x| x.as_int())
1751 .unwrap_or(0),
1752 progress_percentage: v
1753 .map_get("progress_percentage")
1754 .and_then(|x| x.as_float())
1755 .unwrap_or(0.0),
1756 })
1757 }
1758}
1759
1760impl RpcClient {
1765 pub async fn admin_stats(&self) -> Result<AdminStats> {
1767 let v = self.call("admin.stats", vec![]).await?;
1768 Ok(AdminStats {
1769 collections_count: v
1770 .map_get("collections_count")
1771 .and_then(|x| x.as_int())
1772 .unwrap_or(0),
1773 total_vectors: v
1774 .map_get("total_vectors")
1775 .and_then(|x| x.as_int())
1776 .unwrap_or(0),
1777 version: v
1778 .map_get("version")
1779 .and_then(|x| x.as_str())
1780 .unwrap_or("")
1781 .to_owned(),
1782 })
1783 }
1784
1785 pub async fn admin_status(&self) -> Result<AdminStatus> {
1787 let v = self.call("admin.status", vec![]).await?;
1788 Ok(AdminStatus {
1789 ready: v
1790 .map_get("ready")
1791 .and_then(|x| x.as_bool())
1792 .unwrap_or(false),
1793 collections_count: v
1794 .map_get("collections_count")
1795 .and_then(|x| x.as_int())
1796 .unwrap_or(0),
1797 version: v
1798 .map_get("version")
1799 .and_then(|x| x.as_str())
1800 .unwrap_or("")
1801 .to_owned(),
1802 })
1803 }
1804
1805 pub async fn admin_logs(&self, request: Option<VectorizerValue>) -> Result<VectorizerValue> {
1808 let args = request.map(|r| vec![r]).unwrap_or_default();
1809 self.call("admin.logs", args).await
1810 }
1811
1812 pub async fn admin_indexing_progress(&self) -> Result<VectorizerValue> {
1814 self.call("admin.indexing_progress", vec![]).await
1815 }
1816
1817 pub async fn admin_config_get(&self) -> Result<VectorizerValue> {
1819 self.call("admin.config_get", vec![]).await
1820 }
1821
1822 pub async fn admin_config_update(&self, patch: VectorizerValue) -> Result<bool> {
1826 let v = self.call("admin.config_update", vec![patch]).await?;
1827 need_bool(&v, "admin.config_update", "success")
1828 }
1829
1830 pub async fn admin_backups_list(&self) -> Result<VectorizerValue> {
1832 self.call("admin.backups_list", vec![]).await
1833 }
1834
1835 pub async fn admin_backups_create(&self, request: VectorizerValue) -> Result<String> {
1840 let v = self.call("admin.backups_create", vec![request]).await?;
1841 need_str(&v, "admin.backups_create", "backup_id")
1842 }
1843
1844 pub async fn admin_backups_restore(&self, request: VectorizerValue) -> Result<bool> {
1848 let v = self.call("admin.backups_restore", vec![request]).await?;
1849 need_bool(&v, "admin.backups_restore", "success")
1850 }
1851
1852 pub async fn admin_workspaces_list(&self) -> Result<VectorizerValue> {
1854 self.call("admin.workspaces_list", vec![]).await
1855 }
1856
1857 pub async fn admin_workspace_get(&self) -> Result<VectorizerValue> {
1859 self.call("admin.workspace_get", vec![]).await
1860 }
1861
1862 pub async fn admin_workspace_add(&self, request: VectorizerValue) -> Result<VectorizerValue> {
1866 self.call("admin.workspace_add", vec![request]).await
1867 }
1868
1869 pub async fn admin_workspace_remove(&self, name: &str) -> Result<bool> {
1871 let v = self
1872 .call(
1873 "admin.workspace_remove",
1874 vec![VectorizerValue::Str(name.to_owned())],
1875 )
1876 .await?;
1877 need_bool(&v, "admin.workspace_remove", "success")
1878 }
1879
1880 pub async fn admin_restart(&self) -> Result<bool> {
1882 let v = self.call("admin.restart", vec![]).await?;
1883 need_bool(&v, "admin.restart", "success")
1884 }
1885
1886 pub async fn admin_slow_queries_list(&self) -> Result<VectorizerValue> {
1888 self.call("admin.slow_queries_list", vec![]).await
1889 }
1890
1891 pub async fn admin_slow_queries_config(
1897 &self,
1898 config: VectorizerValue,
1899 ) -> Result<SlowQueryConfigResult> {
1900 let v = self.call("admin.slow_queries_config", vec![config]).await?;
1901 Ok(SlowQueryConfigResult {
1902 threshold_ms: v
1903 .map_get("threshold_ms")
1904 .and_then(|x| x.as_int())
1905 .unwrap_or(0),
1906 capacity: v.map_get("capacity").and_then(|x| x.as_int()).unwrap_or(0),
1907 status: v
1908 .map_get("status")
1909 .and_then(|x| x.as_str())
1910 .unwrap_or("ok")
1911 .to_owned(),
1912 })
1913 }
1914}
1915
1916impl RpcClient {
1921 pub async fn auth_me(&self) -> Result<AuthMeResult> {
1923 let v = self.call("auth.me", vec![]).await?;
1924 Ok(AuthMeResult {
1925 username: v
1926 .map_get("username")
1927 .and_then(|x| x.as_str())
1928 .unwrap_or("unknown")
1929 .to_owned(),
1930 authenticated: v
1931 .map_get("authenticated")
1932 .and_then(|x| x.as_bool())
1933 .unwrap_or(false),
1934 })
1935 }
1936
1937 pub async fn auth_logout(&self, token: &str) -> Result<VectorizerValue> {
1939 self.call("auth.logout", vec![VectorizerValue::Str(token.to_owned())])
1940 .await
1941 }
1942
1943 pub async fn auth_refresh_token(&self, token: &str) -> Result<RefreshTokenResult> {
1945 let v = self
1946 .call(
1947 "auth.refresh_token",
1948 vec![VectorizerValue::Str(token.to_owned())],
1949 )
1950 .await?;
1951 Ok(RefreshTokenResult {
1952 access_token: need_str(&v, "auth.refresh_token", "access_token")?,
1953 token_type: v
1954 .map_get("token_type")
1955 .and_then(|x| x.as_str())
1956 .unwrap_or("Bearer")
1957 .to_owned(),
1958 })
1959 }
1960
1961 pub async fn auth_validate_password(&self, password: &str) -> Result<ValidatePasswordResult> {
1964 let v = self
1965 .call(
1966 "auth.validate_password",
1967 vec![VectorizerValue::Str(password.to_owned())],
1968 )
1969 .await?;
1970 let errors = v
1971 .map_get("errors")
1972 .and_then(|x| x.as_array())
1973 .map(|arr| {
1974 arr.iter()
1975 .filter_map(|x| x.as_str().map(str::to_owned))
1976 .collect()
1977 })
1978 .unwrap_or_default();
1979 Ok(ValidatePasswordResult {
1980 valid: v
1981 .map_get("valid")
1982 .and_then(|x| x.as_bool())
1983 .unwrap_or(false),
1984 errors,
1985 })
1986 }
1987
1988 pub async fn auth_api_keys_create(&self, request: VectorizerValue) -> Result<ApiKeyCreated> {
1993 let v = self.call("auth.api_keys_create", vec![request]).await?;
1994 Ok(ApiKeyCreated {
1995 api_key: need_str(&v, "auth.api_keys_create", "api_key")?,
1996 id: need_str(&v, "auth.api_keys_create", "id")?,
1997 name: need_str(&v, "auth.api_keys_create", "name")?,
1998 })
1999 }
2000
2001 pub async fn auth_api_keys_list(&self) -> Result<VectorizerValue> {
2003 self.call("auth.api_keys_list", vec![]).await
2004 }
2005
2006 pub async fn auth_api_keys_revoke(&self, key_id: &str) -> Result<bool> {
2008 let v = self
2009 .call(
2010 "auth.api_keys_revoke",
2011 vec![VectorizerValue::Str(key_id.to_owned())],
2012 )
2013 .await?;
2014 need_bool(&v, "auth.api_keys_revoke", "success")
2015 }
2016
2017 pub async fn rotate_api_key_rpc(&self, key_id: &str) -> Result<RotatedApiKey> {
2022 let v = self
2023 .call(
2024 "auth.api_keys_rotate",
2025 vec![VectorizerValue::Str(key_id.to_owned())],
2026 )
2027 .await?;
2028 Ok(RotatedApiKey {
2029 old_key_id: need_str(&v, "auth.api_keys_rotate", "old_key_id")?,
2030 new_key_id: need_str(&v, "auth.api_keys_rotate", "new_key_id")?,
2031 new_token: need_str(&v, "auth.api_keys_rotate", "new_token")?,
2032 grace_until: v
2033 .map_get("grace_until")
2034 .and_then(|x| x.as_str())
2035 .map(str::to_owned),
2036 })
2037 }
2038
2039 pub async fn auth_api_keys_create_scoped(
2044 &self,
2045 request: VectorizerValue,
2046 ) -> Result<ApiKeyCreated> {
2047 let v = self
2048 .call("auth.api_keys_create_scoped", vec![request])
2049 .await?;
2050 Ok(ApiKeyCreated {
2051 api_key: need_str(&v, "auth.api_keys_create_scoped", "api_key")?,
2052 id: need_str(&v, "auth.api_keys_create_scoped", "id")?,
2053 name: need_str(&v, "auth.api_keys_create_scoped", "name")?,
2054 })
2055 }
2056
2057 pub async fn auth_users_create(&self, request: VectorizerValue) -> Result<VectorizerValue> {
2060 self.call("auth.users_create", vec![request]).await
2061 }
2062
2063 pub async fn auth_users_list(&self) -> Result<VectorizerValue> {
2065 self.call("auth.users_list", vec![]).await
2066 }
2067
2068 pub async fn auth_users_delete(&self, request: VectorizerValue) -> Result<VectorizerValue> {
2070 self.call("auth.users_delete", vec![request]).await
2071 }
2072
2073 pub async fn auth_users_change_password(
2076 &self,
2077 request: VectorizerValue,
2078 ) -> Result<VectorizerValue> {
2079 self.call("auth.users_change_password", vec![request]).await
2080 }
2081
2082 pub async fn auth_introspect(&self, token: &str) -> Result<VectorizerValue> {
2084 self.call(
2085 "auth.introspect",
2086 vec![VectorizerValue::Str(token.to_owned())],
2087 )
2088 .await
2089 }
2090
2091 pub async fn auth_audit(&self, request: VectorizerValue) -> Result<VectorizerValue> {
2096 self.call("auth.audit", vec![request]).await
2097 }
2098}
2099
2100impl RpcClient {
2105 pub async fn replication_status(&self) -> Result<VectorizerValue> {
2107 self.call("replication.status", vec![]).await
2108 }
2109
2110 pub async fn replication_configure(
2116 &self,
2117 config: VectorizerValue,
2118 ) -> Result<ReplicationConfigureResult> {
2119 let v = self.call("replication.configure", vec![config]).await?;
2120 Ok(ReplicationConfigureResult {
2121 success: need_bool(&v, "replication.configure", "success")?,
2122 role: need_str(&v, "replication.configure", "role")?,
2123 message: v
2124 .map_get("message")
2125 .and_then(|x| x.as_str())
2126 .unwrap_or("")
2127 .to_owned(),
2128 })
2129 }
2130
2131 pub async fn replication_stats(&self) -> Result<VectorizerValue> {
2133 self.call("replication.stats", vec![]).await
2134 }
2135
2136 pub async fn replication_replicas_list(&self) -> Result<VectorizerValue> {
2138 self.call("replication.replicas_list", vec![]).await
2139 }
2140}
2141
2142impl RpcClient {
2147 pub async fn cluster_failover(&self, replica_id: &str) -> Result<VectorizerValue> {
2149 self.call(
2150 "cluster.failover",
2151 vec![VectorizerValue::Str(replica_id.to_owned())],
2152 )
2153 .await
2154 }
2155
2156 pub async fn cluster_replica_resync(&self, replica_id: &str) -> Result<VectorizerValue> {
2158 self.call(
2159 "cluster.replica_resync",
2160 vec![VectorizerValue::Str(replica_id.to_owned())],
2161 )
2162 .await
2163 }
2164
2165 pub async fn cluster_peer_add(&self, request: VectorizerValue) -> Result<VectorizerValue> {
2170 self.call("cluster.peer_add", vec![request]).await
2171 }
2172
2173 pub async fn cluster_rebalance(&self) -> Result<VectorizerValue> {
2175 self.call("cluster.rebalance", vec![]).await
2176 }
2177
2178 pub async fn cluster_rebalance_status(&self) -> Result<RebalanceStatus> {
2181 let v = self.call("cluster.rebalance_status", vec![]).await?;
2182 Ok(RebalanceStatus {
2183 status: v
2184 .map_get("status")
2185 .and_then(|x| x.as_str())
2186 .map(str::to_owned),
2187 message: v
2188 .map_get("message")
2189 .and_then(|x| x.as_str())
2190 .map(str::to_owned),
2191 })
2192 }
2193}
2194
2195#[cfg(test)]
2200mod tests {
2201 use super::*;
2202
2203 #[test]
2206 fn collection_info_fields_present() {
2207 let map = VectorizerValue::Map(vec![
2208 (
2209 VectorizerValue::Str("name".into()),
2210 VectorizerValue::Str("test".into()),
2211 ),
2212 (
2213 VectorizerValue::Str("vector_count".into()),
2214 VectorizerValue::Int(42),
2215 ),
2216 (
2217 VectorizerValue::Str("document_count".into()),
2218 VectorizerValue::Int(10),
2219 ),
2220 (
2221 VectorizerValue::Str("dimension".into()),
2222 VectorizerValue::Int(512),
2223 ),
2224 (
2225 VectorizerValue::Str("metric".into()),
2226 VectorizerValue::Str("Cosine".into()),
2227 ),
2228 (
2229 VectorizerValue::Str("created_at".into()),
2230 VectorizerValue::Str("2024-01-01T00:00:00Z".into()),
2231 ),
2232 (
2233 VectorizerValue::Str("updated_at".into()),
2234 VectorizerValue::Str("2024-01-02T00:00:00Z".into()),
2235 ),
2236 ]);
2237 let info = CollectionInfo {
2238 name: need_str(&map, "test", "name").unwrap(),
2239 vector_count: need_int(&map, "test", "vector_count").unwrap(),
2240 document_count: need_int(&map, "test", "document_count").unwrap(),
2241 dimension: need_int(&map, "test", "dimension").unwrap(),
2242 metric: need_str(&map, "test", "metric").unwrap(),
2243 created_at: need_str(&map, "test", "created_at").unwrap(),
2244 updated_at: need_str(&map, "test", "updated_at").unwrap(),
2245 };
2246 assert_eq!(info.name, "test");
2247 assert_eq!(info.vector_count, 42);
2248 assert_eq!(info.dimension, 512);
2249 }
2250
2251 #[test]
2252 fn create_collection_result_decodes() {
2253 let map = VectorizerValue::Map(vec![
2254 (
2255 VectorizerValue::Str("name".into()),
2256 VectorizerValue::Str("myc".into()),
2257 ),
2258 (
2259 VectorizerValue::Str("dimension".into()),
2260 VectorizerValue::Int(128),
2261 ),
2262 (
2263 VectorizerValue::Str("metric".into()),
2264 VectorizerValue::Str("cosine".into()),
2265 ),
2266 (
2267 VectorizerValue::Str("success".into()),
2268 VectorizerValue::Bool(true),
2269 ),
2270 ]);
2271 let r = CreateCollectionResult {
2272 name: need_str(&map, "c", "name").unwrap(),
2273 dimension: need_int(&map, "c", "dimension").unwrap(),
2274 metric: need_str(&map, "c", "metric").unwrap(),
2275 success: need_bool(&map, "c", "success").unwrap(),
2276 };
2277 assert!(r.success);
2278 assert_eq!(r.dimension, 128);
2279 }
2280
2281 #[test]
2282 fn cleanup_empty_result_decodes() {
2283 let map = VectorizerValue::Map(vec![
2284 (
2285 VectorizerValue::Str("removed".into()),
2286 VectorizerValue::Int(3),
2287 ),
2288 (
2289 VectorizerValue::Str("dry_run".into()),
2290 VectorizerValue::Bool(false),
2291 ),
2292 ]);
2293 let r = CleanupEmptyResult {
2294 removed: need_int(&map, "c", "removed").unwrap(),
2295 dry_run: need_bool(&map, "c", "dry_run").unwrap(),
2296 };
2297 assert_eq!(r.removed, 3);
2298 assert!(!r.dry_run);
2299 }
2300
2301 #[test]
2304 fn vector_write_result_decodes() {
2305 let map = VectorizerValue::Map(vec![
2306 (
2307 VectorizerValue::Str("id".into()),
2308 VectorizerValue::Str("abc-123".into()),
2309 ),
2310 (
2311 VectorizerValue::Str("success".into()),
2312 VectorizerValue::Bool(true),
2313 ),
2314 ]);
2315 let r = VectorWriteResult {
2316 id: need_str(&map, "v", "id").unwrap(),
2317 success: need_bool(&map, "v", "success").unwrap(),
2318 };
2319 assert_eq!(r.id, "abc-123");
2320 assert!(r.success);
2321 }
2322
2323 #[test]
2324 fn batch_insert_result_decodes() {
2325 let item = VectorizerValue::Map(vec![
2326 (
2327 VectorizerValue::Str("index".into()),
2328 VectorizerValue::Int(0),
2329 ),
2330 (
2331 VectorizerValue::Str("id".into()),
2332 VectorizerValue::Str("x".into()),
2333 ),
2334 (
2335 VectorizerValue::Str("status".into()),
2336 VectorizerValue::Str("ok".into()),
2337 ),
2338 ]);
2339 let items = &[item];
2340 let results = decode_batch_items(items);
2341 assert_eq!(results.len(), 1);
2342 assert_eq!(results[0].status, "ok");
2343 assert_eq!(results[0].id.as_deref(), Some("x"));
2344 }
2345
2346 #[test]
2347 fn batch_search_result_decodes() {
2348 let hit = VectorizerValue::Map(vec![
2349 (
2350 VectorizerValue::Str("id".into()),
2351 VectorizerValue::Str("v1".into()),
2352 ),
2353 (
2354 VectorizerValue::Str("score".into()),
2355 VectorizerValue::Float(0.95),
2356 ),
2357 ]);
2358 let hits = &[hit];
2359 let decoded = decode_search_hits(hits);
2360 assert_eq!(decoded.len(), 1);
2361 assert_eq!(decoded[0].id, "v1");
2362 assert!((decoded[0].score - 0.95).abs() < 1e-6);
2363 }
2364
2365 #[test]
2366 fn move_rpc_result_decodes() {
2367 let map = VectorizerValue::Map(vec![
2368 (
2369 VectorizerValue::Str("src".into()),
2370 VectorizerValue::Str("col_a".into()),
2371 ),
2372 (
2373 VectorizerValue::Str("dst".into()),
2374 VectorizerValue::Str("col_b".into()),
2375 ),
2376 (
2377 VectorizerValue::Str("moved".into()),
2378 VectorizerValue::Int(5),
2379 ),
2380 (
2381 VectorizerValue::Str("failed".into()),
2382 VectorizerValue::Int(1),
2383 ),
2384 ]);
2385 let r = MoveRpcResult {
2386 src: need_str(&map, "m", "src").unwrap(),
2387 dst: need_str(&map, "m", "dst").unwrap(),
2388 moved: map.map_get("moved").and_then(|x| x.as_int()).unwrap_or(0),
2389 failed: map.map_get("failed").and_then(|x| x.as_int()).unwrap_or(0),
2390 };
2391 assert_eq!(r.src, "col_a");
2392 assert_eq!(r.moved, 5);
2393 }
2394
2395 #[test]
2396 fn set_expiry_result_decodes() {
2397 let map = VectorizerValue::Map(vec![
2398 (
2399 VectorizerValue::Str("id".into()),
2400 VectorizerValue::Str("v99".into()),
2401 ),
2402 (
2403 VectorizerValue::Str("expires_at".into()),
2404 VectorizerValue::Int(9_999_999),
2405 ),
2406 (
2407 VectorizerValue::Str("success".into()),
2408 VectorizerValue::Bool(true),
2409 ),
2410 ]);
2411 let r = SetExpiryResult {
2412 id: need_str(&map, "se", "id").unwrap(),
2413 expires_at: need_int(&map, "se", "expires_at").unwrap(),
2414 success: need_bool(&map, "se", "success").unwrap(),
2415 };
2416 assert_eq!(r.expires_at, 9_999_999);
2417 }
2418
2419 #[test]
2422 fn search_explain_trace_decodes() {
2423 let trace_val = VectorizerValue::Map(vec![
2424 (
2425 VectorizerValue::Str("visited_nodes".into()),
2426 VectorizerValue::Int(50),
2427 ),
2428 (
2429 VectorizerValue::Str("ef_search".into()),
2430 VectorizerValue::Int(100),
2431 ),
2432 (
2433 VectorizerValue::Str("hnsw_search_ms".into()),
2434 VectorizerValue::Float(1.5),
2435 ),
2436 (
2437 VectorizerValue::Str("total_ms".into()),
2438 VectorizerValue::Float(2.0),
2439 ),
2440 ]);
2441 let trace = SearchTrace {
2442 visited_nodes: trace_val
2443 .map_get("visited_nodes")
2444 .and_then(|x| x.as_int())
2445 .unwrap_or(0),
2446 ef_search: trace_val
2447 .map_get("ef_search")
2448 .and_then(|x| x.as_int())
2449 .unwrap_or(0),
2450 hnsw_search_ms: trace_val
2451 .map_get("hnsw_search_ms")
2452 .and_then(|x| x.as_float())
2453 .unwrap_or(0.0),
2454 total_ms: trace_val
2455 .map_get("total_ms")
2456 .and_then(|x| x.as_float())
2457 .unwrap_or(0.0),
2458 };
2459 assert_eq!(trace.visited_nodes, 50);
2460 assert!((trace.hnsw_search_ms - 1.5).abs() < 1e-6);
2461 }
2462
2463 #[test]
2466 fn discover_result_decodes() {
2467 let map = VectorizerValue::Map(vec![
2468 (
2469 VectorizerValue::Str("answer_prompt".into()),
2470 VectorizerValue::Str("Here is ...".into()),
2471 ),
2472 (
2473 VectorizerValue::Str("sections".into()),
2474 VectorizerValue::Int(3),
2475 ),
2476 (
2477 VectorizerValue::Str("bullets".into()),
2478 VectorizerValue::Int(12),
2479 ),
2480 (
2481 VectorizerValue::Str("chunks".into()),
2482 VectorizerValue::Int(8),
2483 ),
2484 ]);
2485 let r = DiscoverResult {
2486 answer_prompt: need_str(&map, "d", "answer_prompt").unwrap(),
2487 sections: map
2488 .map_get("sections")
2489 .and_then(|x| x.as_int())
2490 .unwrap_or(0),
2491 bullets: map.map_get("bullets").and_then(|x| x.as_int()).unwrap_or(0),
2492 chunks: map.map_get("chunks").and_then(|x| x.as_int()).unwrap_or(0),
2493 };
2494 assert_eq!(r.bullets, 12);
2495 }
2496
2497 #[test]
2498 fn expand_queries_result_decodes() {
2499 let map = VectorizerValue::Map(vec![
2500 (
2501 VectorizerValue::Str("original_query".into()),
2502 VectorizerValue::Str("rust".into()),
2503 ),
2504 (
2505 VectorizerValue::Str("expanded_queries".into()),
2506 VectorizerValue::Array(vec![
2507 VectorizerValue::Str("rust programming".into()),
2508 VectorizerValue::Str("rust language".into()),
2509 ]),
2510 ),
2511 (
2512 VectorizerValue::Str("count".into()),
2513 VectorizerValue::Int(2),
2514 ),
2515 ]);
2516 let expanded: Vec<String> = map
2517 .map_get("expanded_queries")
2518 .and_then(|x| x.as_array())
2519 .map(|arr| {
2520 arr.iter()
2521 .filter_map(|x| x.as_str().map(str::to_owned))
2522 .collect()
2523 })
2524 .unwrap_or_default();
2525 assert_eq!(expanded.len(), 2);
2526 }
2527
2528 #[test]
2531 fn graph_discovery_status_decodes() {
2532 let map = VectorizerValue::Map(vec![
2533 (
2534 VectorizerValue::Str("total_nodes".into()),
2535 VectorizerValue::Int(100),
2536 ),
2537 (
2538 VectorizerValue::Str("nodes_with_edges".into()),
2539 VectorizerValue::Int(75),
2540 ),
2541 (
2542 VectorizerValue::Str("total_edges".into()),
2543 VectorizerValue::Int(200),
2544 ),
2545 (
2546 VectorizerValue::Str("progress_percentage".into()),
2547 VectorizerValue::Float(75.0),
2548 ),
2549 ]);
2550 let r = GraphDiscoveryStatus {
2551 total_nodes: map
2552 .map_get("total_nodes")
2553 .and_then(|x| x.as_int())
2554 .unwrap_or(0),
2555 nodes_with_edges: map
2556 .map_get("nodes_with_edges")
2557 .and_then(|x| x.as_int())
2558 .unwrap_or(0),
2559 total_edges: map
2560 .map_get("total_edges")
2561 .and_then(|x| x.as_int())
2562 .unwrap_or(0),
2563 progress_percentage: map
2564 .map_get("progress_percentage")
2565 .and_then(|x| x.as_float())
2566 .unwrap_or(0.0),
2567 };
2568 assert_eq!(r.total_nodes, 100);
2569 assert!((r.progress_percentage - 75.0).abs() < 1e-6);
2570 }
2571
2572 #[test]
2573 fn discover_edges_result_decodes() {
2574 let map = VectorizerValue::Map(vec![
2575 (
2576 VectorizerValue::Str("success".into()),
2577 VectorizerValue::Bool(true),
2578 ),
2579 (
2580 VectorizerValue::Str("total_nodes".into()),
2581 VectorizerValue::Int(50),
2582 ),
2583 (
2584 VectorizerValue::Str("nodes_processed".into()),
2585 VectorizerValue::Int(50),
2586 ),
2587 (
2588 VectorizerValue::Str("nodes_with_edges".into()),
2589 VectorizerValue::Int(40),
2590 ),
2591 (
2592 VectorizerValue::Str("total_edges_created".into()),
2593 VectorizerValue::Int(120),
2594 ),
2595 ]);
2596 let r = DiscoverEdgesResult {
2597 success: map
2598 .map_get("success")
2599 .and_then(|x| x.as_bool())
2600 .unwrap_or(false),
2601 total_nodes: map
2602 .map_get("total_nodes")
2603 .and_then(|x| x.as_int())
2604 .unwrap_or(0),
2605 nodes_processed: map
2606 .map_get("nodes_processed")
2607 .and_then(|x| x.as_int())
2608 .unwrap_or(0),
2609 nodes_with_edges: map
2610 .map_get("nodes_with_edges")
2611 .and_then(|x| x.as_int())
2612 .unwrap_or(0),
2613 total_edges_created: map
2614 .map_get("total_edges_created")
2615 .and_then(|x| x.as_int())
2616 .unwrap_or(0),
2617 };
2618 assert!(r.success);
2619 assert_eq!(r.total_edges_created, 120);
2620 }
2621
2622 #[test]
2625 fn admin_stats_decodes() {
2626 let map = VectorizerValue::Map(vec![
2627 (
2628 VectorizerValue::Str("collections_count".into()),
2629 VectorizerValue::Int(5),
2630 ),
2631 (
2632 VectorizerValue::Str("total_vectors".into()),
2633 VectorizerValue::Int(1000),
2634 ),
2635 (
2636 VectorizerValue::Str("version".into()),
2637 VectorizerValue::Str("3.8.0".into()),
2638 ),
2639 ]);
2640 let r = AdminStats {
2641 collections_count: map
2642 .map_get("collections_count")
2643 .and_then(|x| x.as_int())
2644 .unwrap_or(0),
2645 total_vectors: map
2646 .map_get("total_vectors")
2647 .and_then(|x| x.as_int())
2648 .unwrap_or(0),
2649 version: map
2650 .map_get("version")
2651 .and_then(|x| x.as_str())
2652 .unwrap_or("")
2653 .to_owned(),
2654 };
2655 assert_eq!(r.collections_count, 5);
2656 assert_eq!(r.version, "3.8.0");
2657 }
2658
2659 #[test]
2660 fn slow_query_config_decodes() {
2661 let map = VectorizerValue::Map(vec![
2662 (
2663 VectorizerValue::Str("threshold_ms".into()),
2664 VectorizerValue::Int(100),
2665 ),
2666 (
2667 VectorizerValue::Str("capacity".into()),
2668 VectorizerValue::Int(500),
2669 ),
2670 (
2671 VectorizerValue::Str("status".into()),
2672 VectorizerValue::Str("ok".into()),
2673 ),
2674 ]);
2675 let r = SlowQueryConfigResult {
2676 threshold_ms: map
2677 .map_get("threshold_ms")
2678 .and_then(|x| x.as_int())
2679 .unwrap_or(0),
2680 capacity: map
2681 .map_get("capacity")
2682 .and_then(|x| x.as_int())
2683 .unwrap_or(0),
2684 status: map
2685 .map_get("status")
2686 .and_then(|x| x.as_str())
2687 .unwrap_or("")
2688 .to_owned(),
2689 };
2690 assert_eq!(r.threshold_ms, 100);
2691 assert_eq!(r.status, "ok");
2692 }
2693
2694 #[test]
2697 fn api_key_created_decodes() {
2698 let map = VectorizerValue::Map(vec![
2699 (
2700 VectorizerValue::Str("api_key".into()),
2701 VectorizerValue::Str("vz_abc123".into()),
2702 ),
2703 (
2704 VectorizerValue::Str("id".into()),
2705 VectorizerValue::Str("key-id-1".into()),
2706 ),
2707 (
2708 VectorizerValue::Str("name".into()),
2709 VectorizerValue::Str("ci-key".into()),
2710 ),
2711 ]);
2712 let r = ApiKeyCreated {
2713 api_key: need_str(&map, "a", "api_key").unwrap(),
2714 id: need_str(&map, "a", "id").unwrap(),
2715 name: need_str(&map, "a", "name").unwrap(),
2716 };
2717 assert_eq!(r.api_key, "vz_abc123");
2718 assert_eq!(r.name, "ci-key");
2719 }
2720
2721 #[test]
2722 fn rotated_api_key_decodes() {
2723 let map = VectorizerValue::Map(vec![
2724 (
2725 VectorizerValue::Str("old_key_id".into()),
2726 VectorizerValue::Str("old".into()),
2727 ),
2728 (
2729 VectorizerValue::Str("new_key_id".into()),
2730 VectorizerValue::Str("new".into()),
2731 ),
2732 (
2733 VectorizerValue::Str("new_token".into()),
2734 VectorizerValue::Str("vz_new_xxx".into()),
2735 ),
2736 ]);
2737 let r = RotatedApiKey {
2738 old_key_id: need_str(&map, "r", "old_key_id").unwrap(),
2739 new_key_id: need_str(&map, "r", "new_key_id").unwrap(),
2740 new_token: need_str(&map, "r", "new_token").unwrap(),
2741 grace_until: map
2742 .map_get("grace_until")
2743 .and_then(|x| x.as_str())
2744 .map(str::to_owned),
2745 };
2746 assert_eq!(r.old_key_id, "old");
2747 assert_eq!(r.new_token, "vz_new_xxx");
2748 assert!(r.grace_until.is_none());
2749 }
2750
2751 #[test]
2752 fn validate_password_result_decodes() {
2753 let map = VectorizerValue::Map(vec![
2754 (
2755 VectorizerValue::Str("valid".into()),
2756 VectorizerValue::Bool(false),
2757 ),
2758 (
2759 VectorizerValue::Str("errors".into()),
2760 VectorizerValue::Array(vec![VectorizerValue::Str("too short".into())]),
2761 ),
2762 ]);
2763 let errors: Vec<String> = map
2764 .map_get("errors")
2765 .and_then(|x| x.as_array())
2766 .map(|arr| {
2767 arr.iter()
2768 .filter_map(|x| x.as_str().map(str::to_owned))
2769 .collect()
2770 })
2771 .unwrap_or_default();
2772 assert_eq!(errors.len(), 1);
2773 assert_eq!(errors[0], "too short");
2774 }
2775
2776 #[test]
2779 fn replication_configure_result_decodes() {
2780 let map = VectorizerValue::Map(vec![
2781 (
2782 VectorizerValue::Str("success".into()),
2783 VectorizerValue::Bool(true),
2784 ),
2785 (
2786 VectorizerValue::Str("role".into()),
2787 VectorizerValue::Str("master".into()),
2788 ),
2789 (
2790 VectorizerValue::Str("message".into()),
2791 VectorizerValue::Str("restart required".into()),
2792 ),
2793 ]);
2794 let r = ReplicationConfigureResult {
2795 success: need_bool(&map, "rc", "success").unwrap(),
2796 role: need_str(&map, "rc", "role").unwrap(),
2797 message: map
2798 .map_get("message")
2799 .and_then(|x| x.as_str())
2800 .unwrap_or("")
2801 .to_owned(),
2802 };
2803 assert!(r.success);
2804 assert_eq!(r.role, "master");
2805 }
2806
2807 #[test]
2808 fn rebalance_status_idle_decodes() {
2809 let map = VectorizerValue::Map(vec![
2810 (
2811 VectorizerValue::Str("status".into()),
2812 VectorizerValue::Str("idle".into()),
2813 ),
2814 (
2815 VectorizerValue::Str("message".into()),
2816 VectorizerValue::Str("No rebalance".into()),
2817 ),
2818 ]);
2819 let r = RebalanceStatus {
2820 status: map
2821 .map_get("status")
2822 .and_then(|x| x.as_str())
2823 .map(str::to_owned),
2824 message: map
2825 .map_get("message")
2826 .and_then(|x| x.as_str())
2827 .map(str::to_owned),
2828 };
2829 assert_eq!(r.status.as_deref(), Some("idle"));
2830 }
2831
2832 #[test]
2835 fn need_str_errors_on_missing() {
2836 let map = VectorizerValue::Map(vec![]);
2837 assert!(need_str(&map, "cmd", "missing_field").is_err());
2838 }
2839
2840 #[test]
2841 fn need_int_errors_on_missing() {
2842 let map = VectorizerValue::Map(vec![]);
2843 assert!(need_int(&map, "cmd", "missing_field").is_err());
2844 }
2845
2846 #[test]
2847 fn decode_string_array_errors_on_non_array() {
2848 let v = VectorizerValue::Str("not_an_array".into());
2849 assert!(decode_string_array(v, "cmd").is_err());
2850 }
2851}