1use std::collections::HashMap;
4use std::sync::Arc;
5
6use reqwest::Client;
7use serde::{Deserialize, Serialize};
8
9use crate::auth::{ApiKeyAuth, Auth, AuthConfig, JwtAuth, Target};
10use crate::client::{ApiKeyPosition, ClientRequest, OramaClient};
11use crate::error::Result;
12use crate::stream_manager::OramaCoreStream;
13use crate::types::*;
14use crate::utils::{current_time_millis, format_duration};
15
16const DEFAULT_READER_URL: &str = "https://collections.orama.com";
17const DEFAULT_JWT_URL: &str = "https://app.orama.com/api/user/jwt";
18
19#[derive(Debug, Clone)]
21pub struct CollectionManagerConfig {
22 pub collection_id: String,
23 pub api_key: String,
24 pub cluster: Option<ClusterConfig>,
25 pub auth_jwt_url: Option<String>,
26}
27
28#[derive(Debug, Clone)]
30pub struct ClusterConfig {
31 pub writer_url: Option<String>,
32 pub read_url: Option<String>,
33}
34
35#[derive(Debug, Clone, Serialize)]
37pub struct NlpSearchParams {
38 pub query: String,
39 #[serde(rename = "LLMConfig", skip_serializing_if = "Option::is_none")]
40 pub llm_config: Option<LlmConfig>,
41 #[serde(rename = "userID", skip_serializing_if = "Option::is_none")]
42 pub user_id: Option<String>,
43}
44
45#[derive(Debug, Clone, Serialize)]
47pub struct CreateIndexParams {
48 #[serde(skip_serializing_if = "Option::is_none")]
49 pub id: Option<String>,
50 #[serde(skip_serializing_if = "Option::is_none")]
51 pub embeddings: Option<serde_json::Value>, }
54
55#[derive(Debug, Clone, Serialize)]
57pub struct AddHookConfig {
58 pub name: Hook,
59 pub code: String,
60}
61
62#[derive(Debug, Clone, Deserialize)]
64pub struct NewHookResponse {
65 #[serde(rename = "hookID")]
66 pub hook_id: String,
67 pub code: String,
68}
69
70#[derive(Debug, Clone, Serialize)]
72pub struct ExecuteToolsBody {
73 #[serde(skip_serializing_if = "Option::is_none")]
74 pub tool_ids: Option<Vec<String>>,
75 pub messages: Vec<Message>,
76 #[serde(skip_serializing_if = "Option::is_none")]
77 pub llm_config: Option<LlmConfig>,
78}
79
80#[derive(Debug, Clone)]
82pub struct AiNamespace {
83 client: OramaClient,
84 collection_id: String,
85}
86
87impl AiNamespace {
88 pub(crate) fn new(client: OramaClient, collection_id: String) -> Self {
89 Self {
90 client,
91 collection_id,
92 }
93 }
94
95 pub async fn nlp_search<T>(&self, params: NlpSearchParams) -> Result<Vec<NlpSearchResult<T>>>
97 where
98 T: for<'de> serde::Deserialize<'de>,
99 {
100 let request = ClientRequest::post(
101 format!("/v1/collections/{}/nlp_search", self.collection_id),
102 Target::Reader,
103 ApiKeyPosition::QueryParams,
104 params,
105 );
106
107 self.client.request(request).await
108 }
109
110 pub async fn create_ai_session(&self) -> Result<OramaCoreStream> {
112 OramaCoreStream::new(self.collection_id.clone(), self.client.clone()).await
113 }
114}
115
116#[derive(Debug, Clone)]
118pub struct CollectionsNamespace {
119 client: OramaClient,
120 collection_id: String,
121}
122
123impl CollectionsNamespace {
124 pub(crate) fn new(client: OramaClient, collection_id: String) -> Self {
125 Self {
126 client,
127 collection_id,
128 }
129 }
130
131 pub async fn get_stats(&self) -> Result<serde_json::Value> {
133 let request = ClientRequest::<()>::get(
134 format!("/v1/collections/{}/stats", self.collection_id),
135 Target::Reader,
136 ApiKeyPosition::QueryParams,
137 );
138
139 self.client.request(request).await
140 }
141
142 pub async fn get_all_docs<T>(&self, id: &str) -> Result<Vec<T>>
144 where
145 T: for<'de> serde::Deserialize<'de>,
146 {
147 let body = serde_json::json!({ "id": id });
148 let request = ClientRequest::post(
149 "/v1/collections/list".to_string(),
150 Target::Writer,
151 ApiKeyPosition::Header,
152 body,
153 );
154
155 self.client.request(request).await
156 }
157}
158
159#[derive(Debug, Clone)]
161pub struct IndexNamespace {
162 client: OramaClient,
163 collection_id: String,
164}
165
166impl IndexNamespace {
167 pub(crate) fn new(client: OramaClient, collection_id: String) -> Self {
168 Self {
169 client,
170 collection_id,
171 }
172 }
173
174 pub async fn create(&self, config: CreateIndexParams) -> Result<()> {
176 let body = serde_json::json!({
177 "id": config.id,
178 "embedding": config.embeddings
179 });
180
181 let request = ClientRequest::post(
182 format!("/v1/collections/{}/indexes/create", self.collection_id),
183 Target::Writer,
184 ApiKeyPosition::Header,
185 body,
186 );
187
188 let _: serde_json::Value = self.client.request(request).await?;
189 Ok(())
190 }
191
192 pub async fn delete(&self, index_id: &str) -> Result<()> {
194 let body = serde_json::json!({
195 "index_id_to_delete": index_id
196 });
197
198 let request = ClientRequest::post(
199 format!("/v1/collections/{}/indexes/delete", self.collection_id),
200 Target::Writer,
201 ApiKeyPosition::Header,
202 body,
203 );
204
205 let _: serde_json::Value = self.client.request(request).await?;
206 Ok(())
207 }
208
209 pub fn set(&self, id: String) -> Index {
211 Index::new(self.client.clone(), self.collection_id.clone(), id)
212 }
213}
214
215#[derive(Debug, Clone)]
217pub struct HooksNamespace {
218 client: OramaClient,
219 collection_id: String,
220}
221
222impl HooksNamespace {
223 pub(crate) fn new(client: OramaClient, collection_id: String) -> Self {
224 Self {
225 client,
226 collection_id,
227 }
228 }
229
230 pub async fn insert(&self, config: AddHookConfig) -> Result<NewHookResponse> {
232 let body = serde_json::json!({
233 "name": config.name,
234 "code": config.code
235 });
236
237 let request = ClientRequest::post(
238 format!("/v1/collections/{}/hooks/set", self.collection_id),
239 Target::Writer,
240 ApiKeyPosition::Header,
241 body,
242 );
243
244 let _: serde_json::Value = self.client.request(request).await?;
245
246 Ok(NewHookResponse {
247 hook_id: serde_json::to_string(&config.name)?,
248 code: config.code,
249 })
250 }
251
252 pub async fn list(&self) -> Result<HashMap<String, Option<String>>> {
254 let request = ClientRequest::<()>::get(
255 format!("/v1/collections/{}/hooks/list", self.collection_id),
256 Target::Writer,
257 ApiKeyPosition::Header,
258 );
259
260 let response: serde_json::Value = self.client.request(request).await?;
261 let empty_map = serde_json::Map::new();
262 let hooks = response["hooks"].as_object().unwrap_or(&empty_map);
263
264 let mut result = HashMap::new();
265 for (key, value) in hooks {
266 let val = value.as_str().map(|s| s.to_string());
267 result.insert(key.clone(), val);
268 }
269
270 Ok(result)
271 }
272
273 pub async fn delete(&self, hook: Hook) -> Result<()> {
275 let body = serde_json::json!({
276 "name_to_delete": hook
277 });
278
279 let request = ClientRequest::post(
280 format!("/v1/collections/{}/hooks/delete", self.collection_id),
281 Target::Writer,
282 ApiKeyPosition::Header,
283 body,
284 );
285
286 let _: serde_json::Value = self.client.request(request).await?;
287 Ok(())
288 }
289}
290
291#[derive(Debug, Clone)]
293pub struct SystemPromptsNamespace {
294 client: OramaClient,
295 collection_id: String,
296}
297
298impl SystemPromptsNamespace {
299 pub(crate) fn new(client: OramaClient, collection_id: String) -> Self {
300 Self {
301 client,
302 collection_id,
303 }
304 }
305
306 pub async fn insert(&self, system_prompt: InsertSystemPromptBody) -> Result<serde_json::Value> {
308 let request = ClientRequest::post(
309 format!(
310 "/v1/collections/{}/system_prompts/insert",
311 self.collection_id
312 ),
313 Target::Writer,
314 ApiKeyPosition::Header,
315 system_prompt,
316 );
317
318 self.client.request(request).await
319 }
320
321 pub async fn get(&self, id: &str) -> Result<SystemPrompt> {
323 let request = ClientRequest::<()>::get(
324 format!("/v1/collections/{}/system_prompts/get", self.collection_id),
325 Target::Reader,
326 ApiKeyPosition::QueryParams,
327 )
328 .with_param("system_prompt_id", id);
329
330 let response: serde_json::Value = self.client.request(request).await?;
331 let prompt = response["system_prompt"].clone();
332 Ok(serde_json::from_value(prompt)?)
333 }
334
335 pub async fn get_all(&self) -> Result<Vec<SystemPrompt>> {
337 let request = ClientRequest::<()>::get(
338 format!("/v1/collections/{}/system_prompts/all", self.collection_id),
339 Target::Reader,
340 ApiKeyPosition::QueryParams,
341 );
342
343 let response: serde_json::Value = self.client.request(request).await?;
344 let prompts = response["system_prompts"].clone();
345 Ok(serde_json::from_value(prompts)?)
346 }
347
348 pub async fn delete(&self, id: &str) -> Result<serde_json::Value> {
350 let body = serde_json::json!({ "id": id });
351 let request = ClientRequest::post(
352 format!(
353 "/v1/collections/{}/system_prompts/delete",
354 self.collection_id
355 ),
356 Target::Writer,
357 ApiKeyPosition::Header,
358 body,
359 );
360
361 self.client.request(request).await
362 }
363
364 pub async fn update(&self, system_prompt: SystemPrompt) -> Result<serde_json::Value> {
366 let request = ClientRequest::post(
367 format!(
368 "/v1/collections/{}/system_prompts/update",
369 self.collection_id
370 ),
371 Target::Writer,
372 ApiKeyPosition::Header,
373 system_prompt,
374 );
375
376 self.client.request(request).await
377 }
378
379 pub async fn validate(
381 &self,
382 system_prompt: SystemPrompt,
383 ) -> Result<SystemPromptValidationResponse> {
384 let request = ClientRequest::post(
385 format!(
386 "/v1/collections/{}/system_prompts/validate",
387 self.collection_id
388 ),
389 Target::Writer,
390 ApiKeyPosition::Header,
391 system_prompt,
392 );
393
394 let response: serde_json::Value = self.client.request(request).await?;
395 let result = response["result"].clone();
396 Ok(serde_json::from_value(result)?)
397 }
398}
399
400#[derive(Debug, Clone)]
402pub struct ToolsNamespace {
403 client: OramaClient,
404 collection_id: String,
405}
406
407impl ToolsNamespace {
408 pub(crate) fn new(client: OramaClient, collection_id: String) -> Self {
409 Self {
410 client,
411 collection_id,
412 }
413 }
414
415 pub async fn insert(&self, tool: InsertToolBody) -> Result<()> {
417 let request = ClientRequest::post(
418 format!("/v1/collections/{}/tools/insert", self.collection_id),
419 Target::Writer,
420 ApiKeyPosition::Header,
421 tool,
422 );
423
424 let _: serde_json::Value = self.client.request(request).await?;
425 Ok(())
426 }
427
428 pub async fn get(&self, id: &str) -> Result<Tool> {
430 let request = ClientRequest::<()>::get(
431 format!("/v1/collections/{}/tools/get", self.collection_id),
432 Target::Reader,
433 ApiKeyPosition::QueryParams,
434 )
435 .with_param("tool_id", id);
436
437 let response: serde_json::Value = self.client.request(request).await?;
438 let tool = response["tool"].clone();
439 Ok(serde_json::from_value(tool)?)
440 }
441
442 pub async fn get_all(&self) -> Result<Vec<Tool>> {
444 let request = ClientRequest::<()>::get(
445 format!("/v1/collections/{}/tools/all", self.collection_id),
446 Target::Reader,
447 ApiKeyPosition::QueryParams,
448 );
449
450 let response: serde_json::Value = self.client.request(request).await?;
451 let tools = response["tools"].clone();
452 Ok(serde_json::from_value(tools)?)
453 }
454
455 pub async fn delete(&self, id: &str) -> Result<serde_json::Value> {
457 let body = serde_json::json!({ "id": id });
458 let request = ClientRequest::post(
459 format!("/v1/collections/{}/tools/delete", self.collection_id),
460 Target::Writer,
461 ApiKeyPosition::Header,
462 body,
463 );
464
465 self.client.request(request).await
466 }
467
468 pub async fn update(&self, tool: UpdateToolBody) -> Result<serde_json::Value> {
470 let request = ClientRequest::post(
471 format!("/v1/collections/{}/tools/update", self.collection_id),
472 Target::Writer,
473 ApiKeyPosition::Header,
474 tool,
475 );
476
477 self.client.request(request).await
478 }
479
480 pub async fn execute<T>(&self, tools: ExecuteToolsBody) -> Result<ExecuteToolsParsedResponse<T>>
482 where
483 T: for<'de> serde::Deserialize<'de>,
484 {
485 let request = ClientRequest::post(
486 format!("/v1/collections/{}/tools/run", self.collection_id),
487 Target::Reader,
488 ApiKeyPosition::QueryParams,
489 tools,
490 );
491
492 self.client.request(request).await
493 }
494}
495
496#[derive(Debug, Clone)]
498pub struct Index {
499 client: OramaClient,
500 collection_id: String,
501 index_id: String,
502}
503
504impl Index {
505 pub(crate) fn new(client: OramaClient, collection_id: String, index_id: String) -> Self {
506 Self {
507 client,
508 collection_id,
509 index_id,
510 }
511 }
512
513 pub async fn reindex(&self) -> Result<()> {
515 let request = ClientRequest::<()>::post(
516 format!(
517 "/v1/collections/{}/indexes/{}/reindex",
518 self.collection_id, self.index_id
519 ),
520 Target::Writer,
521 ApiKeyPosition::Header,
522 (),
523 );
524
525 let _: serde_json::Value = self.client.request(request).await?;
526 Ok(())
527 }
528
529 pub async fn insert_documents<T>(&self, documents: Vec<T>) -> Result<()>
531 where
532 T: Serialize,
533 {
534 let body = serde_json::json!({
535 "documents": documents
536 });
537
538 let request = ClientRequest::post(
539 format!(
540 "/v1/collections/{}/indexes/{}/documents/insert",
541 self.collection_id, self.index_id
542 ),
543 Target::Writer,
544 ApiKeyPosition::Header,
545 body,
546 );
547
548 let _: serde_json::Value = self.client.request(request).await?;
549 Ok(())
550 }
551
552 pub async fn delete_documents(&self, document_ids: Vec<String>) -> Result<()> {
554 let body = serde_json::json!({
555 "document_ids": document_ids
556 });
557
558 let request = ClientRequest::post(
559 format!(
560 "/v1/collections/{}/indexes/{}/documents/delete",
561 self.collection_id, self.index_id
562 ),
563 Target::Writer,
564 ApiKeyPosition::Header,
565 body,
566 );
567
568 let _: serde_json::Value = self.client.request(request).await?;
569 Ok(())
570 }
571
572 pub async fn upsert_documents<T>(&self, documents: Vec<T>) -> Result<()>
574 where
575 T: Serialize,
576 {
577 let body = serde_json::json!({
578 "documents": documents
579 });
580
581 let request = ClientRequest::post(
582 format!(
583 "/v1/collections/{}/indexes/{}/documents/upsert",
584 self.collection_id, self.index_id
585 ),
586 Target::Writer,
587 ApiKeyPosition::Header,
588 body,
589 );
590
591 let _: serde_json::Value = self.client.request(request).await?;
592 Ok(())
593 }
594}
595
596#[derive(Debug, Clone)]
598pub struct CollectionManager {
599 client: OramaClient,
600 collection_id: String,
601 pub ai: AiNamespace,
602 pub collections: CollectionsNamespace,
603 pub index: IndexNamespace,
604 pub hooks: HooksNamespace,
605 pub system_prompts: SystemPromptsNamespace,
606 pub tools: ToolsNamespace,
607}
608
609impl CollectionManager {
610 pub async fn new(config: CollectionManagerConfig) -> Result<Self> {
612 let auth_config = if config.api_key.starts_with("p_") {
613 AuthConfig::Jwt(
615 JwtAuth::new(
616 config.auth_jwt_url.as_deref().unwrap_or(DEFAULT_JWT_URL),
617 &config.collection_id,
618 &config.api_key,
619 )
620 .with_reader_url(
621 config
622 .cluster
623 .as_ref()
624 .and_then(|c| c.read_url.as_deref())
625 .unwrap_or(DEFAULT_READER_URL),
626 )
627 .with_writer_url(
628 config
629 .cluster
630 .as_ref()
631 .and_then(|c| c.writer_url.as_deref())
632 .unwrap_or(""),
633 ),
634 )
635 } else {
636 AuthConfig::ApiKey(
638 ApiKeyAuth::new(&config.api_key)
639 .with_reader_url(
640 config
641 .cluster
642 .as_ref()
643 .and_then(|c| c.read_url.as_deref())
644 .unwrap_or(DEFAULT_READER_URL),
645 )
646 .with_writer_url(
647 config
648 .cluster
649 .as_ref()
650 .and_then(|c| c.writer_url.as_deref())
651 .unwrap_or(""),
652 ),
653 )
654 };
655
656 let client = Client::new();
657 let auth = Auth::new(auth_config, Arc::new(client));
658 let orama_client = OramaClient::new(auth)?;
659
660 let collection_id = config.collection_id.clone();
661
662 Ok(Self {
663 ai: AiNamespace::new(orama_client.clone(), collection_id.clone()),
664 collections: CollectionsNamespace::new(orama_client.clone(), collection_id.clone()),
665 index: IndexNamespace::new(orama_client.clone(), collection_id.clone()),
666 hooks: HooksNamespace::new(orama_client.clone(), collection_id.clone()),
667 system_prompts: SystemPromptsNamespace::new(
668 orama_client.clone(),
669 collection_id.clone(),
670 ),
671 tools: ToolsNamespace::new(orama_client.clone(), collection_id.clone()),
672 client: orama_client,
673 collection_id,
674 })
675 }
676
677 pub async fn search<T>(&self, query: &SearchParams) -> Result<SearchResult<T>>
679 where
680 T: for<'de> serde::Deserialize<'de>,
681 {
682 let start_time = current_time_millis();
683
684 let request = ClientRequest::post(
685 format!("/v1/collections/{}/search", self.collection_id),
686 Target::Reader,
687 ApiKeyPosition::QueryParams,
688 query,
689 );
690
691 let mut result: SearchResult<T> = self.client.request(request).await?;
692
693 let elapsed_time = current_time_millis() - start_time;
694 result.elapsed = Some(Elapsed {
695 raw: elapsed_time,
696 formatted: format_duration(elapsed_time),
697 });
698
699 Ok(result)
700 }
701}
702
703impl CollectionManagerConfig {
705 pub fn new<S: Into<String>>(collection_id: S, api_key: S) -> Self {
707 Self {
708 collection_id: collection_id.into(),
709 api_key: api_key.into(),
710 cluster: None,
711 auth_jwt_url: None,
712 }
713 }
714
715 pub fn with_cluster(mut self, cluster: ClusterConfig) -> Self {
717 self.cluster = Some(cluster);
718 self
719 }
720
721 pub fn with_auth_jwt_url<S: Into<String>>(mut self, url: S) -> Self {
723 self.auth_jwt_url = Some(url.into());
724 self
725 }
726}
727
728impl ClusterConfig {
729 pub fn new() -> Self {
731 Self {
732 writer_url: None,
733 read_url: None,
734 }
735 }
736
737 pub fn with_writer_url<S: Into<String>>(mut self, url: S) -> Self {
739 self.writer_url = Some(url.into());
740 self
741 }
742
743 pub fn with_read_url<S: Into<String>>(mut self, url: S) -> Self {
745 self.read_url = Some(url.into());
746 self
747 }
748}
749
750impl Default for ClusterConfig {
751 fn default() -> Self {
752 Self::new()
753 }
754}